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