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