LCOV - code coverage report
Current view: top level - src - rest.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 87.7 % 754 661
Test Date: 2026-10-03 06:57:10 Functions: 96.6 % 29 28
Branches: 48.4 % 1685 816

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

Generated by: LCOV version 2.0-1