1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2020 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 <chain.h>
7 #include <chainparams.h>
8 #include <core_io.h>
9 #include <httpserver.h>
10 #include <index/txindex.h>
11 #include <node/blockstorage.h>
12 #include <node/context.h>
13 #include <primitives/block.h>
14 #include <primitives/transaction.h>
15 #include <rpc/blockchain.h>
16 #include <rpc/protocol.h>
17 #include <rpc/server.h>
18 #include <streams.h>
19 #include <sync.h>
20 #include <txmempool.h>
21 #include <util/check.h>
22 #include <util/system.h>
23 #include <validation.h>
24 #include <version.h>
25 
26 #include <any>
27 
28 #include <boost/algorithm/string.hpp>
29 
30 #include <univalue.h>
31 
32 static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
33 
34 enum class RetFormat {
35     UNDEF,
36     BINARY,
37     HEX,
38     JSON,
39 };
40 
41 static const struct {
42     RetFormat rf;
43     const char* name;
44 } rf_names[] = {
45       {RetFormat::UNDEF, ""},
46       {RetFormat::BINARY, "bin"},
47       {RetFormat::HEX, "hex"},
48       {RetFormat::JSON, "json"},
49 };
50 
51 struct CCoin {
52     uint32_t nHeight;
53     CTxOut out;
54 
CCoinCCoin55     CCoin() : nHeight(0) {}
CCoinCCoin56     explicit CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
57 
SERIALIZE_METHODSCCoin58     SERIALIZE_METHODS(CCoin, obj)
59     {
60         uint32_t nTxVerDummy = 0;
61         READWRITE(nTxVerDummy, obj.nHeight, obj.out);
62     }
63 };
64 
RESTERR(HTTPRequest * req,enum HTTPStatusCode status,std::string message)65 static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
66 {
67     req->WriteHeader("Content-Type", "text/plain");
68     req->WriteReply(status, message + "\r\n");
69     return false;
70 }
71 
72 /**
73  * Get the node context.
74  *
75  * @param[in]  req  The HTTP request, whose status code will be set if node
76  *                  context is not found.
77  * @returns         Pointer to the node context or nullptr if not found.
78  */
GetNodeContext(const std::any & context,HTTPRequest * req)79 static NodeContext* GetNodeContext(const std::any& context, HTTPRequest* req)
80 {
81     auto node_context = util::AnyPtr<NodeContext>(context);
82     if (!node_context) {
83         RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
84                 strprintf("%s:%d (%s)\n"
85                           "Internal bug detected: Node context not found!\n"
86                           "You may report this issue here: %s\n",
87                           __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
88         return nullptr;
89     }
90     return node_context;
91 }
92 
93 /**
94  * Get the node context mempool.
95  *
96  * @param[in]  req The HTTP request, whose status code will be set if node
97  *                 context mempool is not found.
98  * @returns        Pointer to the mempool or nullptr if no mempool found.
99  */
GetMemPool(const std::any & context,HTTPRequest * req)100 static CTxMemPool* GetMemPool(const std::any& context, HTTPRequest* req)
101 {
102     auto node_context = util::AnyPtr<NodeContext>(context);
103     if (!node_context || !node_context->mempool) {
104         RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found");
105         return nullptr;
106     }
107     return node_context->mempool.get();
108 }
109 
110 /**
111  * Get the node context chainstatemanager.
112  *
113  * @param[in]  req The HTTP request, whose status code will be set if node
114  *                 context chainstatemanager is not found.
115  * @returns        Pointer to the chainstatemanager or nullptr if none found.
116  */
GetChainman(const std::any & context,HTTPRequest * req)117 static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req)
118 {
119     auto node_context = util::AnyPtr<NodeContext>(context);
120     if (!node_context || !node_context->chainman) {
121         RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
122                 strprintf("%s:%d (%s)\n"
123                           "Internal bug detected: Chainman disabled or instance not found!\n"
124                           "You may report this issue here: %s\n",
125                           __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
126         return nullptr;
127     }
128     return node_context->chainman.get();
129 }
130 
ParseDataFormat(std::string & param,const std::string & strReq)131 static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
132 {
133     const std::string::size_type pos = strReq.rfind('.');
134     if (pos == std::string::npos)
135     {
136         param = strReq;
137         return rf_names[0].rf;
138     }
139 
140     param = strReq.substr(0, pos);
141     const std::string suff(strReq, pos + 1);
142 
143     for (const auto& rf_name : rf_names) {
144         if (suff == rf_name.name)
145             return rf_name.rf;
146     }
147 
148     /* If no suffix is found, return original string.  */
149     param = strReq;
150     return rf_names[0].rf;
151 }
152 
AvailableDataFormatsString()153 static std::string AvailableDataFormatsString()
154 {
155     std::string formats;
156     for (const auto& rf_name : rf_names) {
157         if (strlen(rf_name.name) > 0) {
158             formats.append(".");
159             formats.append(rf_name.name);
160             formats.append(", ");
161         }
162     }
163 
164     if (formats.length() > 0)
165         return formats.substr(0, formats.length() - 2);
166 
167     return formats;
168 }
169 
CheckWarmup(HTTPRequest * req)170 static bool CheckWarmup(HTTPRequest* req)
171 {
172     std::string statusmessage;
173     if (RPCIsInWarmup(&statusmessage))
174          return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
175     return true;
176 }
177 
rest_headers(const std::any & context,HTTPRequest * req,const std::string & strURIPart)178 static bool rest_headers(const std::any& context,
179                          HTTPRequest* req,
180                          const std::string& strURIPart)
181 {
182     if (!CheckWarmup(req))
183         return false;
184     std::string param;
185     const RetFormat rf = ParseDataFormat(param, strURIPart);
186     std::vector<std::string> path;
187     boost::split(path, param, boost::is_any_of("/"));
188 
189     if (path.size() != 2)
190         return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
191 
192     long count = strtol(path[0].c_str(), nullptr, 10);
193     if (count < 1 || count > 2000)
194         return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
195 
196     std::string hashStr = path[1];
197     uint256 hash;
198     if (!ParseHashStr(hashStr, hash))
199         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
200 
201     const CBlockIndex* tip = nullptr;
202     std::vector<const CBlockIndex *> headers;
203     headers.reserve(count);
204     {
205         ChainstateManager* maybe_chainman = GetChainman(context, req);
206         if (!maybe_chainman) return false;
207         ChainstateManager& chainman = *maybe_chainman;
208         LOCK(cs_main);
209         CChain& active_chain = chainman.ActiveChain();
210         tip = active_chain.Tip();
211         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
212         while (pindex != nullptr && active_chain.Contains(pindex)) {
213             headers.push_back(pindex);
214             if (headers.size() == (unsigned long)count)
215                 break;
216             pindex = active_chain.Next(pindex);
217         }
218     }
219 
220     switch (rf) {
221     case RetFormat::BINARY: {
222         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
223         for (const CBlockIndex *pindex : headers) {
224             ssHeader << pindex->GetBlockHeader();
225         }
226 
227         std::string binaryHeader = ssHeader.str();
228         req->WriteHeader("Content-Type", "application/octet-stream");
229         req->WriteReply(HTTP_OK, binaryHeader);
230         return true;
231     }
232 
233     case RetFormat::HEX: {
234         CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
235         for (const CBlockIndex *pindex : headers) {
236             ssHeader << pindex->GetBlockHeader();
237         }
238 
239         std::string strHex = HexStr(ssHeader) + "\n";
240         req->WriteHeader("Content-Type", "text/plain");
241         req->WriteReply(HTTP_OK, strHex);
242         return true;
243     }
244     case RetFormat::JSON: {
245         UniValue jsonHeaders(UniValue::VARR);
246         for (const CBlockIndex *pindex : headers) {
247             jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
248         }
249         std::string strJSON = jsonHeaders.write() + "\n";
250         req->WriteHeader("Content-Type", "application/json");
251         req->WriteReply(HTTP_OK, strJSON);
252         return true;
253     }
254     default: {
255         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex, .json)");
256     }
257     }
258 }
259 
rest_block(const std::any & context,HTTPRequest * req,const std::string & strURIPart,bool showTxDetails)260 static bool rest_block(const std::any& context,
261                        HTTPRequest* req,
262                        const std::string& strURIPart,
263                        bool showTxDetails)
264 {
265     if (!CheckWarmup(req))
266         return false;
267     std::string hashStr;
268     const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
269 
270     uint256 hash;
271     if (!ParseHashStr(hashStr, hash))
272         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
273 
274     CBlock block;
275     CBlockIndex* pblockindex = nullptr;
276     CBlockIndex* tip = nullptr;
277     {
278         ChainstateManager* maybe_chainman = GetChainman(context, req);
279         if (!maybe_chainman) return false;
280         ChainstateManager& chainman = *maybe_chainman;
281         LOCK(cs_main);
282         tip = chainman.ActiveChain().Tip();
283         pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
284         if (!pblockindex) {
285             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
286         }
287 
288         if (IsBlockPruned(pblockindex))
289             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
290 
291         if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
292             return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
293     }
294 
295     switch (rf) {
296     case RetFormat::BINARY: {
297         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
298         ssBlock << block;
299         std::string binaryBlock = ssBlock.str();
300         req->WriteHeader("Content-Type", "application/octet-stream");
301         req->WriteReply(HTTP_OK, binaryBlock);
302         return true;
303     }
304 
305     case RetFormat::HEX: {
306         CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
307         ssBlock << block;
308         std::string strHex = HexStr(ssBlock) + "\n";
309         req->WriteHeader("Content-Type", "text/plain");
310         req->WriteReply(HTTP_OK, strHex);
311         return true;
312     }
313 
314     case RetFormat::JSON: {
315         UniValue objBlock = blockToJSON(block, tip, pblockindex, showTxDetails);
316         std::string strJSON = objBlock.write() + "\n";
317         req->WriteHeader("Content-Type", "application/json");
318         req->WriteReply(HTTP_OK, strJSON);
319         return true;
320     }
321 
322     default: {
323         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
324     }
325     }
326 }
327 
rest_block_extended(const std::any & context,HTTPRequest * req,const std::string & strURIPart)328 static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
329 {
330     return rest_block(context, req, strURIPart, true);
331 }
332 
rest_block_notxdetails(const std::any & context,HTTPRequest * req,const std::string & strURIPart)333 static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
334 {
335     return rest_block(context, req, strURIPart, false);
336 }
337 
338 // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
339 RPCHelpMan getblockchaininfo();
340 
rest_chaininfo(const std::any & context,HTTPRequest * req,const std::string & strURIPart)341 static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
342 {
343     if (!CheckWarmup(req))
344         return false;
345     std::string param;
346     const RetFormat rf = ParseDataFormat(param, strURIPart);
347 
348     switch (rf) {
349     case RetFormat::JSON: {
350         JSONRPCRequest jsonRequest;
351         jsonRequest.context = context;
352         jsonRequest.params = UniValue(UniValue::VARR);
353         UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest);
354         std::string strJSON = chainInfoObject.write() + "\n";
355         req->WriteHeader("Content-Type", "application/json");
356         req->WriteReply(HTTP_OK, strJSON);
357         return true;
358     }
359     default: {
360         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
361     }
362     }
363 }
364 
rest_mempool_info(const std::any & context,HTTPRequest * req,const std::string & strURIPart)365 static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
366 {
367     if (!CheckWarmup(req))
368         return false;
369     const CTxMemPool* mempool = GetMemPool(context, req);
370     if (!mempool) return false;
371     std::string param;
372     const RetFormat rf = ParseDataFormat(param, strURIPart);
373 
374     switch (rf) {
375     case RetFormat::JSON: {
376         UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
377 
378         std::string strJSON = mempoolInfoObject.write() + "\n";
379         req->WriteHeader("Content-Type", "application/json");
380         req->WriteReply(HTTP_OK, strJSON);
381         return true;
382     }
383     default: {
384         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
385     }
386     }
387 }
388 
rest_mempool_contents(const std::any & context,HTTPRequest * req,const std::string & strURIPart)389 static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
390 {
391     if (!CheckWarmup(req)) return false;
392     const CTxMemPool* mempool = GetMemPool(context, req);
393     if (!mempool) return false;
394     std::string param;
395     const RetFormat rf = ParseDataFormat(param, strURIPart);
396 
397     switch (rf) {
398     case RetFormat::JSON: {
399         UniValue mempoolObject = MempoolToJSON(*mempool, true);
400 
401         std::string strJSON = mempoolObject.write() + "\n";
402         req->WriteHeader("Content-Type", "application/json");
403         req->WriteReply(HTTP_OK, strJSON);
404         return true;
405     }
406     default: {
407         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
408     }
409     }
410 }
411 
rest_tx(const std::any & context,HTTPRequest * req,const std::string & strURIPart)412 static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
413 {
414     if (!CheckWarmup(req))
415         return false;
416     std::string hashStr;
417     const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
418 
419     uint256 hash;
420     if (!ParseHashStr(hashStr, hash))
421         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
422 
423     if (g_txindex) {
424         g_txindex->BlockUntilSyncedToCurrentChain();
425     }
426 
427     const NodeContext* const node = GetNodeContext(context, req);
428     if (!node) return false;
429     uint256 hashBlock = uint256();
430     const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
431     if (!tx) {
432         return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
433     }
434 
435     switch (rf) {
436     case RetFormat::BINARY: {
437         CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
438         ssTx << tx;
439 
440         std::string binaryTx = ssTx.str();
441         req->WriteHeader("Content-Type", "application/octet-stream");
442         req->WriteReply(HTTP_OK, binaryTx);
443         return true;
444     }
445 
446     case RetFormat::HEX: {
447         CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
448         ssTx << tx;
449 
450         std::string strHex = HexStr(ssTx) + "\n";
451         req->WriteHeader("Content-Type", "text/plain");
452         req->WriteReply(HTTP_OK, strHex);
453         return true;
454     }
455 
456     case RetFormat::JSON: {
457         UniValue objTx(UniValue::VOBJ);
458         TxToUniv(*tx, hashBlock, objTx);
459         std::string strJSON = objTx.write() + "\n";
460         req->WriteHeader("Content-Type", "application/json");
461         req->WriteReply(HTTP_OK, strJSON);
462         return true;
463     }
464 
465     default: {
466         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
467     }
468     }
469 }
470 
rest_getutxos(const std::any & context,HTTPRequest * req,const std::string & strURIPart)471 static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
472 {
473     if (!CheckWarmup(req))
474         return false;
475     std::string param;
476     const RetFormat rf = ParseDataFormat(param, strURIPart);
477 
478     std::vector<std::string> uriParts;
479     if (param.length() > 1)
480     {
481         std::string strUriParams = param.substr(1);
482         boost::split(uriParts, strUriParams, boost::is_any_of("/"));
483     }
484 
485     // throw exception in case of an empty request
486     std::string strRequestMutable = req->ReadBody();
487     if (strRequestMutable.length() == 0 && uriParts.size() == 0)
488         return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
489 
490     bool fInputParsed = false;
491     bool fCheckMemPool = false;
492     std::vector<COutPoint> vOutPoints;
493 
494     // parse/deserialize input
495     // input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
496 
497     if (uriParts.size() > 0)
498     {
499         //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
500         if (uriParts[0] == "checkmempool") fCheckMemPool = true;
501 
502         for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
503         {
504             uint256 txid;
505             int32_t nOutput;
506             std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
507             std::string strOutput = uriParts[i].substr(uriParts[i].find('-')+1);
508 
509             if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
510                 return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
511 
512             txid.SetHex(strTxid);
513             vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
514         }
515 
516         if (vOutPoints.size() > 0)
517             fInputParsed = true;
518         else
519             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
520     }
521 
522     switch (rf) {
523     case RetFormat::HEX: {
524         // convert hex to bin, continue then with bin part
525         std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
526         strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
527         [[fallthrough]];
528     }
529 
530     case RetFormat::BINARY: {
531         try {
532             //deserialize only if user sent a request
533             if (strRequestMutable.size() > 0)
534             {
535                 if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
536                     return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");
537 
538                 CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
539                 oss << strRequestMutable;
540                 oss >> fCheckMemPool;
541                 oss >> vOutPoints;
542             }
543         } catch (const std::ios_base::failure&) {
544             // abort in case of unreadable binary data
545             return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
546         }
547         break;
548     }
549 
550     case RetFormat::JSON: {
551         if (!fInputParsed)
552             return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
553         break;
554     }
555     default: {
556         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
557     }
558     }
559 
560     // limit max outpoints
561     if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
562         return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
563 
564     // check spentness and form a bitmap (as well as a JSON capable human-readable string representation)
565     std::vector<unsigned char> bitmap;
566     std::vector<CCoin> outs;
567     std::string bitmapStringRepresentation;
568     std::vector<bool> hits;
569     bitmap.resize((vOutPoints.size() + 7) / 8);
570     ChainstateManager* maybe_chainman = GetChainman(context, req);
571     if (!maybe_chainman) return false;
572     ChainstateManager& chainman = *maybe_chainman;
573     {
574         auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) {
575             for (const COutPoint& vOutPoint : vOutPoints) {
576                 Coin coin;
577                 bool hit = !mempool.isSpent(vOutPoint) && view.GetCoin(vOutPoint, coin);
578                 hits.push_back(hit);
579                 if (hit) outs.emplace_back(std::move(coin));
580             }
581         };
582 
583         if (fCheckMemPool) {
584             const CTxMemPool* mempool = GetMemPool(context, req);
585             if (!mempool) return false;
586             // use db+mempool as cache backend in case user likes to query mempool
587             LOCK2(cs_main, mempool->cs);
588             CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip();
589             CCoinsViewMemPool viewMempool(&viewChain, *mempool);
590             process_utxos(viewMempool, *mempool);
591         } else {
592             LOCK(cs_main);  // no need to lock mempool!
593             process_utxos(chainman.ActiveChainstate().CoinsTip(), CTxMemPool());
594         }
595 
596         for (size_t i = 0; i < hits.size(); ++i) {
597             const bool hit = hits[i];
598             bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
599             bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
600         }
601     }
602 
603     switch (rf) {
604     case RetFormat::BINARY: {
605         // serialize data
606         // use exact same output as mentioned in Bip64
607         CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
608         ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
609         std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
610 
611         req->WriteHeader("Content-Type", "application/octet-stream");
612         req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
613         return true;
614     }
615 
616     case RetFormat::HEX: {
617         CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
618         ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
619         std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
620 
621         req->WriteHeader("Content-Type", "text/plain");
622         req->WriteReply(HTTP_OK, strHex);
623         return true;
624     }
625 
626     case RetFormat::JSON: {
627         UniValue objGetUTXOResponse(UniValue::VOBJ);
628 
629         // pack in some essentials
630         // use more or less the same output as mentioned in Bip64
631         objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
632         objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
633         objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
634 
635         UniValue utxos(UniValue::VARR);
636         for (const CCoin& coin : outs) {
637             UniValue utxo(UniValue::VOBJ);
638             utxo.pushKV("height", (int32_t)coin.nHeight);
639             utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
640 
641             // include the script in a json output
642             UniValue o(UniValue::VOBJ);
643             ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
644             utxo.pushKV("scriptPubKey", o);
645             utxos.push_back(utxo);
646         }
647         objGetUTXOResponse.pushKV("utxos", utxos);
648 
649         // return json string
650         std::string strJSON = objGetUTXOResponse.write() + "\n";
651         req->WriteHeader("Content-Type", "application/json");
652         req->WriteReply(HTTP_OK, strJSON);
653         return true;
654     }
655     default: {
656         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
657     }
658     }
659 }
660 
rest_blockhash_by_height(const std::any & context,HTTPRequest * req,const std::string & str_uri_part)661 static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
662                        const std::string& str_uri_part)
663 {
664     if (!CheckWarmup(req)) return false;
665     std::string height_str;
666     const RetFormat rf = ParseDataFormat(height_str, str_uri_part);
667 
668     int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
669     if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
670         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str));
671     }
672 
673     CBlockIndex* pblockindex = nullptr;
674     {
675         ChainstateManager* maybe_chainman = GetChainman(context, req);
676         if (!maybe_chainman) return false;
677         ChainstateManager& chainman = *maybe_chainman;
678         LOCK(cs_main);
679         const CChain& active_chain = chainman.ActiveChain();
680         if (blockheight > active_chain.Height()) {
681             return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
682         }
683         pblockindex = active_chain[blockheight];
684     }
685     switch (rf) {
686     case RetFormat::BINARY: {
687         CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
688         ss_blockhash << pblockindex->GetBlockHash();
689         req->WriteHeader("Content-Type", "application/octet-stream");
690         req->WriteReply(HTTP_OK, ss_blockhash.str());
691         return true;
692     }
693     case RetFormat::HEX: {
694         req->WriteHeader("Content-Type", "text/plain");
695         req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
696         return true;
697     }
698     case RetFormat::JSON: {
699         req->WriteHeader("Content-Type", "application/json");
700         UniValue resp = UniValue(UniValue::VOBJ);
701         resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
702         req->WriteReply(HTTP_OK, resp.write() + "\n");
703         return true;
704     }
705     default: {
706         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
707     }
708     }
709 }
710 
711 static const struct {
712     const char* prefix;
713     bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq);
714 } uri_prefixes[] = {
715       {"/rest/tx/", rest_tx},
716       {"/rest/block/notxdetails/", rest_block_notxdetails},
717       {"/rest/block/", rest_block_extended},
718       {"/rest/chaininfo", rest_chaininfo},
719       {"/rest/mempool/info", rest_mempool_info},
720       {"/rest/mempool/contents", rest_mempool_contents},
721       {"/rest/headers/", rest_headers},
722       {"/rest/getutxos", rest_getutxos},
723       {"/rest/blockhashbyheight/", rest_blockhash_by_height},
724 };
725 
StartREST(const std::any & context)726 void StartREST(const std::any& context)
727 {
728     for (const auto& up : uri_prefixes) {
729         auto handler = [context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); };
730         RegisterHTTPHandler(up.prefix, false, handler);
731     }
732 }
733 
InterruptREST()734 void InterruptREST()
735 {
736 }
737 
StopREST()738 void StopREST()
739 {
740     for (const auto& up : uri_prefixes) {
741         UnregisterHTTPHandler(up.prefix, false);
742     }
743 }
744