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