rest.cpp raw

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