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