v5.1.6: Fix block sync stall for fresh nodes, REST API refactor
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Build All Platforms / build-linux-daemon (push) Failing after 25m20s
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Build All Platforms / build-linux-daemon (push) Failing after 25m20s
Critical fix: revert initial sync from getheaders back to getblocks.
The headers-first change (05b1fd1) broke chain continuation — after
downloading the first 2000 blocks, fresh nodes would stall because
the getheaders path has no orphan-based continuation mechanism.
The getblocks/inv/orphan cycle is required for full chain sync.
Also adds getblocks fallback to the headers handler so if headers
are used via other paths, sync still continues.
Other changes:
- Extract REST API into separate rest.cpp/rest.h
- Add REST rate limiting and CORS support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+18
-183
@@ -1020,187 +1020,10 @@ static string JSONRPCExecBatch(const Array& vReq)
|
||||
return write_string(Value(ret), false) + "\n";
|
||||
}
|
||||
|
||||
// REST API support
|
||||
extern Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPrintTransactionDetail);
|
||||
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, json_spirit::Object& entry);
|
||||
// REST API support (implementation in rest.cpp)
|
||||
#include "rest.h"
|
||||
|
||||
static string HTTPReplyREST(int nStatus, const string& strMsg, const string& contentType = "application/json")
|
||||
{
|
||||
const char *cStatus;
|
||||
if (nStatus == HTTP_OK) cStatus = "OK";
|
||||
else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request";
|
||||
else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found";
|
||||
else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error";
|
||||
else cStatus = "";
|
||||
return strprintf(
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Date: %s\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Length: %"PRIszu"\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n"
|
||||
"Server: Triangles-json-rpc/%s\r\n"
|
||||
"\r\n"
|
||||
"%s",
|
||||
nStatus,
|
||||
cStatus,
|
||||
rfc1123Time().c_str(),
|
||||
strMsg.size(),
|
||||
contentType.c_str(),
|
||||
FormatFullVersion().c_str(),
|
||||
strMsg.c_str());
|
||||
}
|
||||
|
||||
static bool HandleRESTRequest(const string& strURI, string& strReply, string& strContentType, int& nStatus)
|
||||
{
|
||||
// Parse: /rest/<resource>[/<param>][.format]
|
||||
vector<string> parts;
|
||||
string uri = strURI;
|
||||
// Remove query string if present
|
||||
size_t qpos = uri.find('?');
|
||||
if (qpos != string::npos) uri = uri.substr(0, qpos);
|
||||
|
||||
boost::split(parts, uri, boost::is_any_of("/"));
|
||||
// parts[0]="" parts[1]="rest" parts[2]="resource" parts[3]="param.format"
|
||||
if (parts.size() < 3) {
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Not found\"}";
|
||||
return true;
|
||||
}
|
||||
|
||||
string resource = parts[2];
|
||||
|
||||
// Get param and format from last path component
|
||||
string lastPart = parts.size() > 3 ? parts[parts.size()-1] : "";
|
||||
string param, format = "json";
|
||||
size_t dotPos = lastPart.rfind('.');
|
||||
if (dotPos != string::npos) {
|
||||
param = lastPart.substr(0, dotPos);
|
||||
format = lastPart.substr(dotPos+1);
|
||||
} else {
|
||||
param = lastPart;
|
||||
}
|
||||
|
||||
strContentType = (format == "hex") ? "text/plain" : "application/json";
|
||||
nStatus = HTTP_OK;
|
||||
|
||||
try {
|
||||
LOCK(cs_main);
|
||||
|
||||
if (resource == "chaininfo") {
|
||||
Object obj, diff;
|
||||
obj.push_back(Pair("chain", fTestNet ? string("test") : string("main")));
|
||||
obj.push_back(Pair("blocks", (int)nBestHeight));
|
||||
obj.push_back(Pair("bestblockhash", hashBestChain.GetHex()));
|
||||
diff.push_back(Pair("proof-of-work", GetDifficulty()));
|
||||
diff.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true))));
|
||||
obj.push_back(Pair("difficulty", diff));
|
||||
obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply)));
|
||||
strReply = write_string(Value(obj), false) + "\n";
|
||||
}
|
||||
else if (resource == "block" && !param.empty()) {
|
||||
uint256 hash(param);
|
||||
if (mapBlockIndex.count(hash) == 0) {
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Block not found\"}";
|
||||
return true;
|
||||
}
|
||||
CBlock block;
|
||||
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
||||
block.ReadFromDisk(pblockindex, true);
|
||||
|
||||
if (format == "hex") {
|
||||
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock << block;
|
||||
strReply = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
|
||||
} else {
|
||||
Object obj = blockToJSON(block, pblockindex, false);
|
||||
strReply = write_string(Value(obj), false) + "\n";
|
||||
}
|
||||
}
|
||||
else if (resource == "blockheader" && !param.empty()) {
|
||||
uint256 hash(param);
|
||||
if (mapBlockIndex.count(hash) == 0) {
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Block not found\"}";
|
||||
return true;
|
||||
}
|
||||
CBlockIndex* pblockindex = mapBlockIndex[hash];
|
||||
Object result;
|
||||
result.push_back(Pair("hash", pblockindex->GetBlockHash().GetHex()));
|
||||
result.push_back(Pair("confirmations", pindexBest->nHeight - pblockindex->nHeight + 1));
|
||||
result.push_back(Pair("height", pblockindex->nHeight));
|
||||
result.push_back(Pair("version", pblockindex->nVersion));
|
||||
result.push_back(Pair("merkleroot", pblockindex->hashMerkleRoot.GetHex()));
|
||||
result.push_back(Pair("time", (boost::int64_t)pblockindex->GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (boost::uint64_t)pblockindex->nNonce));
|
||||
result.push_back(Pair("bits", HexBits(pblockindex->nBits)));
|
||||
result.push_back(Pair("difficulty", GetDifficulty(pblockindex)));
|
||||
result.push_back(Pair("flags", strprintf("%s%s",
|
||||
pblockindex->IsProofOfStake() ? "proof-of-stake" : "proof-of-work",
|
||||
pblockindex->GeneratedStakeModifier() ? " stake-modifier" : "")));
|
||||
if (pblockindex->pprev)
|
||||
result.push_back(Pair("previousblockhash", pblockindex->pprev->GetBlockHash().GetHex()));
|
||||
if (pblockindex->pnext)
|
||||
result.push_back(Pair("nextblockhash", pblockindex->pnext->GetBlockHash().GetHex()));
|
||||
strReply = write_string(Value(result), false) + "\n";
|
||||
}
|
||||
else if (resource == "tx" && !param.empty()) {
|
||||
uint256 hash(param);
|
||||
CTransaction tx;
|
||||
uint256 hashBlock = 0;
|
||||
if (!GetTransaction(hash, tx, hashBlock))
|
||||
{
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Transaction not found\"}";
|
||||
return true;
|
||||
}
|
||||
if (format == "hex") {
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << tx;
|
||||
strReply = HexStr(ssTx.begin(), ssTx.end()) + "\n";
|
||||
} else {
|
||||
Object obj;
|
||||
obj.push_back(Pair("txid", tx.GetHash().GetHex()));
|
||||
TxToJSON(tx, hashBlock, obj);
|
||||
strReply = write_string(Value(obj), false) + "\n";
|
||||
}
|
||||
}
|
||||
else if (resource == "blockhashbyheight" && !param.empty()) {
|
||||
int nHeight = atoi(param.c_str());
|
||||
if (nHeight < 0 || nHeight > nBestHeight) {
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Block height out of range\"}";
|
||||
return true;
|
||||
}
|
||||
CBlockIndex* pblockindex = FindBlockByHeight(nHeight);
|
||||
Object obj;
|
||||
obj.push_back(Pair("blockhash", pblockindex->phashBlock->GetHex()));
|
||||
strReply = write_string(Value(obj), false) + "\n";
|
||||
}
|
||||
else if (resource == "mempool") {
|
||||
vector<uint256> vtxid;
|
||||
mempool.queryHashes(vtxid);
|
||||
Array a;
|
||||
BOOST_FOREACH(const uint256& hash, vtxid)
|
||||
a.push_back(hash.ToString());
|
||||
strReply = write_string(Value(a), false) + "\n";
|
||||
}
|
||||
else {
|
||||
nStatus = HTTP_NOT_FOUND;
|
||||
strReply = "{\"error\":\"Unknown REST endpoint\"}";
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
nStatus = HTTP_INTERNAL_SERVER_ERROR;
|
||||
strReply = strprintf("{\"error\":\"%s\"}", e.what());
|
||||
}
|
||||
catch (...) {
|
||||
nStatus = HTTP_INTERNAL_SERVER_ERROR;
|
||||
strReply = "{\"error\":\"Internal server error\"}";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Old HandleRESTRequest removed - now in rest.cpp
|
||||
|
||||
/**
|
||||
* Handle SSE (Server-Sent Events) stream connection.
|
||||
@@ -1294,20 +1117,32 @@ void ThreadRPCServer3(void* parg)
|
||||
|
||||
ReadHTTP(conn->stream(), mapHeaders, strRequest);
|
||||
|
||||
// Handle REST API requests (unauthenticated, read-only)
|
||||
// Handle REST API requests
|
||||
string strHTTPMethod = mapHeaders.count("_method") ? mapHeaders["_method"] : "POST";
|
||||
string strURI = mapHeaders.count("_uri") ? mapHeaders["_uri"] : "/";
|
||||
|
||||
if (strHTTPMethod == "GET" && strURI.substr(0, 6) == "/rest/")
|
||||
if (IsRESTPath(strURI) || (strHTTPMethod == "OPTIONS" && IsRESTPath(strURI)))
|
||||
{
|
||||
if (!GetBoolArg("-rest", false))
|
||||
{
|
||||
conn->stream() << HTTPReplyREST(HTTP_FORBIDDEN, "{\"error\":\"REST API not enabled. Start with -rest=1\"}") << std::flush;
|
||||
break;
|
||||
}
|
||||
|
||||
// Rate limit public (non-wallet) endpoints
|
||||
if (strURI.find("/rest/wallet/") == string::npos)
|
||||
{
|
||||
string strPeerIP = conn->peer_address_to_string();
|
||||
if (!CheckRESTRateLimit(strPeerIP))
|
||||
{
|
||||
conn->stream() << HTTPReplyREST(429, "{\"error\":\"Rate limit exceeded. Try again later.\"}") << std::flush;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string strReply, strContentType;
|
||||
int nRESTStatus;
|
||||
HandleRESTRequest(strURI, strReply, strContentType, nRESTStatus);
|
||||
HandleRESTRequest(strHTTPMethod, strURI, strRequest, mapHeaders, strReply, strContentType, nRESTStatus);
|
||||
conn->stream() << HTTPReplyREST(nRESTStatus, strReply, strContentType) << std::flush;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user