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