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