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