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