rest.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto 2 // Copyright (c) 2009-present The Bitcoin Core developers 3 // Distributed under the MIT software license, see the accompanying 4 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 #include <rest.h> 7 8 #include <blockfilter.h> 9 #include <chain.h> 10 #include <chainparams.h> 11 #include <core_io.h> 12 #include <flatfile.h> 13 #include <httpserver.h> 14 #include <index/blockfilterindex.h> 15 #include <index/txindex.h> 16 #include <node/blockstorage.h> 17 #include <node/context.h> 18 #include <primitives/block.h> 19 #include <primitives/transaction.h> 20 #include <rpc/blockchain.h> 21 #include <rpc/mempool.h> 22 #include <rpc/protocol.h> 23 #include <rpc/server.h> 24 #include <rpc/server_util.h> 25 #include <streams.h> 26 #include <sync.h> 27 #include <txmempool.h> 28 #include <undo.h> 29 #include <util/any.h> 30 #include <util/check.h> 31 #include <util/strencodings.h> 32 #include <validation.h> 33 34 #include <any> 35 #include <vector> 36 37 #include <univalue.h> 38 39 using node::GetTransaction; 40 using node::NodeContext; 41 using util::SplitString; 42 43 static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once 44 static constexpr unsigned int MAX_REST_HEADERS_RESULTS = 2000; 45 46 static const struct { 47 RESTResponseFormat rf; 48 const char* name; 49 } rf_names[] = { 50 {RESTResponseFormat::UNDEF, ""}, 51 {RESTResponseFormat::BINARY, "bin"}, 52 {RESTResponseFormat::HEX, "hex"}, 53 {RESTResponseFormat::JSON, "json"}, 54 }; 55 56 struct CCoin { 57 uint32_t nHeight; 58 CTxOut out; 59 60 CCoin() : nHeight(0) {} 61 explicit CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {} 62 63 SERIALIZE_METHODS(CCoin, obj) 64 { 65 uint32_t nTxVerDummy = 0; 66 READWRITE(nTxVerDummy, obj.nHeight, obj.out); 67 } 68 }; 69 70 static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message) 71 { 72 req->WriteHeader("Content-Type", "text/plain"); 73 req->WriteReply(status, message + "\r\n"); 74 return false; 75 } 76 77 /** 78 * Get the node context. 79 * 80 * @param[in] req The HTTP request, whose status code will be set if node 81 * context is not found. 82 * @returns Pointer to the node context or nullptr if not found. 83 */ 84 static NodeContext* GetNodeContext(const std::any& context, HTTPRequest* req) 85 { 86 auto node_context = util::AnyPtr<NodeContext>(context); 87 if (!node_context) { 88 RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, STR_INTERNAL_BUG("Node context not found!")); 89 return nullptr; 90 } 91 return node_context; 92 } 93 94 /** 95 * Get the node context mempool. 96 * 97 * @param[in] req The HTTP request, whose status code will be set if node 98 * context mempool is not found. 99 * @returns Pointer to the mempool or nullptr if no mempool found. 100 */ 101 static CTxMemPool* GetMemPool(const std::any& context, HTTPRequest* req) 102 { 103 auto node_context = util::AnyPtr<NodeContext>(context); 104 if (!node_context || !node_context->mempool) { 105 RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found"); 106 return nullptr; 107 } 108 return node_context->mempool.get(); 109 } 110 111 /** 112 * Get the node context chainstatemanager. 113 * 114 * @param[in] req The HTTP request, whose status code will be set if node 115 * context chainstatemanager is not found. 116 * @returns Pointer to the chainstatemanager or nullptr if none found. 117 */ 118 static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req) 119 { 120 auto node_context = util::AnyPtr<NodeContext>(context); 121 if (!node_context || !node_context->chainman) { 122 RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, STR_INTERNAL_BUG("Chainman disabled or instance not found!")); 123 return nullptr; 124 } 125 return node_context->chainman.get(); 126 } 127 128 RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq) 129 { 130 // Remove query string (if any, separated with '?') as it should not interfere with 131 // parsing param and data format 132 param = strReq.substr(0, strReq.rfind('?')); 133 const std::string::size_type pos_format{param.rfind('.')}; 134 135 // No format string is found 136 if (pos_format == std::string::npos) { 137 return RESTResponseFormat::UNDEF; 138 } 139 140 // Match format string to available formats 141 const std::string suffix(param, pos_format + 1); 142 for (const auto& rf_name : rf_names) { 143 if (suffix == rf_name.name) { 144 param.erase(pos_format); 145 return rf_name.rf; 146 } 147 } 148 149 // If no suffix is found, return RESTResponseFormat::UNDEF and original string without query string 150 return RESTResponseFormat::UNDEF; 151 } 152 153 static std::string AvailableDataFormatsString() 154 { 155 std::string formats; 156 for (const auto& rf_name : rf_names) { 157 if (strlen(rf_name.name) > 0) { 158 formats.append("."); 159 formats.append(rf_name.name); 160 formats.append(", "); 161 } 162 } 163 164 if (formats.length() > 0) 165 return formats.substr(0, formats.length() - 2); 166 167 return formats; 168 } 169 170 static bool CheckWarmup(HTTPRequest* req) 171 { 172 std::string statusmessage; 173 if (RPCIsInWarmup(&statusmessage)) 174 return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); 175 return true; 176 } 177 178 static bool rest_headers(const std::any& context, 179 HTTPRequest* req, 180 const std::string& uri_part) 181 { 182 if (!CheckWarmup(req)) 183 return false; 184 std::string param; 185 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 186 std::vector<std::string> path = SplitString(param, '/'); 187 188 std::string raw_count; 189 std::string hashStr; 190 if (path.size() == 2) { 191 // deprecated path: /rest/headers/<count>/<hash> 192 hashStr = path[1]; 193 raw_count = path[0]; 194 } else if (path.size() == 1) { 195 // new path with query parameter: /rest/headers/<hash>?count=<count> 196 hashStr = path[0]; 197 try { 198 raw_count = req->GetQueryParameter("count").value_or("5"); 199 } catch (const std::runtime_error& e) { 200 return RESTERR(req, HTTP_BAD_REQUEST, e.what()); 201 } 202 } else { 203 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>"); 204 } 205 206 const auto parsed_count{ToIntegral<size_t>(raw_count)}; 207 if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { 208 return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); 209 } 210 211 auto hash{uint256::FromHex(hashStr)}; 212 if (!hash) { 213 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); 214 } 215 216 const CBlockIndex* tip = nullptr; 217 std::vector<const CBlockIndex*> headers; 218 headers.reserve(*parsed_count); 219 ChainstateManager* maybe_chainman = GetChainman(context, req); 220 if (!maybe_chainman) return false; 221 ChainstateManager& chainman = *maybe_chainman; 222 { 223 LOCK(cs_main); 224 CChain& active_chain = chainman.ActiveChain(); 225 tip = active_chain.Tip(); 226 const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*hash)}; 227 while (pindex != nullptr && active_chain.Contains(pindex)) { 228 headers.push_back(pindex); 229 if (headers.size() == *parsed_count) { 230 break; 231 } 232 pindex = active_chain.Next(pindex); 233 } 234 } 235 236 switch (rf) { 237 case RESTResponseFormat::BINARY: { 238 DataStream ssHeader{}; 239 for (const CBlockIndex *pindex : headers) { 240 ssHeader << pindex->GetBlockHeader(); 241 } 242 243 req->WriteHeader("Content-Type", "application/octet-stream"); 244 req->WriteReply(HTTP_OK, ssHeader); 245 return true; 246 } 247 248 case RESTResponseFormat::HEX: { 249 DataStream ssHeader{}; 250 for (const CBlockIndex *pindex : headers) { 251 ssHeader << pindex->GetBlockHeader(); 252 } 253 254 std::string strHex = HexStr(ssHeader) + "\n"; 255 req->WriteHeader("Content-Type", "text/plain"); 256 req->WriteReply(HTTP_OK, strHex); 257 return true; 258 } 259 case RESTResponseFormat::JSON: { 260 UniValue jsonHeaders(UniValue::VARR); 261 for (const CBlockIndex *pindex : headers) { 262 jsonHeaders.push_back(blockheaderToJSON(*tip, *pindex, chainman.GetConsensus().powLimit)); 263 } 264 std::string strJSON = jsonHeaders.write() + "\n"; 265 req->WriteHeader("Content-Type", "application/json"); 266 req->WriteReply(HTTP_OK, strJSON); 267 return true; 268 } 269 default: { 270 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 271 } 272 } 273 } 274 275 /** 276 * Serialize spent outputs as a list of per-transaction CTxOut lists using binary format. 277 */ 278 static void SerializeBlockUndo(DataStream& stream, const CBlockUndo& block_undo) 279 { 280 WriteCompactSize(stream, block_undo.vtxundo.size() + 1); 281 WriteCompactSize(stream, 0); // block_undo.vtxundo doesn't contain coinbase tx 282 for (const CTxUndo& tx_undo : block_undo.vtxundo) { 283 WriteCompactSize(stream, tx_undo.vprevout.size()); 284 for (const Coin& coin : tx_undo.vprevout) { 285 coin.out.Serialize(stream); 286 } 287 } 288 } 289 290 /** 291 * Serialize spent outputs as a list of per-transaction CTxOut lists using JSON format. 292 */ 293 static void BlockUndoToJSON(const CBlockUndo& block_undo, UniValue& result) 294 { 295 result.push_back({UniValue::VARR}); // block_undo.vtxundo doesn't contain coinbase tx 296 for (const CTxUndo& tx_undo : block_undo.vtxundo) { 297 UniValue tx_prevouts(UniValue::VARR); 298 for (const Coin& coin : tx_undo.vprevout) { 299 UniValue prevout(UniValue::VOBJ); 300 prevout.pushKV("value", ValueFromAmount(coin.out.nValue)); 301 302 UniValue script_pub_key(UniValue::VOBJ); 303 ScriptToUniv(coin.out.scriptPubKey, /*out=*/script_pub_key, /*include_hex=*/true, /*include_address=*/true); 304 prevout.pushKV("scriptPubKey", std::move(script_pub_key)); 305 306 tx_prevouts.push_back(std::move(prevout)); 307 } 308 result.push_back(std::move(tx_prevouts)); 309 } 310 } 311 312 static bool rest_spent_txouts(const std::any& context, HTTPRequest* req, const std::string& uri_part) 313 { 314 if (!CheckWarmup(req)) { 315 return false; 316 } 317 std::string param; 318 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 319 std::vector<std::string> path = SplitString(param, '/'); 320 321 std::string hashStr; 322 if (path.size() == 1) { 323 // path with query parameter: /rest/spenttxouts/<hash> 324 hashStr = path[0]; 325 } else { 326 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/spenttxouts/<hash>.<ext>"); 327 } 328 329 auto hash{uint256::FromHex(hashStr)}; 330 if (!hash) { 331 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); 332 } 333 334 ChainstateManager* chainman = GetChainman(context, req); 335 if (!chainman) { 336 return false; 337 } 338 339 const CBlockIndex* pblockindex = WITH_LOCK(cs_main, return chainman->m_blockman.LookupBlockIndex(*hash)); 340 if (!pblockindex) { 341 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); 342 } 343 344 CBlockUndo block_undo; 345 if (pblockindex->nHeight > 0 && !chainman->m_blockman.ReadBlockUndo(block_undo, *pblockindex)) { 346 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " undo not available"); 347 } 348 349 switch (rf) { 350 case RESTResponseFormat::BINARY: { 351 DataStream ssSpentResponse{}; 352 SerializeBlockUndo(ssSpentResponse, block_undo); 353 req->WriteHeader("Content-Type", "application/octet-stream"); 354 req->WriteReply(HTTP_OK, ssSpentResponse); 355 return true; 356 } 357 358 case RESTResponseFormat::HEX: { 359 DataStream ssSpentResponse{}; 360 SerializeBlockUndo(ssSpentResponse, block_undo); 361 const std::string strHex{HexStr(ssSpentResponse) + "\n"}; 362 req->WriteHeader("Content-Type", "text/plain"); 363 req->WriteReply(HTTP_OK, strHex); 364 return true; 365 } 366 367 case RESTResponseFormat::JSON: { 368 UniValue result(UniValue::VARR); 369 BlockUndoToJSON(block_undo, result); 370 std::string strJSON = result.write() + "\n"; 371 req->WriteHeader("Content-Type", "application/json"); 372 req->WriteReply(HTTP_OK, strJSON); 373 return true; 374 } 375 376 default: { 377 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 378 } 379 } 380 } 381 382 /** 383 * This handler is used by multiple HTTP endpoints: 384 * - `/block/` via `rest_block_extended()` 385 * - `/block/notxdetails/` via `rest_block_notxdetails()` 386 * - `/blockpart/` via `rest_block_part()` (doesn't support JSON response, so `tx_verbosity` is unset) 387 */ 388 static bool rest_block(const std::any& context, 389 HTTPRequest* req, 390 const std::string& uri_part, 391 std::optional<TxVerbosity> tx_verbosity, 392 std::optional<std::pair<size_t, size_t>> block_part = std::nullopt) 393 { 394 if (!CheckWarmup(req)) 395 return false; 396 std::string hashStr; 397 const RESTResponseFormat rf = ParseDataFormat(hashStr, uri_part); 398 399 auto hash{uint256::FromHex(hashStr)}; 400 if (!hash) { 401 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); 402 } 403 404 FlatFilePos pos{}; 405 const CBlockIndex* pblockindex = nullptr; 406 const CBlockIndex* tip = nullptr; 407 ChainstateManager* maybe_chainman = GetChainman(context, req); 408 if (!maybe_chainman) return false; 409 ChainstateManager& chainman = *maybe_chainman; 410 { 411 LOCK(cs_main); 412 tip = chainman.ActiveChain().Tip(); 413 pblockindex = chainman.m_blockman.LookupBlockIndex(*hash); 414 if (!pblockindex) { 415 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); 416 } 417 if (!(pblockindex->nStatus & BLOCK_HAVE_DATA)) { 418 if (chainman.m_blockman.IsBlockPruned(*pblockindex)) { 419 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); 420 } 421 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (not fully downloaded)"); 422 } 423 pos = pblockindex->GetBlockPos(); 424 } 425 426 const auto block_data{chainman.m_blockman.ReadRawBlock(pos, block_part)}; 427 if (!block_data) { 428 switch (block_data.error()) { 429 case node::ReadRawError::IO: return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "I/O error reading " + hashStr); 430 case node::ReadRawError::BadPartRange: 431 assert(block_part); 432 return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Bad block part offset/size %d/%d for %s", block_part->first, block_part->second, hashStr)); 433 } // no default case, so the compiler can warn about missing cases 434 assert(false); 435 } 436 437 switch (rf) { 438 case RESTResponseFormat::BINARY: { 439 req->WriteHeader("Content-Type", "application/octet-stream"); 440 req->WriteReply(HTTP_OK, *block_data); 441 return true; 442 } 443 444 case RESTResponseFormat::HEX: { 445 const std::string strHex{HexStr(*block_data) + "\n"}; 446 req->WriteHeader("Content-Type", "text/plain"); 447 req->WriteReply(HTTP_OK, strHex); 448 return true; 449 } 450 451 case RESTResponseFormat::JSON: { 452 if (tx_verbosity) { 453 CBlock block{}; 454 SpanReader{*block_data} >> TX_WITH_WITNESS(block); 455 UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, *tx_verbosity, chainman.GetConsensus().powLimit); 456 std::string strJSON = objBlock.write() + "\n"; 457 req->WriteHeader("Content-Type", "application/json"); 458 req->WriteReply(HTTP_OK, strJSON); 459 return true; 460 } 461 return RESTERR(req, HTTP_BAD_REQUEST, "JSON output is not supported for this request type"); 462 } 463 464 default: { 465 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 466 } 467 } 468 } 469 470 static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& uri_part) 471 { 472 return rest_block(context, req, uri_part, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); 473 } 474 475 static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& uri_part) 476 { 477 return rest_block(context, req, uri_part, TxVerbosity::SHOW_TXID); 478 } 479 480 static bool rest_block_part(const std::any& context, HTTPRequest* req, const std::string& uri_part) 481 { 482 try { 483 if (const auto opt_offset{ToIntegral<size_t>(req->GetQueryParameter("offset").value_or(""))}) { 484 if (const auto opt_size{ToIntegral<size_t>(req->GetQueryParameter("size").value_or(""))}) { 485 return rest_block(context, req, uri_part, 486 /*tx_verbosity=*/std::nullopt, 487 /*block_part=*/{{*opt_offset, *opt_size}}); 488 } else { 489 return RESTERR(req, HTTP_BAD_REQUEST, "Block part size missing or invalid"); 490 } 491 } else { 492 return RESTERR(req, HTTP_BAD_REQUEST, "Block part offset missing or invalid"); 493 } 494 } catch (const std::runtime_error& e) { 495 return RESTERR(req, HTTP_BAD_REQUEST, e.what()); 496 } 497 } 498 499 static bool rest_filter_header(const std::any& context, HTTPRequest* req, const std::string& uri_part) 500 { 501 if (!CheckWarmup(req)) return false; 502 503 std::string param; 504 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 505 506 std::vector<std::string> uri_parts = SplitString(param, '/'); 507 std::string raw_count; 508 std::string raw_blockhash; 509 if (uri_parts.size() == 3) { 510 // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash> 511 raw_blockhash = uri_parts[2]; 512 raw_count = uri_parts[1]; 513 } else if (uri_parts.size() == 2) { 514 // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count> 515 raw_blockhash = uri_parts[1]; 516 try { 517 raw_count = req->GetQueryParameter("count").value_or("5"); 518 } catch (const std::runtime_error& e) { 519 return RESTERR(req, HTTP_BAD_REQUEST, e.what()); 520 } 521 } else { 522 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>"); 523 } 524 525 const auto parsed_count{ToIntegral<size_t>(raw_count)}; 526 if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { 527 return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); 528 } 529 530 auto block_hash{uint256::FromHex(raw_blockhash)}; 531 if (!block_hash) { 532 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash); 533 } 534 535 BlockFilterType filtertype; 536 if (!BlockFilterTypeByName(uri_parts[0], filtertype)) { 537 return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]); 538 } 539 540 BlockFilterIndex* index = GetBlockFilterIndex(filtertype); 541 if (!index) { 542 return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]); 543 } 544 545 std::vector<const CBlockIndex*> headers; 546 headers.reserve(*parsed_count); 547 { 548 ChainstateManager* maybe_chainman = GetChainman(context, req); 549 if (!maybe_chainman) return false; 550 ChainstateManager& chainman = *maybe_chainman; 551 LOCK(cs_main); 552 CChain& active_chain = chainman.ActiveChain(); 553 const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*block_hash)}; 554 while (pindex != nullptr && active_chain.Contains(pindex)) { 555 headers.push_back(pindex); 556 if (headers.size() == *parsed_count) 557 break; 558 pindex = active_chain.Next(pindex); 559 } 560 } 561 562 bool index_ready = index->BlockUntilSyncedToCurrentChain(); 563 564 std::vector<uint256> filter_headers; 565 filter_headers.reserve(*parsed_count); 566 for (const CBlockIndex* pindex : headers) { 567 uint256 filter_header; 568 if (!index->LookupFilterHeader(pindex, filter_header)) { 569 std::string errmsg = "Filter not found."; 570 571 if (!index_ready) { 572 errmsg += " Block filters are still in the process of being indexed."; 573 } else { 574 errmsg += " This error is unexpected and indicates index corruption."; 575 } 576 577 return RESTERR(req, HTTP_NOT_FOUND, errmsg); 578 } 579 filter_headers.push_back(filter_header); 580 } 581 582 switch (rf) { 583 case RESTResponseFormat::BINARY: { 584 DataStream ssHeader{}; 585 for (const uint256& header : filter_headers) { 586 ssHeader << header; 587 } 588 589 req->WriteHeader("Content-Type", "application/octet-stream"); 590 req->WriteReply(HTTP_OK, ssHeader); 591 return true; 592 } 593 case RESTResponseFormat::HEX: { 594 DataStream ssHeader{}; 595 for (const uint256& header : filter_headers) { 596 ssHeader << header; 597 } 598 599 std::string strHex = HexStr(ssHeader) + "\n"; 600 req->WriteHeader("Content-Type", "text/plain"); 601 req->WriteReply(HTTP_OK, strHex); 602 return true; 603 } 604 case RESTResponseFormat::JSON: { 605 UniValue jsonHeaders(UniValue::VARR); 606 for (const uint256& header : filter_headers) { 607 jsonHeaders.push_back(header.GetHex()); 608 } 609 610 std::string strJSON = jsonHeaders.write() + "\n"; 611 req->WriteHeader("Content-Type", "application/json"); 612 req->WriteReply(HTTP_OK, strJSON); 613 return true; 614 } 615 default: { 616 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 617 } 618 } 619 } 620 621 static bool rest_block_filter(const std::any& context, HTTPRequest* req, const std::string& uri_part) 622 { 623 if (!CheckWarmup(req)) return false; 624 625 std::string param; 626 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 627 628 // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash 629 std::vector<std::string> uri_parts = SplitString(param, '/'); 630 if (uri_parts.size() != 2) { 631 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>"); 632 } 633 634 auto block_hash{uint256::FromHex(uri_parts[1])}; 635 if (!block_hash) { 636 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]); 637 } 638 639 BlockFilterType filtertype; 640 if (!BlockFilterTypeByName(uri_parts[0], filtertype)) { 641 return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]); 642 } 643 644 BlockFilterIndex* index = GetBlockFilterIndex(filtertype); 645 if (!index) { 646 return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]); 647 } 648 649 const CBlockIndex* block_index; 650 bool block_was_connected; 651 { 652 ChainstateManager* maybe_chainman = GetChainman(context, req); 653 if (!maybe_chainman) return false; 654 ChainstateManager& chainman = *maybe_chainman; 655 LOCK(cs_main); 656 block_index = chainman.m_blockman.LookupBlockIndex(*block_hash); 657 if (!block_index) { 658 return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found"); 659 } 660 block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS); 661 } 662 663 bool index_ready = index->BlockUntilSyncedToCurrentChain(); 664 665 BlockFilter filter; 666 if (!index->LookupFilter(block_index, filter)) { 667 std::string errmsg = "Filter not found."; 668 669 if (!block_was_connected) { 670 errmsg += " Block was not connected to active chain."; 671 } else if (!index_ready) { 672 errmsg += " Block filters are still in the process of being indexed."; 673 } else { 674 errmsg += " This error is unexpected and indicates index corruption."; 675 } 676 677 return RESTERR(req, HTTP_NOT_FOUND, errmsg); 678 } 679 680 switch (rf) { 681 case RESTResponseFormat::BINARY: { 682 DataStream ssResp{}; 683 ssResp << filter; 684 685 req->WriteHeader("Content-Type", "application/octet-stream"); 686 req->WriteReply(HTTP_OK, ssResp); 687 return true; 688 } 689 case RESTResponseFormat::HEX: { 690 DataStream ssResp{}; 691 ssResp << filter; 692 693 std::string strHex = HexStr(ssResp) + "\n"; 694 req->WriteHeader("Content-Type", "text/plain"); 695 req->WriteReply(HTTP_OK, strHex); 696 return true; 697 } 698 case RESTResponseFormat::JSON: { 699 UniValue ret(UniValue::VOBJ); 700 ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); 701 std::string strJSON = ret.write() + "\n"; 702 req->WriteHeader("Content-Type", "application/json"); 703 req->WriteReply(HTTP_OK, strJSON); 704 return true; 705 } 706 default: { 707 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 708 } 709 } 710 } 711 712 // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp 713 RPCHelpMan getblockchaininfo(); 714 715 static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std::string& uri_part) 716 { 717 if (!CheckWarmup(req)) 718 return false; 719 std::string param; 720 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 721 722 switch (rf) { 723 case RESTResponseFormat::JSON: { 724 JSONRPCRequest jsonRequest; 725 jsonRequest.context = context; 726 jsonRequest.params = UniValue(UniValue::VARR); 727 UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest); 728 std::string strJSON = chainInfoObject.write() + "\n"; 729 req->WriteHeader("Content-Type", "application/json"); 730 req->WriteReply(HTTP_OK, strJSON); 731 return true; 732 } 733 default: { 734 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); 735 } 736 } 737 } 738 739 740 RPCHelpMan getdeploymentinfo(); 741 742 static bool rest_deploymentinfo(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) 743 { 744 if (!CheckWarmup(req)) return false; 745 746 std::string hash_str; 747 const RESTResponseFormat rf = ParseDataFormat(hash_str, str_uri_part); 748 749 switch (rf) { 750 case RESTResponseFormat::JSON: { 751 JSONRPCRequest jsonRequest; 752 jsonRequest.context = context; 753 jsonRequest.params = UniValue(UniValue::VARR); 754 755 if (!hash_str.empty()) { 756 auto hash{uint256::FromHex(hash_str)}; 757 if (!hash) { 758 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str); 759 } 760 761 const ChainstateManager* chainman = GetChainman(context, req); 762 if (!chainman) return false; 763 if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(*hash))) { 764 return RESTERR(req, HTTP_BAD_REQUEST, "Block not found"); 765 } 766 767 jsonRequest.params.push_back(hash_str); 768 } 769 770 req->WriteHeader("Content-Type", "application/json"); 771 req->WriteReply(HTTP_OK, getdeploymentinfo().HandleRequest(jsonRequest).write() + "\n"); 772 return true; 773 } 774 default: { 775 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); 776 } 777 } 778 779 } 780 781 static bool rest_mempool(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) 782 { 783 if (!CheckWarmup(req)) 784 return false; 785 786 std::string param; 787 const RESTResponseFormat rf = ParseDataFormat(param, str_uri_part); 788 if (param != "contents" && param != "info") { 789 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/mempool/<info|contents>.json"); 790 } 791 792 const CTxMemPool* mempool = GetMemPool(context, req); 793 if (!mempool) return false; 794 795 switch (rf) { 796 case RESTResponseFormat::JSON: { 797 std::string str_json; 798 if (param == "contents") { 799 std::string raw_verbose; 800 try { 801 raw_verbose = req->GetQueryParameter("verbose").value_or("true"); 802 } catch (const std::runtime_error& e) { 803 return RESTERR(req, HTTP_BAD_REQUEST, e.what()); 804 } 805 if (raw_verbose != "true" && raw_verbose != "false") { 806 return RESTERR(req, HTTP_BAD_REQUEST, "The \"verbose\" query parameter must be either \"true\" or \"false\"."); 807 } 808 std::string raw_mempool_sequence; 809 try { 810 raw_mempool_sequence = req->GetQueryParameter("mempool_sequence").value_or("false"); 811 } catch (const std::runtime_error& e) { 812 return RESTERR(req, HTTP_BAD_REQUEST, e.what()); 813 } 814 if (raw_mempool_sequence != "true" && raw_mempool_sequence != "false") { 815 return RESTERR(req, HTTP_BAD_REQUEST, "The \"mempool_sequence\" query parameter must be either \"true\" or \"false\"."); 816 } 817 const bool verbose{raw_verbose == "true"}; 818 const bool mempool_sequence{raw_mempool_sequence == "true"}; 819 if (verbose && mempool_sequence) { 820 return RESTERR(req, HTTP_BAD_REQUEST, "Verbose results cannot contain mempool sequence values. (hint: set \"verbose=false\")"); 821 } 822 str_json = MempoolToJSON(*mempool, verbose, mempool_sequence).write() + "\n"; 823 } else { 824 str_json = MempoolInfoToJSON(*mempool).write() + "\n"; 825 } 826 827 req->WriteHeader("Content-Type", "application/json"); 828 req->WriteReply(HTTP_OK, str_json); 829 return true; 830 } 831 default: { 832 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); 833 } 834 } 835 } 836 837 static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string& uri_part) 838 { 839 if (!CheckWarmup(req)) 840 return false; 841 std::string hashStr; 842 const RESTResponseFormat rf = ParseDataFormat(hashStr, uri_part); 843 844 auto hash{Txid::FromHex(hashStr)}; 845 if (!hash) { 846 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); 847 } 848 849 if (g_txindex) { 850 g_txindex->BlockUntilSyncedToCurrentChain(); 851 } 852 853 const NodeContext* const node = GetNodeContext(context, req); 854 if (!node) return false; 855 uint256 hashBlock = uint256(); 856 const CTransactionRef tx{GetTransaction(/*block_index=*/nullptr, node->mempool.get(), *hash, node->chainman->m_blockman, hashBlock)}; 857 if (!tx) { 858 return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); 859 } 860 861 switch (rf) { 862 case RESTResponseFormat::BINARY: { 863 DataStream ssTx; 864 ssTx << TX_WITH_WITNESS(tx); 865 866 req->WriteHeader("Content-Type", "application/octet-stream"); 867 req->WriteReply(HTTP_OK, ssTx); 868 return true; 869 } 870 871 case RESTResponseFormat::HEX: { 872 DataStream ssTx; 873 ssTx << TX_WITH_WITNESS(tx); 874 875 std::string strHex = HexStr(ssTx) + "\n"; 876 req->WriteHeader("Content-Type", "text/plain"); 877 req->WriteReply(HTTP_OK, strHex); 878 return true; 879 } 880 881 case RESTResponseFormat::JSON: { 882 UniValue objTx(UniValue::VOBJ); 883 TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/ objTx); 884 std::string strJSON = objTx.write() + "\n"; 885 req->WriteHeader("Content-Type", "application/json"); 886 req->WriteReply(HTTP_OK, strJSON); 887 return true; 888 } 889 890 default: { 891 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 892 } 893 } 894 } 895 896 static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::string& uri_part) 897 { 898 if (!CheckWarmup(req)) 899 return false; 900 std::string param; 901 const RESTResponseFormat rf = ParseDataFormat(param, uri_part); 902 903 std::vector<std::string> uriParts; 904 if (param.length() > 1) 905 { 906 std::string strUriParams = param.substr(1); 907 uriParts = SplitString(strUriParams, '/'); 908 } 909 910 // throw exception in case of an empty request 911 std::string strRequestMutable = req->ReadBody(); 912 if (strRequestMutable.length() == 0 && uriParts.size() == 0) 913 return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request"); 914 915 bool fInputParsed = false; 916 bool fCheckMemPool = false; 917 std::vector<COutPoint> vOutPoints; 918 919 // parse/deserialize input 920 // input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ... 921 922 if (uriParts.size() > 0) 923 { 924 //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...) 925 if (uriParts[0] == "checkmempool") fCheckMemPool = true; 926 927 for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++) 928 { 929 const auto txid_out{util::Split<std::string_view>(uriParts[i], '-')}; 930 if (txid_out.size() != 2) { 931 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error"); 932 } 933 auto txid{Txid::FromHex(txid_out.at(0))}; 934 auto output{ToIntegral<uint32_t>(txid_out.at(1))}; 935 936 if (!txid || !output) { 937 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error"); 938 } 939 940 vOutPoints.emplace_back(*txid, *output); 941 } 942 943 if (vOutPoints.size() > 0) 944 fInputParsed = true; 945 else 946 return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request"); 947 } 948 949 switch (rf) { 950 case RESTResponseFormat::HEX: { 951 // convert hex to bin, continue then with bin part 952 std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable); 953 strRequestMutable.assign(strRequestV.begin(), strRequestV.end()); 954 [[fallthrough]]; 955 } 956 957 case RESTResponseFormat::BINARY: { 958 try { 959 //deserialize only if user sent a request 960 if (strRequestMutable.size() > 0) 961 { 962 if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA 963 return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed"); 964 965 DataStream oss{}; 966 oss << strRequestMutable; 967 oss >> fCheckMemPool; 968 oss >> vOutPoints; 969 } 970 } catch (const std::ios_base::failure&) { 971 // abort in case of unreadable binary data 972 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error"); 973 } 974 break; 975 } 976 977 case RESTResponseFormat::JSON: { 978 if (!fInputParsed) 979 return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request"); 980 break; 981 } 982 default: { 983 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 984 } 985 } 986 987 // limit max outpoints 988 if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS) 989 return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size())); 990 991 // check spentness and form a bitmap (as well as a JSON capable human-readable string representation) 992 std::vector<unsigned char> bitmap; 993 std::vector<CCoin> outs; 994 std::string bitmapStringRepresentation; 995 std::vector<bool> hits; 996 bitmap.resize((vOutPoints.size() + 7) / 8); 997 ChainstateManager* maybe_chainman = GetChainman(context, req); 998 if (!maybe_chainman) return false; 999 ChainstateManager& chainman = *maybe_chainman; 1000 decltype(chainman.ActiveHeight()) active_height; 1001 uint256 active_hash; 1002 { 1003 auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) { 1004 for (const COutPoint& vOutPoint : vOutPoints) { 1005 auto coin = !mempool || !mempool->isSpent(vOutPoint) ? view.GetCoin(vOutPoint) : std::nullopt; 1006 hits.push_back(coin.has_value()); 1007 if (coin) outs.emplace_back(std::move(*coin)); 1008 } 1009 active_height = chainman.ActiveHeight(); 1010 active_hash = chainman.ActiveTip()->GetBlockHash(); 1011 }; 1012 1013 if (fCheckMemPool) { 1014 const CTxMemPool* mempool = GetMemPool(context, req); 1015 if (!mempool) return false; 1016 // use db+mempool as cache backend in case user likes to query mempool 1017 LOCK2(cs_main, mempool->cs); 1018 CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip(); 1019 CCoinsViewMemPool viewMempool(&viewChain, *mempool); 1020 process_utxos(viewMempool, mempool); 1021 } else { 1022 LOCK(cs_main); 1023 process_utxos(chainman.ActiveChainstate().CoinsTip(), nullptr); 1024 } 1025 1026 for (size_t i = 0; i < hits.size(); ++i) { 1027 const bool hit = hits[i]; 1028 bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output) 1029 bitmap[i / 8] |= ((uint8_t)hit) << (i % 8); 1030 } 1031 } 1032 1033 switch (rf) { 1034 case RESTResponseFormat::BINARY: { 1035 // serialize data 1036 // use exact same output as mentioned in Bip64 1037 DataStream ssGetUTXOResponse{}; 1038 ssGetUTXOResponse << active_height << active_hash << bitmap << outs; 1039 1040 req->WriteHeader("Content-Type", "application/octet-stream"); 1041 req->WriteReply(HTTP_OK, ssGetUTXOResponse); 1042 return true; 1043 } 1044 1045 case RESTResponseFormat::HEX: { 1046 DataStream ssGetUTXOResponse{}; 1047 ssGetUTXOResponse << active_height << active_hash << bitmap << outs; 1048 std::string strHex = HexStr(ssGetUTXOResponse) + "\n"; 1049 1050 req->WriteHeader("Content-Type", "text/plain"); 1051 req->WriteReply(HTTP_OK, strHex); 1052 return true; 1053 } 1054 1055 case RESTResponseFormat::JSON: { 1056 UniValue objGetUTXOResponse(UniValue::VOBJ); 1057 1058 // pack in some essentials 1059 // use more or less the same output as mentioned in Bip64 1060 objGetUTXOResponse.pushKV("chainHeight", active_height); 1061 objGetUTXOResponse.pushKV("chaintipHash", active_hash.GetHex()); 1062 objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation); 1063 1064 UniValue utxos(UniValue::VARR); 1065 for (const CCoin& coin : outs) { 1066 UniValue utxo(UniValue::VOBJ); 1067 utxo.pushKV("height", coin.nHeight); 1068 utxo.pushKV("value", ValueFromAmount(coin.out.nValue)); 1069 1070 // include the script in a json output 1071 UniValue o(UniValue::VOBJ); 1072 ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); 1073 utxo.pushKV("scriptPubKey", std::move(o)); 1074 utxos.push_back(std::move(utxo)); 1075 } 1076 objGetUTXOResponse.pushKV("utxos", std::move(utxos)); 1077 1078 // return json string 1079 std::string strJSON = objGetUTXOResponse.write() + "\n"; 1080 req->WriteHeader("Content-Type", "application/json"); 1081 req->WriteReply(HTTP_OK, strJSON); 1082 return true; 1083 } 1084 default: { 1085 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 1086 } 1087 } 1088 } 1089 1090 static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req, 1091 const std::string& str_uri_part) 1092 { 1093 if (!CheckWarmup(req)) return false; 1094 std::string height_str; 1095 const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part); 1096 1097 const auto blockheight{ToIntegral<int32_t>(height_str)}; 1098 if (!blockheight || *blockheight < 0) { 1099 return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str, SAFE_CHARS_URI)); 1100 } 1101 1102 CBlockIndex* pblockindex = nullptr; 1103 { 1104 ChainstateManager* maybe_chainman = GetChainman(context, req); 1105 if (!maybe_chainman) return false; 1106 ChainstateManager& chainman = *maybe_chainman; 1107 LOCK(cs_main); 1108 const CChain& active_chain = chainman.ActiveChain(); 1109 if (*blockheight > active_chain.Height()) { 1110 return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range"); 1111 } 1112 pblockindex = active_chain[*blockheight]; 1113 } 1114 switch (rf) { 1115 case RESTResponseFormat::BINARY: { 1116 DataStream ss_blockhash{}; 1117 ss_blockhash << pblockindex->GetBlockHash(); 1118 req->WriteHeader("Content-Type", "application/octet-stream"); 1119 req->WriteReply(HTTP_OK, ss_blockhash); 1120 return true; 1121 } 1122 case RESTResponseFormat::HEX: { 1123 req->WriteHeader("Content-Type", "text/plain"); 1124 req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n"); 1125 return true; 1126 } 1127 case RESTResponseFormat::JSON: { 1128 req->WriteHeader("Content-Type", "application/json"); 1129 UniValue resp = UniValue(UniValue::VOBJ); 1130 resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex()); 1131 req->WriteReply(HTTP_OK, resp.write() + "\n"); 1132 return true; 1133 } 1134 default: { 1135 return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); 1136 } 1137 } 1138 } 1139 1140 static const struct { 1141 const char* prefix; 1142 bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq); 1143 } uri_prefixes[] = { 1144 {"/rest/tx/", rest_tx}, 1145 {"/rest/block/notxdetails/", rest_block_notxdetails}, 1146 {"/rest/block/", rest_block_extended}, 1147 {"/rest/blockpart/", rest_block_part}, 1148 {"/rest/blockfilter/", rest_block_filter}, 1149 {"/rest/blockfilterheaders/", rest_filter_header}, 1150 {"/rest/chaininfo", rest_chaininfo}, 1151 {"/rest/mempool/", rest_mempool}, 1152 {"/rest/headers/", rest_headers}, 1153 {"/rest/getutxos", rest_getutxos}, 1154 {"/rest/deploymentinfo/", rest_deploymentinfo}, 1155 {"/rest/deploymentinfo", rest_deploymentinfo}, 1156 {"/rest/blockhashbyheight/", rest_blockhash_by_height}, 1157 {"/rest/spenttxouts/", rest_spent_txouts}, 1158 }; 1159 1160 void StartREST(const std::any& context) 1161 { 1162 for (const auto& up : uri_prefixes) { 1163 auto handler = [context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); }; 1164 RegisterHTTPHandler(up.prefix, false, handler); 1165 } 1166 } 1167 1168 void InterruptREST() 1169 { 1170 } 1171 1172 void StopREST() 1173 { 1174 for (const auto& up : uri_prefixes) { 1175 UnregisterHTTPHandler(up.prefix, false); 1176 } 1177 }