/ src / rest.cpp
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  }