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