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