LCOV - code coverage report
Current view: top level - src - rest.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 12 586 2.0 %
Date: 2026-06-25 07:23:51 Functions: 2 34 5.9 %

          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           0 :     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           0 : static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
      72             : {
      73           0 :     req->WriteHeader("Content-Type", "text/plain");
      74           0 :     req->WriteReply(status, message + "\r\n");
      75           0 :     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           0 : static NodeContext* GetNodeContext(const CoreContext& context, HTTPRequest* req)
      86             : {
      87           0 :     auto* node_context = GetContext<NodeContext>(context);
      88           0 :     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           0 :     return node_context;
      97           0 : }
      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           0 : static CTxMemPool* GetMemPool(const CoreContext& context, HTTPRequest* req)
     107             : {
     108           0 :     auto* node_context = GetContext<NodeContext>(context);
     109           0 :     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           0 :     return node_context->mempool.get();
     114           0 : }
     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           0 : static ChainstateManager* GetChainman(const CoreContext& context, HTTPRequest* req)
     124             : {
     125           0 :     auto node_context = GetContext<NodeContext>(context);
     126           0 :     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           0 :     return node_context->chainman.get();
     135           0 : }
     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           0 : static LLMQContext* GetLLMQContext(const CoreContext& context, HTTPRequest* req)
     145             : {
     146           0 :     auto node_context = GetContext<NodeContext>(context);
     147           0 :     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           0 :     return node_context->llmq_ctx.get();
     156           0 : }
     157             : 
     158           6 : 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           6 :     param = strReq.substr(0, strReq.rfind('?'));
     163           6 :     const std::string::size_type pos_format{param.rfind('.')};
     164             : 
     165             :     // No format string is found
     166           6 :     if (pos_format == std::string::npos) {
     167           2 :         return rf_names[0].rf;
     168             :     }
     169             : 
     170             :     // Match format string to available formats
     171           4 :     const std::string suffix(param, pos_format + 1);
     172          14 :     for (const auto& rf_name : rf_names) {
     173          13 :         if (suffix == rf_name.name) {
     174           3 :             param.erase(pos_format);
     175           3 :             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           6 : }
     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           0 : static bool CheckWarmup(HTTPRequest* req)
     201             : {
     202           0 :     std::string statusmessage;
     203           0 :     if (RPCIsInWarmup(&statusmessage))
     204           0 :          return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
     205           0 :     return true;
     206           0 : }
     207             : 
     208           0 : static bool rest_headers(const CoreContext& context,
     209             :                          HTTPRequest* req,
     210             :                          const std::string& strURIPart)
     211             : {
     212           0 :     if (!CheckWarmup(req))
     213           0 :         return false;
     214           0 :     std::string param;
     215           0 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     216           0 :     std::vector<std::string> path = SplitString(param, '/');
     217             : 
     218           0 :     std::string raw_count;
     219           0 :     std::string hashStr;
     220           0 :     if (path.size() == 2) {
     221             :         // deprecated path: /rest/headers/<count>/<hash>
     222           0 :         hashStr = path[1];
     223           0 :         raw_count = path[0];
     224           0 :     } else if (path.size() == 1) {
     225             :         // new path with query parameter: /rest/headers/<hash>?count=<count>
     226           0 :         hashStr = path[0];
     227           0 :         raw_count = req->GetQueryParameter("count").value_or("5");
     228           0 :     } else {
     229           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>");
     230             :     }
     231             : 
     232           0 :     const auto parsed_count{ToIntegral<size_t>(raw_count)};
     233           0 :     if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
     234           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));
     235             :     }
     236             : 
     237           0 :     uint256 hash;
     238           0 :     if (!ParseHashStr(hashStr, hash))
     239           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     240             : 
     241           0 :     const CBlockIndex* tip = nullptr;
     242           0 :     std::vector<const CBlockIndex*> headers;
     243           0 :     headers.reserve(*parsed_count);
     244             :     {
     245           0 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     246           0 :         if (!maybe_chainman) return false;
     247           0 :         ChainstateManager& chainman = *maybe_chainman;
     248           0 :         LOCK(cs_main);
     249           0 :         CChain& active_chain = chainman.ActiveChain();
     250           0 :         tip = active_chain.Tip();
     251           0 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
     252           0 :         while (pindex != nullptr && active_chain.Contains(pindex)) {
     253           0 :             headers.push_back(pindex);
     254           0 :             if (headers.size() == *parsed_count) {
     255           0 :                 break;
     256             :             }
     257           0 :             pindex = active_chain.Next(pindex);
     258             :         }
     259           0 :     }
     260             : 
     261           0 :     switch (rf) {
     262             :     case RESTResponseFormat::BINARY: {
     263           0 :         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
     264           0 :         for (const CBlockIndex *pindex : headers) {
     265           0 :             ssHeader << pindex->GetBlockHeader();
     266             :         }
     267             : 
     268           0 :         std::string binaryHeader = ssHeader.str();
     269           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     270           0 :         req->WriteReply(HTTP_OK, binaryHeader);
     271           0 :         return true;
     272           0 :     }
     273             : 
     274             :     case RESTResponseFormat::HEX: {
     275           0 :         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
     276           0 :         for (const CBlockIndex *pindex : headers) {
     277           0 :             ssHeader << pindex->GetBlockHeader();
     278             :         }
     279             : 
     280           0 :         std::string strHex = HexStr(ssHeader) + "\n";
     281           0 :         req->WriteHeader("Content-Type", "text/plain");
     282           0 :         req->WriteReply(HTTP_OK, strHex);
     283           0 :         return true;
     284           0 :     }
     285             :     case RESTResponseFormat::JSON: {
     286           0 :         const NodeContext* const node = GetNodeContext(context, req);
     287           0 :         if (!node || !node->chainlocks) return false;
     288             : 
     289           0 :         UniValue jsonHeaders(UniValue::VARR);
     290           0 :         for (const CBlockIndex *pindex : headers) {
     291           0 :             jsonHeaders.push_back(blockheaderToJSON(tip, pindex, *node->chainlocks));
     292             :         }
     293           0 :         std::string strJSON = jsonHeaders.write() + "\n";
     294           0 :         req->WriteHeader("Content-Type", "application/json");
     295           0 :         req->WriteReply(HTTP_OK, strJSON);
     296           0 :         return true;
     297           0 :     }
     298             :     default: {
     299           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     300             :     }
     301             :     }
     302           0 : }
     303             : 
     304           0 : static bool rest_block(const CoreContext& context,
     305             :                        HTTPRequest* req,
     306             :                        const std::string& strURIPart,
     307             :                        TxVerbosity tx_verbosity)
     308             : {
     309           0 :     if (!CheckWarmup(req))
     310           0 :         return false;
     311           0 :     std::string hashStr;
     312           0 :     const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
     313             : 
     314           0 :     uint256 hash;
     315           0 :     if (!ParseHashStr(hashStr, hash))
     316           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     317             : 
     318           0 :     CBlock block;
     319           0 :     const CBlockIndex* pblockindex = nullptr;
     320           0 :     const CBlockIndex* tip = nullptr;
     321           0 :     ChainstateManager* maybe_chainman = GetChainman(context, req);
     322           0 :     if (!maybe_chainman) return false;
     323           0 :     ChainstateManager& chainman = *maybe_chainman;
     324             :     {
     325           0 :         LOCK(cs_main);
     326           0 :         tip = chainman.ActiveChain().Tip();
     327           0 :         pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
     328           0 :         if (!pblockindex) {
     329           0 :             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     330             :         }
     331             : 
     332           0 :         if (chainman.m_blockman.IsBlockPruned(pblockindex))
     333           0 :             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
     334           0 :     }
     335             : 
     336           0 :     if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
     337           0 :         return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     338             :     }
     339             : 
     340           0 :     switch (rf) {
     341             :     case RESTResponseFormat::BINARY: {
     342           0 :         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
     343           0 :         ssBlock << block;
     344           0 :         std::string binaryBlock = ssBlock.str();
     345           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     346           0 :         req->WriteReply(HTTP_OK, binaryBlock);
     347           0 :         return true;
     348           0 :     }
     349             : 
     350             :     case RESTResponseFormat::HEX: {
     351           0 :         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
     352           0 :         ssBlock << block;
     353           0 :         std::string strHex = HexStr(ssBlock) + "\n";
     354           0 :         req->WriteHeader("Content-Type", "text/plain");
     355           0 :         req->WriteReply(HTTP_OK, strHex);
     356           0 :         return true;
     357           0 :     }
     358             : 
     359             :     case RESTResponseFormat::JSON: {
     360           0 :         const NodeContext* const node = GetNodeContext(context, req);
     361           0 :         if (!node || !node->chainlocks) return false;
     362             : 
     363           0 :         const LLMQContext* llmq_ctx = GetLLMQContext(context, req);
     364           0 :         if (!llmq_ctx) return false;
     365             : 
     366           0 :         UniValue objBlock = blockToJSON(chainman.m_blockman, block, tip, pblockindex, *node->chainlocks, *llmq_ctx->isman, tx_verbosity);
     367           0 :         std::string strJSON = objBlock.write() + "\n";
     368           0 :         req->WriteHeader("Content-Type", "application/json");
     369           0 :         req->WriteReply(HTTP_OK, strJSON);
     370           0 :         return true;
     371           0 :     }
     372             : 
     373             :     default: {
     374           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     375             :     }
     376             :     }
     377           0 : }
     378             : 
     379           0 : static bool rest_block_extended(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     380             : {
     381           0 :     return rest_block(context, req, strURIPart, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
     382             : }
     383             : 
     384           0 : static bool rest_block_notxdetails(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     385             : {
     386           0 :     return rest_block(context, req, strURIPart, TxVerbosity::SHOW_TXID);
     387             : }
     388             : 
     389           0 : static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     390             : {
     391           0 :     if (!CheckWarmup(req)) return false;
     392             : 
     393           0 :     std::string param;
     394           0 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     395             : 
     396           0 :     std::vector<std::string> uri_parts = SplitString(param, '/');
     397           0 :     std::string raw_count;
     398           0 :     std::string raw_blockhash;
     399           0 :     if (uri_parts.size() == 3) {
     400             :         // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>
     401           0 :         raw_blockhash = uri_parts[2];
     402           0 :         raw_count = uri_parts[1];
     403           0 :     } else if (uri_parts.size() == 2) {
     404             :         // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count>
     405           0 :         raw_blockhash = uri_parts[1];
     406           0 :         raw_count = req->GetQueryParameter("count").value_or("5");
     407           0 :     } else {
     408           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>");
     409             :     }
     410             : 
     411           0 :     const auto parsed_count{ToIntegral<size_t>(raw_count)};
     412           0 :     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           0 :     uint256 block_hash;
     417           0 :     if (!ParseHashStr(raw_blockhash, block_hash)) {
     418           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
     419             :     }
     420             : 
     421             :     BlockFilterType filtertype;
     422           0 :     if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
     423           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
     424             :     }
     425             : 
     426           0 :     BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
     427           0 :     if (!index) {
     428           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
     429             :     }
     430             : 
     431           0 :     std::vector<const CBlockIndex*> headers;
     432           0 :     headers.reserve(*parsed_count);
     433             :     {
     434           0 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     435           0 :         if (!maybe_chainman) return false;
     436           0 :         ChainstateManager& chainman = *maybe_chainman;
     437           0 :         LOCK(cs_main);
     438           0 :         CChain& active_chain = chainman.ActiveChain();
     439           0 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash);
     440           0 :         while (pindex != nullptr && active_chain.Contains(pindex)) {
     441           0 :             headers.push_back(pindex);
     442           0 :             if (headers.size() == *parsed_count)
     443           0 :                 break;
     444           0 :             pindex = active_chain.Next(pindex);
     445             :         }
     446           0 :     }
     447             : 
     448           0 :     bool index_ready = index->BlockUntilSyncedToCurrentChain();
     449             : 
     450           0 :     std::vector<uint256> filter_headers;
     451           0 :     filter_headers.reserve(*parsed_count);
     452           0 :     for (const CBlockIndex* pindex : headers) {
     453           0 :         uint256 filter_header;
     454           0 :         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           0 :         filter_headers.push_back(filter_header);
     466             :     }
     467             : 
     468           0 :     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           0 :         UniValue jsonHeaders(UniValue::VARR);
     493           0 :         for (const uint256& header : filter_headers) {
     494           0 :             jsonHeaders.push_back(header.GetHex());
     495             :         }
     496             : 
     497           0 :         std::string strJSON = jsonHeaders.write() + "\n";
     498           0 :         req->WriteHeader("Content-Type", "application/json");
     499           0 :         req->WriteReply(HTTP_OK, strJSON);
     500           0 :         return true;
     501           0 :     }
     502             :     default: {
     503           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     504             :     }
     505             :     }
     506           0 : }
     507             : 
     508           0 : static bool rest_block_filter(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     509             : {
     510           0 :     if (!CheckWarmup(req)) return false;
     511             : 
     512           0 :     std::string param;
     513           0 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     514             : 
     515             :     // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash
     516           0 :     std::vector<std::string> uri_parts = SplitString(param, '/');
     517           0 :     if (uri_parts.size() != 2) {
     518           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>");
     519             :     }
     520             : 
     521           0 :     uint256 block_hash;
     522           0 :     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           0 :     if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
     528           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
     529             :     }
     530             : 
     531           0 :     BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
     532           0 :     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           0 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     540           0 :         if (!maybe_chainman) return false;
     541           0 :         ChainstateManager& chainman = *maybe_chainman;
     542           0 :         LOCK(cs_main);
     543           0 :         block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
     544           0 :         if (!block_index) {
     545           0 :             return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found");
     546             :         }
     547           0 :         block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
     548           0 :     }
     549             : 
     550           0 :     bool index_ready = index->BlockUntilSyncedToCurrentChain();
     551             : 
     552           0 :     BlockFilter filter;
     553           0 :     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           0 :     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           0 :         UniValue ret(UniValue::VOBJ);
     588           0 :         ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
     589           0 :         std::string strJSON = ret.write() + "\n";
     590           0 :         req->WriteHeader("Content-Type", "application/json");
     591           0 :         req->WriteReply(HTTP_OK, strJSON);
     592           0 :         return true;
     593           0 :     }
     594             :     default: {
     595           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     596             :     }
     597             :     }
     598           0 : }
     599             : 
     600             : // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
     601             : RPCHelpMan getblockchaininfo();
     602             : 
     603           0 : static bool rest_chaininfo(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     604             : {
     605           0 :     if (!CheckWarmup(req))
     606           0 :         return false;
     607           0 :     std::string param;
     608           0 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     609             : 
     610           0 :     switch (rf) {
     611             :     case RESTResponseFormat::JSON: {
     612           0 :         JSONRPCRequest jsonRequest;
     613           0 :         jsonRequest.context = context;
     614           0 :         jsonRequest.params = UniValue(UniValue::VARR);
     615           0 :         UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest);
     616           0 :         std::string strJSON = chainInfoObject.write() + "\n";
     617           0 :         req->WriteHeader("Content-Type", "application/json");
     618           0 :         req->WriteReply(HTTP_OK, strJSON);
     619           0 :         return true;
     620           0 :     }
     621             :     default: {
     622           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
     623             :     }
     624             :     }
     625           0 : }
     626             : 
     627           0 : static bool rest_mempool(const CoreContext& context, HTTPRequest* req, const std::string& str_uri_part)
     628             : {
     629           0 :     if (!CheckWarmup(req))
     630           0 :         return false;
     631             : 
     632           0 :     std::string param;
     633           0 :     const RESTResponseFormat rf = ParseDataFormat(param, str_uri_part);
     634           0 :     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           0 :     const CTxMemPool* mempool = GetMemPool(context, req);
     639           0 :     if (!mempool) return false;
     640             : 
     641           0 :     switch (rf) {
     642             :     case RESTResponseFormat::JSON: {
     643           0 :         const LLMQContext* llmq_ctx = GetLLMQContext(context, req);
     644           0 :         if (!llmq_ctx) return false;
     645             : 
     646           0 :         std::string str_json;
     647           0 :         if (param == "contents") {
     648           0 :             str_json = MempoolToJSON(*mempool, llmq_ctx->isman.get(), true).write() + "\n";
     649           0 :         } else {
     650           0 :             str_json = MempoolInfoToJSON(*mempool, *llmq_ctx->isman).write() + "\n";
     651             :         }
     652             : 
     653           0 :         req->WriteHeader("Content-Type", "application/json");
     654           0 :         req->WriteReply(HTTP_OK, str_json);
     655           0 :         return true;
     656           0 :     }
     657             :     default: {
     658           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
     659             :     }
     660             :     }
     661           0 : }
     662             : 
     663           0 : static bool rest_tx(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     664             : {
     665           0 :     if (!CheckWarmup(req))
     666           0 :         return false;
     667           0 :     std::string hashStr;
     668           0 :     const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
     669             : 
     670           0 :     uint256 hash;
     671           0 :     if (!ParseHashStr(hashStr, hash))
     672           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
     673             : 
     674           0 :     if (g_txindex) {
     675           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
     676           0 :     }
     677             : 
     678           0 :     const NodeContext* const node = GetNodeContext(context, req);
     679           0 :     if (!node) return false;
     680           0 :     uint256 hashBlock = uint256();
     681           0 :     const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
     682           0 :     if (!tx) {
     683           0 :         return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
     684             :     }
     685             : 
     686           0 :     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           0 :         CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
     699           0 :         ssTx << tx;
     700             : 
     701           0 :         std::string strHex = HexStr(ssTx) + "\n";
     702           0 :         req->WriteHeader("Content-Type", "text/plain");
     703           0 :         req->WriteReply(HTTP_OK, strHex);
     704           0 :         return true;
     705           0 :     }
     706             : 
     707             :     case RESTResponseFormat::JSON: {
     708           0 :         UniValue objTx(UniValue::VOBJ);
     709           0 :         TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/objTx);
     710           0 :         std::string strJSON = objTx.write() + "\n";
     711           0 :         req->WriteHeader("Content-Type", "application/json");
     712           0 :         req->WriteReply(HTTP_OK, strJSON);
     713           0 :         return true;
     714           0 :     }
     715             : 
     716             :     default: {
     717           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     718             :     }
     719             :     }
     720           0 : }
     721             : 
     722           0 : static bool rest_getutxos(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
     723             : {
     724           0 :     if (!CheckWarmup(req))
     725           0 :         return false;
     726           0 :     std::string param;
     727           0 :     const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
     728             : 
     729           0 :     std::vector<std::string> uriParts;
     730           0 :     if (param.length() > 1)
     731             :     {
     732           0 :         std::string strUriParams = param.substr(1);
     733           0 :         uriParts = SplitString(strUriParams, '/');
     734           0 :     }
     735             : 
     736             :     // throw exception in case of an empty request
     737           0 :     std::string strRequestMutable = req->ReadBody();
     738           0 :     if (strRequestMutable.length() == 0 && uriParts.size() == 0)
     739           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     740             : 
     741           0 :     bool fInputParsed = false;
     742           0 :     bool fCheckMemPool = false;
     743           0 :     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           0 :     if (uriParts.size() > 0)
     749             :     {
     750             :         //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
     751           0 :         if (uriParts[0] == "checkmempool") fCheckMemPool = true;
     752             : 
     753           0 :         for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
     754             :         {
     755           0 :             uint256 txid;
     756             :             int32_t nOutput;
     757           0 :             std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
     758           0 :             std::string strOutput = uriParts[i].substr(uriParts[i].find('-')+1);
     759             : 
     760           0 :             if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
     761           0 :                 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
     762             : 
     763           0 :             txid.SetHex(strTxid);
     764           0 :             vOutPoints.emplace_back(txid, (uint32_t)nOutput);
     765           0 :         }
     766             : 
     767           0 :         if (vOutPoints.size() > 0)
     768           0 :             fInputParsed = true;
     769             :         else
     770           0 :             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     771           0 :     }
     772             : 
     773           0 :     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           0 :             if (strRequestMutable.size() > 0)
     785             :             {
     786           0 :                 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           0 :                 CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
     790           0 :                 oss << strRequestMutable;
     791           0 :                 oss >> fCheckMemPool;
     792           0 :                 oss >> vOutPoints;
     793           0 :             }
     794           0 :         } catch (const std::ios_base::failure&) {
     795             :             // abort in case of unreadable binary data
     796           0 :             return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
     797           0 :         }
     798           0 :         break;
     799             :     }
     800             : 
     801             :     case RESTResponseFormat::JSON: {
     802           0 :         if (!fInputParsed)
     803           0 :             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
     804           0 :         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           0 :     if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
     813           0 :         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           0 :     std::vector<unsigned char> bitmap;
     817           0 :     std::vector<CCoin> outs;
     818           0 :     std::string bitmapStringRepresentation;
     819           0 :     std::vector<bool> hits;
     820           0 :     bitmap.resize((vOutPoints.size() + 7) / 8);
     821           0 :     ChainstateManager* maybe_chainman = GetChainman(context, req);
     822           0 :     if (!maybe_chainman) return false;
     823           0 :     ChainstateManager& chainman = *maybe_chainman;
     824             :     {
     825           0 :         auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool* mempool) {
     826           0 :             for (const COutPoint& vOutPoint : vOutPoints) {
     827           0 :                 Coin coin;
     828           0 :                 bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin);
     829           0 :                 hits.push_back(hit);
     830           0 :                 if (hit) outs.emplace_back(std::move(coin));
     831           0 :             }
     832           0 :         };
     833             : 
     834           0 :         if (fCheckMemPool) {
     835           0 :             const CTxMemPool* mempool = GetMemPool(context, req);
     836           0 :             if (!mempool) return false;
     837             :             // use db+mempool as cache backend in case user likes to query mempool
     838           0 :             LOCK2(cs_main, mempool->cs);
     839           0 :             CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip();
     840           0 :             CCoinsViewMemPool viewMempool(&viewChain, *mempool);
     841           0 :             process_utxos(viewMempool, mempool);
     842           0 :         } else {
     843           0 :             LOCK(cs_main);
     844           0 :             process_utxos(chainman.ActiveChainstate().CoinsTip(), nullptr);
     845           0 :         }
     846             : 
     847           0 :         for (size_t i = 0; i < hits.size(); ++i) {
     848           0 :             const bool hit = hits[i];
     849           0 :             bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
     850           0 :             bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
     851           0 :         }
     852             :     }
     853             : 
     854           0 :     switch (rf) {
     855             :     case RESTResponseFormat::BINARY: {
     856             :         // serialize data
     857             :         // use exact same output as mentioned in Bip64
     858           0 :         CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
     859           0 :         ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
     860           0 :         std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
     861             : 
     862           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     863           0 :         req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
     864           0 :         return true;
     865           0 :     }
     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           0 :         UniValue objGetUTXOResponse(UniValue::VOBJ);
     879             : 
     880             :         // pack in some essentials
     881             :         // use more or less the same output as mentioned in Bip64
     882           0 :         objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
     883           0 :         objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
     884           0 :         objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
     885             : 
     886           0 :         UniValue utxos(UniValue::VARR);
     887           0 :         for (const CCoin& coin : outs) {
     888           0 :             UniValue utxo(UniValue::VOBJ);
     889           0 :             utxo.pushKV("height", (int32_t)coin.nHeight);
     890           0 :             utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
     891             : 
     892             :             // include the script in a json output
     893           0 :             UniValue o(UniValue::VOBJ);
     894           0 :             ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
     895           0 :             utxo.pushKV("scriptPubKey", o);
     896           0 :             utxos.push_back(utxo);
     897           0 :         }
     898           0 :         objGetUTXOResponse.pushKV("utxos", utxos);
     899             : 
     900             :         // return json string
     901           0 :         std::string strJSON = objGetUTXOResponse.write() + "\n";
     902           0 :         req->WriteHeader("Content-Type", "application/json");
     903           0 :         req->WriteReply(HTTP_OK, strJSON);
     904           0 :         return true;
     905           0 :     }
     906             :     default: {
     907           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     908             :     }
     909             :     }
     910           0 : }
     911             : 
     912           0 : static bool rest_blockhash_by_height(const CoreContext& context, HTTPRequest* req,
     913             :                        const std::string& str_uri_part)
     914             : {
     915           0 :     if (!CheckWarmup(req)) return false;
     916           0 :     std::string height_str;
     917           0 :     const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part);
     918             : 
     919           0 :     int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
     920           0 :     if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
     921           0 :         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str));
     922             :     }
     923             : 
     924           0 :     CBlockIndex* pblockindex = nullptr;
     925             :     {
     926           0 :         ChainstateManager* maybe_chainman = GetChainman(context, req);
     927           0 :         if (!maybe_chainman) return false;
     928           0 :         ChainstateManager& chainman = *maybe_chainman;
     929           0 :         LOCK(cs_main);
     930           0 :         const CChain& active_chain = chainman.ActiveChain();
     931           0 :         if (blockheight > active_chain.Height()) {
     932           0 :             return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
     933             :         }
     934           0 :         pblockindex = active_chain[blockheight];
     935           0 :     }
     936           0 :     switch (rf) {
     937             :     case RESTResponseFormat::BINARY: {
     938           0 :         CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
     939           0 :         ss_blockhash << pblockindex->GetBlockHash();
     940           0 :         req->WriteHeader("Content-Type", "application/octet-stream");
     941           0 :         req->WriteReply(HTTP_OK, ss_blockhash.str());
     942           0 :         return true;
     943           0 :     }
     944             :     case RESTResponseFormat::HEX: {
     945           0 :         req->WriteHeader("Content-Type", "text/plain");
     946           0 :         req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
     947           0 :         return true;
     948             :     }
     949             :     case RESTResponseFormat::JSON: {
     950           0 :         req->WriteHeader("Content-Type", "application/json");
     951           0 :         UniValue resp = UniValue(UniValue::VOBJ);
     952           0 :         resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
     953           0 :         req->WriteReply(HTTP_OK, resp.write() + "\n");
     954           0 :         return true;
     955           0 :     }
     956             :     default: {
     957           0 :         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
     958             :     }
     959             :     }
     960           0 : }
     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           0 : void StartREST(const CoreContext& context)
     979             : {
     980           0 :     for (const auto& up : uri_prefixes) {
     981           0 :         auto handler = [context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); };
     982           0 :         RegisterHTTPHandler(up.prefix, false, handler);
     983             :     }
     984           0 : }
     985             : 
     986           0 : void InterruptREST()
     987             : {
     988           0 : }
     989             : 
     990           0 : void StopREST()
     991             : {
     992           0 :     for (const auto& up : uri_prefixes) {
     993           0 :         UnregisterHTTPHandler(up.prefix, false);
     994             :     }
     995           0 : }

Generated by: LCOV version 1.16