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