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