LCOV - code coverage report
Current view: top level - src - rest.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 458 586 78.2 %
Date: 2026-06-25 07:23:43 Functions: 30 34 88.2 %

          Line data    Source code
       1             : // Copyright (c) 2009-2010 Satoshi Nakamoto
       2             : // Copyright (c) 2009-2021 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 <chainlock/chainlock.h>
      12             : #include <context.h>
      13             : #include <core_io.h>
      14             : #include <httpserver.h>
      15             : #include <index/blockfilterindex.h>
      16             : #include <index/txindex.h>
      17             : #include <instantsend/instantsend.h>
      18             : #include <llmq/context.h>
      19             : #include <node/blockstorage.h>
      20             : #include <node/context.h>
      21             : #include <primitives/block.h>
      22             : #include <primitives/transaction.h>
      23             : #include <rpc/blockchain.h>
      24             : #include <rpc/mempool.h>
      25             : #include <rpc/protocol.h>
      26             : #include <rpc/server.h>
      27             : #include <rpc/server_util.h>
      28             : #include <streams.h>
      29             : #include <sync.h>
      30             : #include <txmempool.h>
      31             : #include <util/check.h>
      32             : #include <util/strencodings.h>
      33             : #include <validation.h>
      34             : #include <version.h>
      35             : 
      36             : #include <string>
      37             : 
      38             : #include <univalue.h>
      39             : 
      40             : using node::GetTransaction;
      41             : using node::NodeContext;
      42             : using node::ReadBlockFromDisk;
      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          32 :     explicit CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
      63             : 
      64           0 :     SERIALIZE_METHODS(CCoin, obj)
      65             :     {
      66           0 :         uint32_t nTxVerDummy = 0;
      67           0 :         READWRITE(nTxVerDummy, obj.nHeight, obj.out);
      68           0 :     }
      69             : };
      70             : 
      71          36 : static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
      72             : {
      73          36 :     req->WriteHeader("Content-Type", "text/plain");
      74          36 :     req->WriteReply(status, message + "\r\n");
      75          36 :     return false;
      76           0 : }
      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          28 : static NodeContext* GetNodeContext(const CoreContext& context, HTTPRequest* req)
      86             : {
      87          28 :     auto* node_context = GetContext<NodeContext>(context);
      88          28 :     if (!node_context) {
      89           0 :         RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
      90           0 :                 strprintf("%s:%d (%s)\n"
      91             :                           "Internal bug detected: Node context not found!\n"
      92             :                           "You may report this issue here: %s\n",
      93           0 :                           __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
      94           0 :         return nullptr;
      95             :     }
      96          28 :     return node_context;
      97          28 : }
      98             : 
      99             : /**
     100             :  * Get the node context mempool.
     101             :  *
     102             :  * @param[in]  req The HTTP request, whose status code will be set if node
     103             :  *                 context mempool is not found.
     104             :  * @returns        Pointer to the mempool or nullptr if no mempool found.
     105             :  */
     106          14 : static CTxMemPool* GetMemPool(const CoreContext& context, HTTPRequest* req)
     107             : {
     108          14 :     auto* node_context = GetContext<NodeContext>(context);
     109          14 :     if (!node_context || !node_context->mempool) {
     110           0 :         RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found");
     111           0 :         return nullptr;
     112             :     }
     113          14 :     return node_context->mempool.get();
     114          14 : }
     115             : 
     116             : /**
     117             :  * Get the node context chainstatemanager.
     118             :  *
     119             :  * @param[in]  req The HTTP request, whose status code will be set if node
     120             :  *                 context chainstatemanager is not found.
     121             :  * @returns        Pointer to the chainstatemanager or nullptr if none found.
     122             :  */
     123          68 : static ChainstateManager* GetChainman(const CoreContext& context, HTTPRequest* req)
     124             : {
     125          68 :     auto node_context = GetContext<NodeContext>(context);
     126          68 :     if (!node_context || !node_context->chainman) {
     127           0 :         RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
     128           0 :                 strprintf("%s:%d (%s)\n"
     129             :                           "Internal bug detected: Chainman disabled or instance not found!\n"
     130             :                           "You may report this issue here: %s\n",
     131           0 :                           __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
     132           0 :         return nullptr;
     133             :     }
     134          68 :     return node_context->chainman.get();
     135          68 : }
     136             : 
     137             : /**
     138             :  * Get the node context LLMQContext.
     139             :  *
     140             :  * @param[in]  req The HTTP request, whose status code will be set if node
     141             :  *                 context LLMQContext is not found.
     142             :  * @returns        Pointer to the LLMQContext or nullptr if none found.
     143             :  */
     144          12 : static LLMQContext* GetLLMQContext(const CoreContext& context, HTTPRequest* req)
     145             : {
     146          12 :     auto node_context = GetContext<NodeContext>(context);
     147          12 :     if (!node_context || !node_context->llmq_ctx) {
     148           0 :         RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
     149           0 :                 strprintf("%s:%d (%s)\n"
     150             :                           "Internal bug detected: LLMQ context not found!\n"
     151             :                           "You may report this issue here: %s\n",
     152           0 :                           __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
     153           0 :         return nullptr;
     154             :     }
     155          12 :     return node_context->llmq_ctx.get();
     156          12 : }
     157             : 
     158         118 : RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq)
     159             : {
     160             :     // Remove query string (if any, separated with '?') as it should not interfere with
     161             :     // parsing param and data format
     162         118 :     param = strReq.substr(0, strReq.rfind('?'));
     163         118 :     const std::string::size_type pos_format{param.rfind('.')};
     164             : 
     165             :     // No format string is found
     166         118 :     if (pos_format == std::string::npos) {
     167           2 :         return rf_names[0].rf;
     168             :     }
     169             : 
     170             :     // Match format string to available formats
     171         116 :     const std::string suffix(param, pos_format + 1);
     172         434 :     for (const auto& rf_name : rf_names) {
     173         433 :         if (suffix == rf_name.name) {
     174         115 :             param.erase(pos_format);
     175         115 :             return rf_name.rf;
     176             :         }
     177             :     }
     178             : 
     179             :     // If no suffix is found, return RESTResponseFormat::UNDEF and original string without query string
     180           1 :     return rf_names[0].rf;
     181         118 : }
     182             : 
     183           0 : static std::string AvailableDataFormatsString()
     184             : {
     185           0 :     std::string formats;
     186           0 :     for (const auto& rf_name : rf_names) {
     187           0 :         if (strlen(rf_name.name) > 0) {
     188           0 :             formats.append(".");
     189           0 :             formats.append(rf_name.name);
     190           0 :             formats.append(", ");
     191           0 :         }
     192             :     }
     193             : 
     194           0 :     if (formats.length() > 0)
     195           0 :         return formats.substr(0, formats.length() - 2);
     196             : 
     197           0 :     return formats;
     198           0 : }
     199             : 
     200         112 : static bool CheckWarmup(HTTPRequest* req)
     201             : {
     202         112 :     std::string statusmessage;
     203         112 :     if (RPCIsInWarmup(&statusmessage))
     204           0 :          return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
     205         112 :     return true;
     206         112 : }
     207             : 
     208          26 : static bool rest_headers(const CoreContext& context,
     209             :                          HTTPRequest* req,
     210             :                          const std::string& strURIPart)
     211             : {
     212          26 :     if (!CheckWarmup(req))
     213           0 :         return false;
     214          26 :     std::string param;
     215          26 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     216          26 :     std::vector<std::string> path = SplitString(param, '/');
     217             : 
     218          26 :     std::string raw_count;
     219          26 :     std::string hashStr;
     220          26 :     if (path.size() == 2) {
     221             :         // deprecated path: /rest/headers/<count>/<hash>
     222           2 :         hashStr = path[1];
     223           2 :         raw_count = path[0];
     224          26 :     } else if (path.size() == 1) {
     225             :         // new path with query parameter: /rest/headers/<hash>?count=<count>
     226          24 :         hashStr = path[0];
     227          24 :         raw_count = req->GetQueryParameter("count").value_or("5");
     228          24 :     } else {
     229           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>");
     230             :     }
     231             : 
     232          26 :     const auto parsed_count{ToIntegral<size_t>(raw_count)};
     233          26 :     if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
     234          10 :         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));
     235             :     }
     236             : 
     237          16 :     uint256 hash;
     238          16 :     if (!ParseHashStr(hashStr, hash))
     239           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     240             : 
     241          16 :     const CBlockIndex* tip = nullptr;
     242          16 :     std::vector<const CBlockIndex*> headers;
     243          16 :     headers.reserve(*parsed_count);
     244             :     {
     245          16 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     246          16 :         if (!maybe_chainman) return false;
     247          16 :         ChainstateManager& chainman = *maybe_chainman;
     248          16 :         LOCK(cs_main);
     249          16 :         CChain& active_chain = chainman.ActiveChain();
     250          16 :         tip = active_chain.Tip();
     251          16 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
     252          46 :         while (pindex != nullptr && active_chain.Contains(pindex)) {
     253          20 :             headers.push_back(pindex);
     254          20 :             if (headers.size() == *parsed_count) {
     255          12 :                 break;
     256             :             }
     257           8 :             pindex = active_chain.Next(pindex);
     258             :         }
     259          16 :     }
     260             : 
     261          16 :     switch (rf) {
     262             :     case RESTResponseFormat::BINARY: {
     263           2 :         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
     264           4 :         for (const CBlockIndex *pindex : headers) {
     265           2 :             ssHeader << pindex->GetBlockHeader();
     266             :         }
     267             : 
     268           2 :         std::string binaryHeader = ssHeader.str();
     269           2 :         req->WriteHeader("Content-Type", "application/octet-stream");
     270           2 :         req->WriteReply(HTTP_OK, binaryHeader);
     271           2 :         return true;
     272           2 :     }
     273             : 
     274             :     case RESTResponseFormat::HEX: {
     275           2 :         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
     276           4 :         for (const CBlockIndex *pindex : headers) {
     277           2 :             ssHeader << pindex->GetBlockHeader();
     278             :         }
     279             : 
     280           2 :         std::string strHex = HexStr(ssHeader) + "\n";
     281           2 :         req->WriteHeader("Content-Type", "text/plain");
     282           2 :         req->WriteReply(HTTP_OK, strHex);
     283           2 :         return true;
     284           2 :     }
     285             :     case RESTResponseFormat::JSON: {
     286          12 :         const NodeContext* const node = GetNodeContext(context, req);
     287          12 :         if (!node || !node->chainlocks) return false;
     288             : 
     289          12 :         UniValue jsonHeaders(UniValue::VARR);
     290          28 :         for (const CBlockIndex *pindex : headers) {
     291          16 :             jsonHeaders.push_back(blockheaderToJSON(tip, pindex, *node->chainlocks));
     292             :         }
     293          12 :         std::string strJSON = jsonHeaders.write() + "\n";
     294          12 :         req->WriteHeader("Content-Type", "application/json");
     295          12 :         req->WriteReply(HTTP_OK, strJSON);
     296          12 :         return true;
     297          12 :     }
     298             :     default: {
     299           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     300             :     }
     301             :     }
     302          26 : }
     303             : 
     304          14 : static bool rest_block(const CoreContext& context,
     305             :                        HTTPRequest* req,
     306             :                        const std::string& strURIPart,
     307             :                        TxVerbosity tx_verbosity)
     308             : {
     309          14 :     if (!CheckWarmup(req))
     310           0 :         return false;
     311          14 :     std::string hashStr;
     312          14 :     const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
     313             : 
     314          14 :     uint256 hash;
     315          14 :     if (!ParseHashStr(hashStr, hash))
     316           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     317             : 
     318          14 :     CBlock block;
     319          14 :     const CBlockIndex* pblockindex = nullptr;
     320          14 :     const CBlockIndex* tip = nullptr;
     321          14 :     ChainstateManager* maybe_chainman = GetChainman(context, req);
     322          14 :     if (!maybe_chainman) return false;
     323          14 :     ChainstateManager& chainman = *maybe_chainman;
     324             :     {
     325          14 :         LOCK(cs_main);
     326          14 :         tip = chainman.ActiveChain().Tip();
     327          14 :         pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
     328          14 :         if (!pblockindex) {
     329           2 :             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     330             :         }
     331             : 
     332          12 :         if (chainman.m_blockman.IsBlockPruned(pblockindex))
     333           0 :             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
     334          14 :     }
     335             : 
     336          12 :     if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
     337           0 :         return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     338             :     }
     339             : 
     340          12 :     switch (rf) {
     341             :     case RESTResponseFormat::BINARY: {
     342           2 :         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
     343           2 :         ssBlock << block;
     344           2 :         std::string binaryBlock = ssBlock.str();
     345           2 :         req->WriteHeader("Content-Type", "application/octet-stream");
     346           2 :         req->WriteReply(HTTP_OK, binaryBlock);
     347           2 :         return true;
     348           2 :     }
     349             : 
     350             :     case RESTResponseFormat::HEX: {
     351           2 :         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
     352           2 :         ssBlock << block;
     353           2 :         std::string strHex = HexStr(ssBlock) + "\n";
     354           2 :         req->WriteHeader("Content-Type", "text/plain");
     355           2 :         req->WriteReply(HTTP_OK, strHex);
     356           2 :         return true;
     357           2 :     }
     358             : 
     359             :     case RESTResponseFormat::JSON: {
     360           8 :         const NodeContext* const node = GetNodeContext(context, req);
     361           8 :         if (!node || !node->chainlocks) return false;
     362             : 
     363           8 :         const LLMQContext* llmq_ctx = GetLLMQContext(context, req);
     364           8 :         if (!llmq_ctx) return false;
     365             : 
     366           8 :         UniValue objBlock = blockToJSON(chainman.m_blockman, block, tip, pblockindex, *node->chainlocks, *llmq_ctx->isman, tx_verbosity);
     367           8 :         std::string strJSON = objBlock.write() + "\n";
     368           8 :         req->WriteHeader("Content-Type", "application/json");
     369           8 :         req->WriteReply(HTTP_OK, strJSON);
     370           8 :         return true;
     371           8 :     }
     372             : 
     373             :     default: {
     374           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     375             :     }
     376             :     }
     377          14 : }
     378             : 
     379          12 : static bool rest_block_extended(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     380             : {
     381          12 :     return rest_block(context, req, strURIPart, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
     382             : }
     383             : 
     384           2 : static bool rest_block_notxdetails(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     385             : {
     386           2 :     return rest_block(context, req, strURIPart, TxVerbosity::SHOW_TXID);
     387             : }
     388             : 
     389          10 : static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     390             : {
     391          10 :     if (!CheckWarmup(req)) return false;
     392             : 
     393          10 :     std::string param;
     394          10 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     395             : 
     396          10 :     std::vector<std::string> uri_parts = SplitString(param, '/');
     397          10 :     std::string raw_count;
     398          10 :     std::string raw_blockhash;
     399          10 :     if (uri_parts.size() == 3) {
     400             :         // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>
     401           2 :         raw_blockhash = uri_parts[2];
     402           2 :         raw_count = uri_parts[1];
     403          10 :     } else if (uri_parts.size() == 2) {
     404             :         // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count>
     405           8 :         raw_blockhash = uri_parts[1];
     406           8 :         raw_count = req->GetQueryParameter("count").value_or("5");
     407           8 :     } else {
     408           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>");
     409             :     }
     410             : 
     411          10 :     const auto parsed_count{ToIntegral<size_t>(raw_count)};
     412          10 :     if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
     413           0 :         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));
     414             :     }
     415             : 
     416          10 :     uint256 block_hash;
     417          10 :     if (!ParseHashStr(raw_blockhash, block_hash)) {
     418           2 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
     419             :     }
     420             : 
     421             :     BlockFilterType filtertype;
     422           8 :     if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
     423           2 :         return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
     424             :     }
     425             : 
     426           6 :     BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
     427           6 :     if (!index) {
     428           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
     429             :     }
     430             : 
     431           6 :     std::vector<const CBlockIndex*> headers;
     432           6 :     headers.reserve(*parsed_count);
     433             :     {
     434           6 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     435           6 :         if (!maybe_chainman) return false;
     436           6 :         ChainstateManager& chainman = *maybe_chainman;
     437           6 :         LOCK(cs_main);
     438           6 :         CChain& active_chain = chainman.ActiveChain();
     439           6 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash);
     440          30 :         while (pindex != nullptr && active_chain.Contains(pindex)) {
     441          14 :             headers.push_back(pindex);
     442          14 :             if (headers.size() == *parsed_count)
     443           4 :                 break;
     444          10 :             pindex = active_chain.Next(pindex);
     445             :         }
     446           6 :     }
     447             : 
     448           6 :     bool index_ready = index->BlockUntilSyncedToCurrentChain();
     449             : 
     450           6 :     std::vector<uint256> filter_headers;
     451           6 :     filter_headers.reserve(*parsed_count);
     452          20 :     for (const CBlockIndex* pindex : headers) {
     453          14 :         uint256 filter_header;
     454          14 :         if (!index->LookupFilterHeader(pindex, filter_header)) {
     455           0 :             std::string errmsg = "Filter not found.";
     456             : 
     457           0 :             if (!index_ready) {
     458           0 :                 errmsg += " Block filters are still in the process of being indexed.";
     459           0 :             } else {
     460           0 :                 errmsg += " This error is unexpected and indicates index corruption.";
     461             :             }
     462             : 
     463           0 :             return RESTERR(req, HTTP_NOT_FOUND, errmsg);
     464           0 :         }
     465          14 :         filter_headers.push_back(filter_header);
     466             :     }
     467             : 
     468           6 :     switch (rf) {
     469             :     case RESTResponseFormat::BINARY: {
     470           0 :         CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
     471           0 :         for (const uint256& header : filter_headers) {
     472           0 :             ssHeader << header;
     473             :         }
     474             : 
     475           0 :         std::string binaryHeader = ssHeader.str();
     476           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     477           0 :         req->WriteReply(HTTP_OK, binaryHeader);
     478           0 :         return true;
     479           0 :     }
     480             :     case RESTResponseFormat::HEX: {
     481           0 :         CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
     482           0 :         for (const uint256& header : filter_headers) {
     483           0 :             ssHeader << header;
     484             :         }
     485             : 
     486           0 :         std::string strHex = HexStr(ssHeader) + "\n";
     487           0 :         req->WriteHeader("Content-Type", "text/plain");
     488           0 :         req->WriteReply(HTTP_OK, strHex);
     489           0 :         return true;
     490           0 :     }
     491             :     case RESTResponseFormat::JSON: {
     492           6 :         UniValue jsonHeaders(UniValue::VARR);
     493          20 :         for (const uint256& header : filter_headers) {
     494          14 :             jsonHeaders.push_back(header.GetHex());
     495             :         }
     496             : 
     497           6 :         std::string strJSON = jsonHeaders.write() + "\n";
     498           6 :         req->WriteHeader("Content-Type", "application/json");
     499           6 :         req->WriteReply(HTTP_OK, strJSON);
     500           6 :         return true;
     501           6 :     }
     502             :     default: {
     503           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     504             :     }
     505             :     }
     506          10 : }
     507             : 
     508           2 : static bool rest_block_filter(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     509             : {
     510           2 :     if (!CheckWarmup(req)) return false;
     511             : 
     512           2 :     std::string param;
     513           2 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     514             : 
     515             :     // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash
     516           2 :     std::vector<std::string> uri_parts = SplitString(param, '/');
     517           2 :     if (uri_parts.size() != 2) {
     518           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>");
     519             :     }
     520             : 
     521           2 :     uint256 block_hash;
     522           2 :     if (!ParseHashStr(uri_parts[1], block_hash)) {
     523           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]);
     524             :     }
     525             : 
     526             :     BlockFilterType filtertype;
     527           2 :     if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
     528           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
     529             :     }
     530             : 
     531           2 :     BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
     532           2 :     if (!index) {
     533           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
     534             :     }
     535             : 
     536             :     const CBlockIndex* block_index;
     537             :     bool block_was_connected;
     538             :     {
     539           2 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     540           2 :         if (!maybe_chainman) return false;
     541           2 :         ChainstateManager& chainman = *maybe_chainman;
     542           2 :         LOCK(cs_main);
     543           2 :         block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
     544           2 :         if (!block_index) {
     545           0 :             return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found");
     546             :         }
     547           2 :         block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
     548           2 :     }
     549             : 
     550           2 :     bool index_ready = index->BlockUntilSyncedToCurrentChain();
     551             : 
     552           2 :     BlockFilter filter;
     553           2 :     if (!index->LookupFilter(block_index, filter)) {
     554           0 :         std::string errmsg = "Filter not found.";
     555             : 
     556           0 :         if (!block_was_connected) {
     557           0 :             errmsg += " Block was not connected to active chain.";
     558           0 :         } else if (!index_ready) {
     559           0 :             errmsg += " Block filters are still in the process of being indexed.";
     560           0 :         } else {
     561           0 :             errmsg += " This error is unexpected and indicates index corruption.";
     562             :         }
     563             : 
     564           0 :         return RESTERR(req, HTTP_NOT_FOUND, errmsg);
     565           0 :     }
     566             : 
     567           2 :     switch (rf) {
     568             :     case RESTResponseFormat::BINARY: {
     569           0 :         CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
     570           0 :         ssResp << filter;
     571             : 
     572           0 :         std::string binaryResp = ssResp.str();
     573           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     574           0 :         req->WriteReply(HTTP_OK, binaryResp);
     575           0 :         return true;
     576           0 :     }
     577             :     case RESTResponseFormat::HEX: {
     578           0 :         CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
     579           0 :         ssResp << filter;
     580             : 
     581           0 :         std::string strHex = HexStr(ssResp) + "\n";
     582           0 :         req->WriteHeader("Content-Type", "text/plain");
     583           0 :         req->WriteReply(HTTP_OK, strHex);
     584           0 :         return true;
     585           0 :     }
     586             :     case RESTResponseFormat::JSON: {
     587           2 :         UniValue ret(UniValue::VOBJ);
     588           2 :         ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
     589           2 :         std::string strJSON = ret.write() + "\n";
     590           2 :         req->WriteHeader("Content-Type", "application/json");
     591           2 :         req->WriteReply(HTTP_OK, strJSON);
     592           2 :         return true;
     593           2 :     }
     594             :     default: {
     595           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     596             :     }
     597             :     }
     598           2 : }
     599             : 
     600             : // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
     601             : RPCHelpMan getblockchaininfo();
     602             : 
     603           2 : static bool rest_chaininfo(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     604             : {
     605           2 :     if (!CheckWarmup(req))
     606           0 :         return false;
     607           2 :     std::string param;
     608           2 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     609             : 
     610           2 :     switch (rf) {
     611             :     case RESTResponseFormat::JSON: {
     612           2 :         JSONRPCRequest jsonRequest;
     613           2 :         jsonRequest.context = context;
     614           2 :         jsonRequest.params = UniValue(UniValue::VARR);
     615           2 :         UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest);
     616           2 :         std::string strJSON = chainInfoObject.write() + "\n";
     617           2 :         req->WriteHeader("Content-Type", "application/json");
     618           2 :         req->WriteReply(HTTP_OK, strJSON);
     619           2 :         return true;
     620           2 :     }
     621             :     default: {
     622           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
     623             :     }
     624             :     }
     625           2 : }
     626             : 
     627           4 : static bool rest_mempool(const CoreContext& context, HTTPRequest* req, const std::string& str_uri_part)
     628             : {
     629           4 :     if (!CheckWarmup(req))
     630           0 :         return false;
     631             : 
     632           4 :     std::string param;
     633           4 :     const RESTResponseFormat rf = ParseDataFormat(param, str_uri_part);
     634           4 :     if (param != "contents" && param != "info") {
     635           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/mempool/<info|contents>.json");
     636             :     }
     637             : 
     638           4 :     const CTxMemPool* mempool = GetMemPool(context, req);
     639           4 :     if (!mempool) return false;
     640             : 
     641           4 :     switch (rf) {
     642             :     case RESTResponseFormat::JSON: {
     643           4 :         const LLMQContext* llmq_ctx = GetLLMQContext(context, req);
     644           4 :         if (!llmq_ctx) return false;
     645             : 
     646           4 :         std::string str_json;
     647           4 :         if (param == "contents") {
     648           2 :             str_json = MempoolToJSON(*mempool, llmq_ctx->isman.get(), true).write() + "\n";
     649           2 :         } else {
     650           2 :             str_json = MempoolInfoToJSON(*mempool, *llmq_ctx->isman).write() + "\n";
     651             :         }
     652             : 
     653           4 :         req->WriteHeader("Content-Type", "application/json");
     654           4 :         req->WriteReply(HTTP_OK, str_json);
     655           4 :         return true;
     656           4 :     }
     657             :     default: {
     658           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
     659             :     }
     660             :     }
     661           4 : }
     662             : 
     663          10 : static bool rest_tx(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     664             : {
     665          10 :     if (!CheckWarmup(req))
     666           0 :         return false;
     667          10 :     std::string hashStr;
     668          10 :     const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
     669             : 
     670          10 :     uint256 hash;
     671          10 :     if (!ParseHashStr(hashStr, hash))
     672           2 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     673             : 
     674           8 :     if (g_txindex) {
     675           8 :         g_txindex->BlockUntilSyncedToCurrentChain();
     676           8 :     }
     677             : 
     678           8 :     const NodeContext* const node = GetNodeContext(context, req);
     679           8 :     if (!node) return false;
     680           8 :     uint256 hashBlock = uint256();
     681           8 :     const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
     682           8 :     if (!tx) {
     683           2 :         return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     684             :     }
     685             : 
     686           6 :     switch (rf) {
     687             :     case RESTResponseFormat::BINARY: {
     688           0 :         CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
     689           0 :         ssTx << tx;
     690             : 
     691           0 :         std::string binaryTx = ssTx.str();
     692           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     693           0 :         req->WriteReply(HTTP_OK, binaryTx);
     694           0 :         return true;
     695           0 :     }
     696             : 
     697             :     case RESTResponseFormat::HEX: {
     698           2 :         CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
     699           2 :         ssTx << tx;
     700             : 
     701           2 :         std::string strHex = HexStr(ssTx) + "\n";
     702           2 :         req->WriteHeader("Content-Type", "text/plain");
     703           2 :         req->WriteReply(HTTP_OK, strHex);
     704           2 :         return true;
     705           2 :     }
     706             : 
     707             :     case RESTResponseFormat::JSON: {
     708           4 :         UniValue objTx(UniValue::VOBJ);
     709           4 :         TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/objTx);
     710           4 :         std::string strJSON = objTx.write() + "\n";
     711           4 :         req->WriteHeader("Content-Type", "application/json");
     712           4 :         req->WriteReply(HTTP_OK, strJSON);
     713           4 :         return true;
     714           4 :     }
     715             : 
     716             :     default: {
     717           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     718             :     }
     719             :     }
     720          10 : }
     721             : 
     722          30 : static bool rest_getutxos(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     723             : {
     724          30 :     if (!CheckWarmup(req))
     725           0 :         return false;
     726          30 :     std::string param;
     727          30 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     728             : 
     729          30 :     std::vector<std::string> uriParts;
     730          30 :     if (param.length() > 1)
     731             :     {
     732          24 :         std::string strUriParams = param.substr(1);
     733          24 :         uriParts = SplitString(strUriParams, '/');
     734          24 :     }
     735             : 
     736             :     // throw exception in case of an empty request
     737          30 :     std::string strRequestMutable = req->ReadBody();
     738          30 :     if (strRequestMutable.length() == 0 && uriParts.size() == 0)
     739           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     740             : 
     741          30 :     bool fInputParsed = false;
     742          30 :     bool fCheckMemPool = false;
     743          30 :     std::vector<COutPoint> vOutPoints;
     744             : 
     745             :     // parse/deserialize input
     746             :     // input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
     747             : 
     748          30 :     if (uriParts.size() > 0)
     749             :     {
     750             :         //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
     751          24 :         if (uriParts[0] == "checkmempool") fCheckMemPool = true;
     752             : 
     753         114 :         for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
     754             :         {
     755          90 :             uint256 txid;
     756             :             int32_t nOutput;
     757          90 :             std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
     758          90 :             std::string strOutput = uriParts[i].substr(uriParts[i].find('-')+1);
     759             : 
     760          90 :             if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
     761           0 :                 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
     762             : 
     763          90 :             txid.SetHex(strTxid);
     764          90 :             vOutPoints.emplace_back(txid, (uint32_t)nOutput);
     765          90 :         }
     766             : 
     767          24 :         if (vOutPoints.size() > 0)
     768          22 :             fInputParsed = true;
     769             :         else
     770           2 :             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     771          22 :     }
     772             : 
     773          28 :     switch (rf) {
     774             :     case RESTResponseFormat::HEX: {
     775             :         // convert hex to bin, continue then with bin part
     776           0 :         std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
     777           0 :         strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
     778             :         [[fallthrough]];
     779           0 :     }
     780             : 
     781             :     case RESTResponseFormat::BINARY: {
     782             :         try {
     783             :             //deserialize only if user sent a request
     784           4 :             if (strRequestMutable.size() > 0)
     785             :             {
     786           4 :                 if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
     787           0 :                     return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");
     788             : 
     789           4 :                 CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
     790           4 :                 oss << strRequestMutable;
     791           4 :                 oss >> fCheckMemPool;
     792           4 :                 oss >> vOutPoints;
     793           4 :             }
     794           4 :         } catch (const std::ios_base::failure&) {
     795             :             // abort in case of unreadable binary data
     796           2 :             return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
     797           2 :         }
     798           2 :         break;
     799             :     }
     800             : 
     801             :     case RESTResponseFormat::JSON: {
     802          24 :         if (!fInputParsed)
     803           2 :             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     804          22 :         break;
     805             :     }
     806             :     default: {
     807           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     808             :     }
     809             :     }
     810             : 
     811             :     // limit max outpoints
     812          24 :     if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
     813           2 :         return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
     814             : 
     815             :     // check spentness and form a bitmap (as well as a JSON capable human-readable string representation)
     816          22 :     std::vector<unsigned char> bitmap;
     817          22 :     std::vector<CCoin> outs;
     818          22 :     std::string bitmapStringRepresentation;
     819          22 :     std::vector<bool> hits;
     820          22 :     bitmap.resize((vOutPoints.size() + 7) / 8);
     821          22 :     ChainstateManager* maybe_chainman = GetChainman(context, req);
     822          22 :     if (!maybe_chainman) return false;
     823          22 :     ChainstateManager& chainman = *maybe_chainman;
     824             :     {
     825          44 :         auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool* mempool) {
     826          74 :             for (const COutPoint& vOutPoint : vOutPoints) {
     827         124 :                 Coin coin;
     828         174 :                 bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin);
     829          88 :                 hits.push_back(hit);
     830          52 :                 if (hit) outs.emplace_back(std::move(coin));
     831         124 :             }
     832         122 :         };
     833             : 
     834          22 :         if (fCheckMemPool) {
     835          10 :             const CTxMemPool* mempool = GetMemPool(context, req);
     836          10 :             if (!mempool) return false;
     837             :             // use db+mempool as cache backend in case user likes to query mempool
     838          10 :             LOCK2(cs_main, mempool->cs);
     839          10 :             CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip();
     840          10 :             CCoinsViewMemPool viewMempool(&viewChain, *mempool);
     841          10 :             process_utxos(viewMempool, mempool);
     842          10 :         } else {
     843          12 :             LOCK(cs_main);
     844          12 :             process_utxos(chainman.ActiveChainstate().CoinsTip(), nullptr);
     845          12 :         }
     846             : 
     847          74 :         for (size_t i = 0; i < hits.size(); ++i) {
     848          52 :             const bool hit = hits[i];
     849          52 :             bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
     850          52 :             bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
     851          52 :         }
     852             :     }
     853             : 
     854          22 :     switch (rf) {
     855             :     case RESTResponseFormat::BINARY: {
     856             :         // serialize data
     857             :         // use exact same output as mentioned in Bip64
     858           2 :         CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
     859           2 :         ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
     860           2 :         std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
     861             : 
     862           2 :         req->WriteHeader("Content-Type", "application/octet-stream");
     863           2 :         req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
     864           2 :         return true;
     865           2 :     }
     866             : 
     867             :     case RESTResponseFormat::HEX: {
     868           0 :         CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
     869           0 :         ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
     870           0 :         std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
     871             : 
     872           0 :         req->WriteHeader("Content-Type", "text/plain");
     873           0 :         req->WriteReply(HTTP_OK, strHex);
     874           0 :         return true;
     875           0 :     }
     876             : 
     877             :     case RESTResponseFormat::JSON: {
     878          20 :         UniValue objGetUTXOResponse(UniValue::VOBJ);
     879             : 
     880             :         // pack in some essentials
     881             :         // use more or less the same output as mentioned in Bip64
     882          20 :         objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
     883          20 :         objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
     884          20 :         objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
     885             : 
     886          20 :         UniValue utxos(UniValue::VARR);
     887          36 :         for (const CCoin& coin : outs) {
     888          16 :             UniValue utxo(UniValue::VOBJ);
     889          16 :             utxo.pushKV("height", (int32_t)coin.nHeight);
     890          16 :             utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
     891             : 
     892             :             // include the script in a json output
     893          16 :             UniValue o(UniValue::VOBJ);
     894          16 :             ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
     895          16 :             utxo.pushKV("scriptPubKey", o);
     896          16 :             utxos.push_back(utxo);
     897          16 :         }
     898          20 :         objGetUTXOResponse.pushKV("utxos", utxos);
     899             : 
     900             :         // return json string
     901          20 :         std::string strJSON = objGetUTXOResponse.write() + "\n";
     902          20 :         req->WriteHeader("Content-Type", "application/json");
     903          20 :         req->WriteReply(HTTP_OK, strJSON);
     904          20 :         return true;
     905          20 :     }
     906             :     default: {
     907           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     908             :     }
     909             :     }
     910          32 : }
     911             : 
     912          14 : static bool rest_blockhash_by_height(const CoreContext& context, HTTPRequest* req,
     913             :                        const std::string& str_uri_part)
     914             : {
     915          14 :     if (!CheckWarmup(req)) return false;
     916          14 :     std::string height_str;
     917          14 :     const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part);
     918             : 
     919          14 :     int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
     920          14 :     if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
     921           6 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str));
     922             :     }
     923             : 
     924           8 :     CBlockIndex* pblockindex = nullptr;
     925             :     {
     926           8 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     927           8 :         if (!maybe_chainman) return false;
     928           8 :         ChainstateManager& chainman = *maybe_chainman;
     929           8 :         LOCK(cs_main);
     930           8 :         const CChain& active_chain = chainman.ActiveChain();
     931           8 :         if (blockheight > active_chain.Height()) {
     932           2 :             return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
     933             :         }
     934           6 :         pblockindex = active_chain[blockheight];
     935           8 :     }
     936           6 :     switch (rf) {
     937             :     case RESTResponseFormat::BINARY: {
     938           2 :         CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
     939           2 :         ss_blockhash << pblockindex->GetBlockHash();
     940           2 :         req->WriteHeader("Content-Type", "application/octet-stream");
     941           2 :         req->WriteReply(HTTP_OK, ss_blockhash.str());
     942           2 :         return true;
     943           2 :     }
     944             :     case RESTResponseFormat::HEX: {
     945           2 :         req->WriteHeader("Content-Type", "text/plain");
     946           2 :         req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
     947           2 :         return true;
     948             :     }
     949             :     case RESTResponseFormat::JSON: {
     950           2 :         req->WriteHeader("Content-Type", "application/json");
     951           2 :         UniValue resp = UniValue(UniValue::VOBJ);
     952           2 :         resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
     953           2 :         req->WriteReply(HTTP_OK, resp.write() + "\n");
     954           2 :         return true;
     955           2 :     }
     956             :     default: {
     957           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     958             :     }
     959             :     }
     960          14 : }
     961             : 
     962             : static const struct {
     963             :     const char* prefix;
     964             :     bool (*handler)(const CoreContext& context, HTTPRequest* req, const std::string& strReq);
     965             : } uri_prefixes[] = {
     966             :       {"/rest/tx/", rest_tx},
     967             :       {"/rest/block/notxdetails/", rest_block_notxdetails},
     968             :       {"/rest/block/", rest_block_extended},
     969             :       {"/rest/blockfilter/", rest_block_filter},
     970             :       {"/rest/blockfilterheaders/", rest_filter_header},
     971             :       {"/rest/chaininfo", rest_chaininfo},
     972             :       {"/rest/mempool/", rest_mempool},
     973             :       {"/rest/headers/", rest_headers},
     974             :       {"/rest/getutxos", rest_getutxos},
     975             :       {"/rest/blockhashbyheight/", rest_blockhash_by_height},
     976             : };
     977             : 
     978           2 : void StartREST(const CoreContext& context)
     979             : {
     980          22 :     for (const auto& up : uri_prefixes) {
     981         132 :         auto handler = [context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); };
     982          20 :         RegisterHTTPHandler(up.prefix, false, handler);
     983             :     }
     984           2 : }
     985             : 
     986        3030 : void InterruptREST()
     987             : {
     988        3030 : }
     989             : 
     990        3030 : void StopREST()
     991             : {
     992       33330 :     for (const auto& up : uri_prefixes) {
     993       30300 :         UnregisterHTTPHandler(up.prefix, false);
     994             :     }
     995        3030 : }

Generated by: LCOV version 1.16