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