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