Line data Source code
1 : // Copyright (c) 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 <rpc/blockchain.h>
7 :
8 : #include <blockfilter.h>
9 : #include <chain.h>
10 : #include <chainparams.h>
11 : #include <coins.h>
12 : #include <common/args.h>
13 : #include <consensus/amount.h>
14 : #include <consensus/params.h>
15 : #include <consensus/validation.h>
16 : #include <core_io.h>
17 2 : #include <deploymentinfo.h>
18 2 : #include <deploymentstatus.h>
19 : #include <hash.h>
20 : #include <index/blockfilterindex.h>
21 : #include <index/coinstatsindex.h>
22 : #include <kernel/coinstats.h>
23 : #include <logging/timer.h>
24 : #include <net.h>
25 : #include <net_processing.h>
26 : #include <node/blockstorage.h>
27 2 : #include <node/context.h>
28 : #include <node/transaction.h>
29 : #include <node/utxo_snapshot.h>
30 : #include <primitives/transaction.h>
31 : #include <rpc/server.h>
32 : #include <rpc/server_util.h>
33 : #include <rpc/util.h>
34 : #include <script/descriptor.h>
35 : #include <streams.h>
36 : #include <sync.h>
37 : #include <txdb.h>
38 : #include <txmempool.h>
39 : #include <undo.h>
40 : #include <univalue.h>
41 : #include <util/check.h>
42 : #include <util/fs.h>
43 : #include <util/strencodings.h>
44 : #include <util/translation.h>
45 : #include <validation.h>
46 : #include <validationinterface.h>
47 : #include <versionbits.h>
48 : #include <warnings.h>
49 :
50 : #include <stdint.h>
51 2 :
52 : #include <condition_variable>
53 : #include <memory>
54 : #include <mutex>
55 :
56 : using kernel::CCoinsStats;
57 : using kernel::CoinStatsHashType;
58 :
59 : using node::BlockManager;
60 : using node::NodeContext;
61 : using node::SnapshotMetadata;
62 :
63 : struct CUpdatedBlock
64 : {
65 : uint256 hash;
66 : int height;
67 : };
68 :
69 : static GlobalMutex cs_blockchange;
70 2 : static std::condition_variable cond_blockchange;
71 2 : static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
72 :
73 : /* Calculate the difficulty for a given block index.
74 2 : */
75 0 : double GetDifficulty(const CBlockIndex* blockindex)
76 : {
77 0 : CHECK_NONFATAL(blockindex);
78 :
79 0 : int nShift = (blockindex->nBits >> 24) & 0xff;
80 0 : double dDiff =
81 0 : (double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff);
82 :
83 2 : while (nShift < 29)
84 : {
85 0 : dDiff *= 256.0;
86 0 : nShift++;
87 : }
88 0 : while (nShift > 29)
89 : {
90 0 : dDiff /= 256.0;
91 0 : nShift--;
92 : }
93 :
94 0 : return dDiff;
95 : }
96 :
97 0 : static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* blockindex, const CBlockIndex*& next)
98 : {
99 0 : next = tip->GetAncestor(blockindex->nHeight + 1);
100 0 : if (next && next->pprev == blockindex) {
101 0 : return tip->nHeight - blockindex->nHeight + 1;
102 : }
103 0 : next = nullptr;
104 0 : return blockindex == tip ? 1 : -1;
105 0 : }
106 :
107 0 : static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman)
108 : {
109 0 : LOCK(::cs_main);
110 0 : CChain& active_chain = chainman.ActiveChain();
111 :
112 0 : if (param.isNum()) {
113 0 : const int height{param.getInt<int>()};
114 0 : if (height < 0) {
115 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
116 : }
117 0 : const int current_tip{active_chain.Height()};
118 0 : if (height > current_tip) {
119 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
120 : }
121 :
122 0 : return active_chain[height];
123 : } else {
124 0 : const uint256 hash{ParseHashV(param, "hash_or_height")};
125 0 : const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
126 :
127 0 : if (!pindex) {
128 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
129 : }
130 :
131 0 : return pindex;
132 : }
133 0 : }
134 :
135 0 : UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex)
136 : {
137 : // Serialize passed information without accessing chain state of the active chain!
138 0 : AssertLockNotHeld(cs_main); // For performance reasons
139 :
140 0 : UniValue result(UniValue::VOBJ);
141 0 : result.pushKV("hash", blockindex->GetBlockHash().GetHex());
142 : const CBlockIndex* pnext;
143 0 : int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
144 0 : result.pushKV("confirmations", confirmations);
145 0 : result.pushKV("height", blockindex->nHeight);
146 0 : result.pushKV("version", blockindex->nVersion);
147 0 : result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
148 0 : result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
149 0 : result.pushKV("time", (int64_t)blockindex->nTime);
150 6 : result.pushKV("mediantime", (int64_t)blockindex->GetMedianTimePast());
151 8 : result.pushKV("nonce", (uint64_t)blockindex->nNonce);
152 0 : result.pushKV("bits", strprintf("%08x", blockindex->nBits));
153 0 : result.pushKV("difficulty", GetDifficulty(blockindex));
154 0 : result.pushKV("chainwork", blockindex->nChainWork.GetHex());
155 0 : result.pushKV("nTx", (uint64_t)blockindex->nTx);
156 :
157 0 : if (blockindex->pprev)
158 0 : result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
159 0 : if (pnext)
160 0 : result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
161 0 : return result;
162 0 : }
163 :
164 0 : UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
165 : {
166 0 : UniValue result = blockheaderToJSON(tip, blockindex);
167 :
168 0 : result.pushKV("strippedsize", (int)::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS));
169 0 : result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
170 0 : result.pushKV("weight", (int)::GetBlockWeight(block));
171 0 : UniValue txs(UniValue::VARR);
172 :
173 0 : switch (verbosity) {
174 : case TxVerbosity::SHOW_TXID:
175 0 : for (const CTransactionRef& tx : block.vtx) {
176 0 : txs.push_back(tx->GetHash().GetHex());
177 : }
178 0 : break;
179 :
180 : case TxVerbosity::SHOW_DETAILS:
181 : case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
182 0 : CBlockUndo blockUndo;
183 0 : const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
184 0 : const bool have_undo{is_not_pruned && blockman.UndoReadFromDisk(blockUndo, *blockindex)};
185 :
186 0 : for (size_t i = 0; i < block.vtx.size(); ++i) {
187 0 : const CTransactionRef& tx = block.vtx.at(i);
188 : // coinbase transaction (i.e. i == 0) doesn't have undo data
189 0 : const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
190 0 : UniValue objTx(UniValue::VOBJ);
191 0 : TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
192 0 : txs.push_back(objTx);
193 0 : }
194 : break;
195 0 : }
196 :
197 0 : result.pushKV("tx", txs);
198 :
199 0 : return result;
200 0 : }
201 :
202 2 : static RPCHelpMan getblockcount()
203 : {
204 4 : return RPCHelpMan{"getblockcount",
205 2 : "\nReturns the height of the most-work fully-validated chain.\n"
206 : "The genesis block has height 0.\n",
207 2 : {},
208 2 : RPCResult{
209 2 : RPCResult::Type::NUM, "", "The current block count"},
210 2 : RPCExamples{
211 2 : HelpExampleCli("getblockcount", "")
212 2 : + HelpExampleRpc("getblockcount", "")
213 : },
214 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
215 : {
216 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
217 0 : LOCK(cs_main);
218 0 : return chainman.ActiveChain().Height();
219 0 : },
220 : };
221 0 : }
222 :
223 2 : static RPCHelpMan getbestblockhash()
224 : {
225 4 : return RPCHelpMan{"getbestblockhash",
226 2 : "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n",
227 2 : {},
228 2 : RPCResult{
229 2 : RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
230 2 : RPCExamples{
231 2 : HelpExampleCli("getbestblockhash", "")
232 2 : + HelpExampleRpc("getbestblockhash", "")
233 : },
234 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
235 : {
236 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
237 0 : LOCK(cs_main);
238 0 : return chainman.ActiveChain().Tip()->GetBlockHash().GetHex();
239 0 : },
240 : };
241 0 : }
242 :
243 0 : void RPCNotifyBlockChange(const CBlockIndex* pindex)
244 : {
245 0 : if(pindex) {
246 0 : LOCK(cs_blockchange);
247 0 : latestblock.hash = pindex->GetBlockHash();
248 0 : latestblock.height = pindex->nHeight;
249 0 : }
250 0 : cond_blockchange.notify_all();
251 0 : }
252 :
253 2 : static RPCHelpMan waitfornewblock()
254 : {
255 4 : return RPCHelpMan{"waitfornewblock",
256 2 : "\nWaits for a specific new block and returns useful info about it.\n"
257 : "\nReturns the current block on timeout or exit.\n",
258 4 : {
259 2 : {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
260 : },
261 2 : RPCResult{
262 2 : RPCResult::Type::OBJ, "", "",
263 6 : {
264 2 : {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
265 2 : {RPCResult::Type::NUM, "height", "Block height"},
266 : }},
267 2 : RPCExamples{
268 2 : HelpExampleCli("waitfornewblock", "1000")
269 2 : + HelpExampleRpc("waitfornewblock", "1000")
270 : },
271 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
272 : {
273 0 : int timeout = 0;
274 0 : if (!request.params[0].isNull())
275 0 : timeout = request.params[0].getInt<int>();
276 :
277 0 : CUpdatedBlock block;
278 : {
279 0 : WAIT_LOCK(cs_blockchange, lock);
280 0 : block = latestblock;
281 0 : if(timeout)
282 0 : cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
283 : else
284 0 : cond_blockchange.wait(lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
285 0 : block = latestblock;
286 0 : }
287 0 : UniValue ret(UniValue::VOBJ);
288 0 : ret.pushKV("hash", block.hash.GetHex());
289 0 : ret.pushKV("height", block.height);
290 0 : return ret;
291 0 : },
292 : };
293 0 : }
294 :
295 2 : static RPCHelpMan waitforblock()
296 : {
297 4 : return RPCHelpMan{"waitforblock",
298 2 : "\nWaits for a specific new block and returns useful info about it.\n"
299 : "\nReturns the current block on timeout or exit.\n",
300 6 : {
301 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash to wait for."},
302 2 : {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
303 : },
304 2 : RPCResult{
305 2 : RPCResult::Type::OBJ, "", "",
306 6 : {
307 2 : {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
308 2 : {RPCResult::Type::NUM, "height", "Block height"},
309 : }},
310 2 : RPCExamples{
311 2 : HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\" 1000")
312 2 : + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000")
313 : },
314 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
315 : {
316 0 : int timeout = 0;
317 :
318 0 : uint256 hash(ParseHashV(request.params[0], "blockhash"));
319 :
320 0 : if (!request.params[1].isNull())
321 0 : timeout = request.params[1].getInt<int>();
322 :
323 0 : CUpdatedBlock block;
324 : {
325 0 : WAIT_LOCK(cs_blockchange, lock);
326 0 : if(timeout)
327 0 : cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning();});
328 : else
329 0 : cond_blockchange.wait(lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning(); });
330 0 : block = latestblock;
331 0 : }
332 :
333 0 : UniValue ret(UniValue::VOBJ);
334 0 : ret.pushKV("hash", block.hash.GetHex());
335 0 : ret.pushKV("height", block.height);
336 0 : return ret;
337 0 : },
338 : };
339 0 : }
340 :
341 2 : static RPCHelpMan waitforblockheight()
342 : {
343 4 : return RPCHelpMan{"waitforblockheight",
344 2 : "\nWaits for (at least) block height and returns the height and hash\n"
345 : "of the current tip.\n"
346 : "\nReturns the current block on timeout or exit.\n",
347 6 : {
348 2 : {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "Block height to wait for."},
349 2 : {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
350 : },
351 2 : RPCResult{
352 2 : RPCResult::Type::OBJ, "", "",
353 6 : {
354 2 : {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
355 2 : {RPCResult::Type::NUM, "height", "Block height"},
356 : }},
357 2 : RPCExamples{
358 2 : HelpExampleCli("waitforblockheight", "100 1000")
359 2 : + HelpExampleRpc("waitforblockheight", "100, 1000")
360 : },
361 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
362 : {
363 0 : int timeout = 0;
364 :
365 0 : int height = request.params[0].getInt<int>();
366 :
367 0 : if (!request.params[1].isNull())
368 0 : timeout = request.params[1].getInt<int>();
369 :
370 0 : CUpdatedBlock block;
371 : {
372 0 : WAIT_LOCK(cs_blockchange, lock);
373 0 : if(timeout)
374 0 : cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning();});
375 : else
376 0 : cond_blockchange.wait(lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning(); });
377 0 : block = latestblock;
378 0 : }
379 0 : UniValue ret(UniValue::VOBJ);
380 0 : ret.pushKV("hash", block.hash.GetHex());
381 0 : ret.pushKV("height", block.height);
382 0 : return ret;
383 0 : },
384 : };
385 0 : }
386 :
387 2 : static RPCHelpMan syncwithvalidationinterfacequeue()
388 : {
389 4 : return RPCHelpMan{"syncwithvalidationinterfacequeue",
390 2 : "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n",
391 2 : {},
392 2 : RPCResult{RPCResult::Type::NONE, "", ""},
393 2 : RPCExamples{
394 2 : HelpExampleCli("syncwithvalidationinterfacequeue","")
395 2 : + HelpExampleRpc("syncwithvalidationinterfacequeue","")
396 : },
397 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
398 : {
399 0 : SyncWithValidationInterfaceQueue();
400 0 : return UniValue::VNULL;
401 0 : },
402 : };
403 0 : }
404 :
405 2 : static RPCHelpMan getdifficulty()
406 : {
407 4 : return RPCHelpMan{"getdifficulty",
408 2 : "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n",
409 2 : {},
410 2 : RPCResult{
411 2 : RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum difficulty."},
412 2 : RPCExamples{
413 2 : HelpExampleCli("getdifficulty", "")
414 2 : + HelpExampleRpc("getdifficulty", "")
415 : },
416 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
417 : {
418 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
419 0 : LOCK(cs_main);
420 0 : return GetDifficulty(chainman.ActiveChain().Tip());
421 0 : },
422 : };
423 0 : }
424 :
425 2 : static RPCHelpMan getblockfrompeer()
426 : {
427 2 : return RPCHelpMan{
428 2 : "getblockfrompeer",
429 2 : "Attempt to fetch block from a given peer.\n\n"
430 : "We must have the header for this block, e.g. using submitheader.\n"
431 : "Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
432 : "Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
433 : "When a peer does not respond with a block, we will disconnect.\n"
434 : "Note: The block could be re-pruned as soon as it is received.\n\n"
435 : "Returns an empty JSON object if the request was successfully scheduled.",
436 6 : {
437 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
438 2 : {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
439 : },
440 2 : RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
441 2 : RPCExamples{
442 2 : HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
443 2 : + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
444 : },
445 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
446 : {
447 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
448 0 : ChainstateManager& chainman = EnsureChainman(node);
449 0 : PeerManager& peerman = EnsurePeerman(node);
450 :
451 0 : const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
452 0 : const NodeId peer_id{request.params[1].getInt<int64_t>()};
453 :
454 0 : const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
455 :
456 0 : if (!index) {
457 0 : throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
458 : }
459 :
460 : // Fetching blocks before the node has syncing past their height can prevent block files from
461 : // being pruned, so we avoid it if the node is in prune mode.
462 0 : if (chainman.m_blockman.IsPruneMode() && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
463 0 : throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer");
464 : }
465 :
466 0 : const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA);
467 0 : if (block_has_data) {
468 0 : throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
469 : }
470 :
471 0 : if (const auto err{peerman.FetchBlock(peer_id, *index)}) {
472 0 : throw JSONRPCError(RPC_MISC_ERROR, err.value());
473 : }
474 0 : return UniValue::VOBJ;
475 0 : },
476 : };
477 0 : }
478 :
479 2 : static RPCHelpMan getblockhash()
480 : {
481 4 : return RPCHelpMan{"getblockhash",
482 2 : "\nReturns hash of block in best-block-chain at height provided.\n",
483 4 : {
484 2 : {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"},
485 : },
486 2 : RPCResult{
487 2 : RPCResult::Type::STR_HEX, "", "The block hash"},
488 2 : RPCExamples{
489 2 : HelpExampleCli("getblockhash", "1000")
490 2 : + HelpExampleRpc("getblockhash", "1000")
491 : },
492 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
493 : {
494 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
495 0 : LOCK(cs_main);
496 0 : const CChain& active_chain = chainman.ActiveChain();
497 :
498 0 : int nHeight = request.params[0].getInt<int>();
499 0 : if (nHeight < 0 || nHeight > active_chain.Height())
500 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
501 :
502 0 : const CBlockIndex* pblockindex = active_chain[nHeight];
503 0 : return pblockindex->GetBlockHash().GetHex();
504 0 : },
505 : };
506 0 : }
507 :
508 2 : static RPCHelpMan getblockheader()
509 : {
510 4 : return RPCHelpMan{"getblockheader",
511 2 : "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n"
512 : "If verbose is true, returns an Object with information about blockheader <hash>.\n",
513 6 : {
514 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
515 2 : {"verbose", RPCArg::Type::BOOL, RPCArg::Default{true}, "true for a json object, false for the hex-encoded data"},
516 : },
517 6 : {
518 4 : RPCResult{"for verbose = true",
519 2 : RPCResult::Type::OBJ, "", "",
520 32 : {
521 2 : {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
522 2 : {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
523 2 : {RPCResult::Type::NUM, "height", "The block height or index"},
524 2 : {RPCResult::Type::NUM, "version", "The block version"},
525 2 : {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
526 2 : {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
527 2 : {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
528 2 : {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
529 2 : {RPCResult::Type::NUM, "nonce", "The nonce"},
530 2 : {RPCResult::Type::STR_HEX, "bits", "The bits"},
531 2 : {RPCResult::Type::NUM, "difficulty", "The difficulty"},
532 2 : {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"},
533 2 : {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
534 2 : {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
535 2 : {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
536 : }},
537 4 : RPCResult{"for verbose=false",
538 2 : RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
539 : },
540 2 : RPCExamples{
541 2 : HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
542 2 : + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
543 : },
544 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
545 : {
546 0 : uint256 hash(ParseHashV(request.params[0], "hash"));
547 :
548 0 : bool fVerbose = true;
549 0 : if (!request.params[1].isNull())
550 0 : fVerbose = request.params[1].get_bool();
551 :
552 : const CBlockIndex* pblockindex;
553 : const CBlockIndex* tip;
554 : {
555 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
556 0 : LOCK(cs_main);
557 0 : pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
558 0 : tip = chainman.ActiveChain().Tip();
559 0 : }
560 :
561 0 : if (!pblockindex) {
562 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
563 : }
564 :
565 0 : if (!fVerbose)
566 : {
567 0 : DataStream ssBlock{};
568 0 : ssBlock << pblockindex->GetBlockHeader();
569 0 : std::string strHex = HexStr(ssBlock);
570 0 : return strHex;
571 0 : }
572 :
573 0 : return blockheaderToJSON(tip, pblockindex);
574 0 : },
575 : };
576 0 : }
577 :
578 0 : static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex* pblockindex)
579 : {
580 0 : CBlock block;
581 : {
582 0 : LOCK(cs_main);
583 0 : if (blockman.IsBlockPruned(pblockindex)) {
584 0 : throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
585 : }
586 0 : }
587 :
588 0 : if (!blockman.ReadBlockFromDisk(block, *pblockindex)) {
589 : // Block not found on disk. This could be because we have the block
590 : // header in our index but not yet have the block or did not accept the
591 : // block. Or if the block was pruned right after we released the lock above.
592 0 : throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
593 : }
594 :
595 0 : return block;
596 0 : }
597 :
598 0 : static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblockindex)
599 : {
600 0 : CBlockUndo blockUndo;
601 :
602 : // The Genesis block does not have undo data
603 0 : if (pblockindex->nHeight == 0) return blockUndo;
604 :
605 : {
606 0 : LOCK(cs_main);
607 0 : if (blockman.IsBlockPruned(pblockindex)) {
608 0 : throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)");
609 : }
610 0 : }
611 :
612 0 : if (!blockman.UndoReadFromDisk(blockUndo, *pblockindex)) {
613 0 : throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
614 : }
615 :
616 0 : return blockUndo;
617 0 : }
618 :
619 2 : const RPCResult getblock_vin{
620 2 : RPCResult::Type::ARR, "vin", "",
621 4 : {
622 4 : {RPCResult::Type::OBJ, "", "",
623 6 : {
624 2 : {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"},
625 4 : {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)",
626 10 : {
627 2 : {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
628 2 : {RPCResult::Type::NUM, "height", "The height of the prevout"},
629 2 : {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
630 4 : {RPCResult::Type::OBJ, "scriptPubKey", "",
631 12 : {
632 2 : {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
633 2 : {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
634 2 : {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
635 2 : {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
636 2 : {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
637 : }},
638 : }},
639 : }},
640 : }
641 : };
642 :
643 2 : static RPCHelpMan getblock()
644 : {
645 4 : return RPCHelpMan{"getblock",
646 2 : "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
647 : "If verbosity is 1, returns an Object with information about block <hash>.\n"
648 : "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n"
649 : "If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n",
650 6 : {
651 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
652 2 : {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs",
653 6 : RPCArgOptions{.skip_type_check = true}},
654 : },
655 10 : {
656 4 : RPCResult{"for verbosity = 0",
657 2 : RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
658 4 : RPCResult{"for verbosity = 1",
659 2 : RPCResult::Type::OBJ, "", "",
660 40 : {
661 2 : {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
662 2 : {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
663 2 : {RPCResult::Type::NUM, "size", "The block size"},
664 2 : {RPCResult::Type::NUM, "strippedsize", "The block size excluding witness data"},
665 2 : {RPCResult::Type::NUM, "weight", "The block weight as defined in BIP 141"},
666 2 : {RPCResult::Type::NUM, "height", "The block height or index"},
667 2 : {RPCResult::Type::NUM, "version", "The block version"},
668 2 : {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
669 2 : {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
670 4 : {RPCResult::Type::ARR, "tx", "The transaction ids",
671 2 : {{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
672 2 : {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
673 2 : {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
674 2 : {RPCResult::Type::NUM, "nonce", "The nonce"},
675 2 : {RPCResult::Type::STR_HEX, "bits", "The bits"},
676 2 : {RPCResult::Type::NUM, "difficulty", "The difficulty"},
677 2 : {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"},
678 2 : {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
679 2 : {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
680 2 : {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
681 : }},
682 4 : RPCResult{"for verbosity = 2",
683 2 : RPCResult::Type::OBJ, "", "",
684 6 : {
685 2 : {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
686 4 : {RPCResult::Type::ARR, "tx", "",
687 4 : {
688 4 : {RPCResult::Type::OBJ, "", "",
689 6 : {
690 2 : {RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
691 2 : {RPCResult::Type::NUM, "fee", "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
692 : }},
693 : }},
694 : }},
695 4 : RPCResult{"for verbosity = 3",
696 2 : RPCResult::Type::OBJ, "", "",
697 6 : {
698 2 : {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"},
699 4 : {RPCResult::Type::ARR, "tx", "",
700 4 : {
701 4 : {RPCResult::Type::OBJ, "", "",
702 2 : {
703 2 : getblock_vin,
704 : }},
705 : }},
706 : }},
707 : },
708 2 : RPCExamples{
709 2 : HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
710 2 : + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
711 : },
712 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
713 : {
714 0 : uint256 hash(ParseHashV(request.params[0], "blockhash"));
715 :
716 0 : int verbosity = 1;
717 0 : if (!request.params[1].isNull()) {
718 0 : if (request.params[1].isBool()) {
719 0 : verbosity = request.params[1].get_bool() ? 1 : 0;
720 0 : } else {
721 0 : verbosity = request.params[1].getInt<int>();
722 : }
723 0 : }
724 :
725 : const CBlockIndex* pblockindex;
726 : const CBlockIndex* tip;
727 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
728 : {
729 0 : LOCK(cs_main);
730 0 : pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
731 0 : tip = chainman.ActiveChain().Tip();
732 :
733 0 : if (!pblockindex) {
734 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
735 : }
736 0 : }
737 :
738 0 : const CBlock block{GetBlockChecked(chainman.m_blockman, pblockindex)};
739 :
740 0 : if (verbosity <= 0)
741 : {
742 0 : CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
743 0 : ssBlock << block;
744 0 : std::string strHex = HexStr(ssBlock);
745 0 : return strHex;
746 0 : }
747 :
748 : TxVerbosity tx_verbosity;
749 0 : if (verbosity == 1) {
750 0 : tx_verbosity = TxVerbosity::SHOW_TXID;
751 0 : } else if (verbosity == 2) {
752 0 : tx_verbosity = TxVerbosity::SHOW_DETAILS;
753 0 : } else {
754 0 : tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
755 : }
756 :
757 0 : return blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity);
758 0 : },
759 : };
760 0 : }
761 :
762 2 : static RPCHelpMan pruneblockchain()
763 : {
764 4 : return RPCHelpMan{"pruneblockchain", "",
765 4 : {
766 2 : {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or to a " + UNIX_EPOCH_TIME + "\n"
767 : " to prune blocks whose block time is at least 2 hours older than the provided timestamp."},
768 : },
769 2 : RPCResult{
770 2 : RPCResult::Type::NUM, "", "Height of the last block pruned"},
771 2 : RPCExamples{
772 2 : HelpExampleCli("pruneblockchain", "1000")
773 2 : + HelpExampleRpc("pruneblockchain", "1000")
774 : },
775 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
776 : {
777 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
778 0 : if (!chainman.m_blockman.IsPruneMode()) {
779 0 : throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode.");
780 : }
781 :
782 0 : LOCK(cs_main);
783 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
784 0 : CChain& active_chain = active_chainstate.m_chain;
785 :
786 0 : int heightParam = request.params[0].getInt<int>();
787 0 : if (heightParam < 0) {
788 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height.");
789 : }
790 :
791 : // Height value more than a billion is too high to be a block height, and
792 : // too low to be a block time (corresponds to timestamp from Sep 2001).
793 0 : if (heightParam > 1000000000) {
794 : // Add a 2 hour buffer to include blocks which might have had old timestamps
795 0 : const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
796 0 : if (!pindex) {
797 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp.");
798 : }
799 0 : heightParam = pindex->nHeight;
800 0 : }
801 :
802 0 : unsigned int height = (unsigned int) heightParam;
803 0 : unsigned int chainHeight = (unsigned int) active_chain.Height();
804 0 : if (chainHeight < chainman.GetParams().PruneAfterHeight()) {
805 0 : throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning.");
806 0 : } else if (height > chainHeight) {
807 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height.");
808 0 : } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
809 0 : LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n");
810 0 : height = chainHeight - MIN_BLOCKS_TO_KEEP;
811 0 : }
812 :
813 0 : PruneBlockFilesManual(active_chainstate, height);
814 0 : const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())};
815 0 : return block.nStatus & BLOCK_HAVE_DATA ? active_chainstate.m_blockman.GetFirstStoredBlock(block)->nHeight - 1 : block.nHeight;
816 0 : },
817 : };
818 0 : }
819 :
820 0 : CoinStatsHashType ParseHashType(const std::string& hash_type_input)
821 : {
822 0 : if (hash_type_input == "hash_serialized_2") {
823 0 : return CoinStatsHashType::HASH_SERIALIZED;
824 0 : } else if (hash_type_input == "muhash") {
825 0 : return CoinStatsHashType::MUHASH;
826 0 : } else if (hash_type_input == "none") {
827 0 : return CoinStatsHashType::NONE;
828 : } else {
829 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("'%s' is not a valid hash_type", hash_type_input));
830 : }
831 0 : }
832 :
833 : /**
834 : * Calculate statistics about the unspent transaction output set
835 : *
836 : * @param[in] index_requested Signals if the coinstatsindex should be used (when available).
837 : */
838 0 : static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman,
839 : kernel::CoinStatsHashType hash_type,
840 : const std::function<void()>& interruption_point = {},
841 : const CBlockIndex* pindex = nullptr,
842 : bool index_requested = true)
843 : {
844 : // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
845 0 : if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) {
846 0 : if (pindex) {
847 0 : return g_coin_stats_index->LookUpStats(*pindex);
848 : } else {
849 0 : CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())));
850 0 : return g_coin_stats_index->LookUpStats(block_index);
851 : }
852 : }
853 :
854 : // If the coinstats index isn't requested or is otherwise not usable, the
855 : // pindex should either be null or equal to the view's best block. This is
856 : // because without the coinstats index we can only get coinstats about the
857 : // best block.
858 0 : CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock());
859 :
860 0 : return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
861 0 : }
862 :
863 2 : static RPCHelpMan gettxoutsetinfo()
864 : {
865 4 : return RPCHelpMan{"gettxoutsetinfo",
866 2 : "\nReturns statistics about the unspent transaction output set.\n"
867 : "Note this call may take some time if you are not using coinstatsindex.\n",
868 8 : {
869 2 : {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
870 4 : {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).",
871 6 : RPCArgOptions{
872 : .skip_type_check = true,
873 2 : .type_str = {"", "string or numeric"},
874 : }},
875 2 : {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
876 : },
877 2 : RPCResult{
878 2 : RPCResult::Type::OBJ, "", "",
879 24 : {
880 2 : {RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"},
881 2 : {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"},
882 2 : {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
883 2 : {RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
884 2 : {RPCResult::Type::STR_HEX, "hash_serialized_2", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
885 2 : {RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
886 2 : {RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
887 2 : {RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
888 2 : {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
889 2 : {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", /*optional=*/true, "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
890 4 : {RPCResult::Type::OBJ, "block_info", /*optional=*/true, "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
891 12 : {
892 2 : {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
893 2 : {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
894 2 : {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
895 2 : {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
896 4 : {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
897 10 : {
898 2 : {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
899 2 : {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
900 2 : {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
901 2 : {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
902 : }}
903 : }},
904 : }},
905 2 : RPCExamples{
906 4 : HelpExampleCli("gettxoutsetinfo", "") +
907 4 : HelpExampleCli("gettxoutsetinfo", R"("none")") +
908 4 : HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
909 4 : HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
910 4 : HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") +
911 4 : HelpExampleRpc("gettxoutsetinfo", "") +
912 4 : HelpExampleRpc("gettxoutsetinfo", R"("none")") +
913 4 : HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
914 2 : HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")
915 : },
916 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
917 : {
918 0 : UniValue ret(UniValue::VOBJ);
919 :
920 0 : const CBlockIndex* pindex{nullptr};
921 0 : const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
922 0 : bool index_requested = request.params[2].isNull() || request.params[2].get_bool();
923 :
924 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
925 0 : ChainstateManager& chainman = EnsureChainman(node);
926 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
927 0 : active_chainstate.ForceFlushStateToDisk();
928 :
929 : CCoinsView* coins_view;
930 : BlockManager* blockman;
931 : {
932 0 : LOCK(::cs_main);
933 0 : coins_view = &active_chainstate.CoinsDB();
934 0 : blockman = &active_chainstate.m_blockman;
935 0 : pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
936 0 : }
937 :
938 0 : if (!request.params[1].isNull()) {
939 0 : if (!g_coin_stats_index) {
940 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
941 : }
942 :
943 0 : if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
944 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block");
945 : }
946 :
947 0 : if (!index_requested) {
948 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block");
949 : }
950 0 : pindex = ParseHashOrHeight(request.params[1], chainman);
951 0 : }
952 :
953 0 : if (index_requested && g_coin_stats_index) {
954 0 : if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
955 0 : const IndexSummary summary{g_coin_stats_index->GetSummary()};
956 :
957 : // If a specific block was requested and the index has already synced past that height, we can return the
958 : // data already even though the index is not fully synced yet.
959 0 : if (pindex->nHeight > summary.best_block_height) {
960 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
961 : }
962 0 : }
963 0 : }
964 :
965 0 : const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
966 0 : if (maybe_stats.has_value()) {
967 0 : const CCoinsStats& stats = maybe_stats.value();
968 0 : ret.pushKV("height", (int64_t)stats.nHeight);
969 0 : ret.pushKV("bestblock", stats.hashBlock.GetHex());
970 0 : ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
971 0 : ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
972 0 : if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
973 0 : ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
974 0 : }
975 0 : if (hash_type == CoinStatsHashType::MUHASH) {
976 0 : ret.pushKV("muhash", stats.hashSerialized.GetHex());
977 0 : }
978 0 : CHECK_NONFATAL(stats.total_amount.has_value());
979 0 : ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value()));
980 0 : if (!stats.index_used) {
981 0 : ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
982 0 : ret.pushKV("disk_size", stats.nDiskSize);
983 0 : } else {
984 0 : ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
985 :
986 0 : CCoinsStats prev_stats{};
987 0 : if (pindex->nHeight > 0) {
988 0 : const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested);
989 0 : if (!maybe_prev_stats) {
990 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
991 : }
992 0 : prev_stats = maybe_prev_stats.value();
993 0 : }
994 :
995 0 : UniValue block_info(UniValue::VOBJ);
996 0 : block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
997 0 : block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
998 0 : block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
999 0 : block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
1000 :
1001 0 : UniValue unspendables(UniValue::VOBJ);
1002 0 : unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
1003 0 : unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
1004 0 : unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
1005 0 : unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
1006 0 : block_info.pushKV("unspendables", unspendables);
1007 :
1008 0 : ret.pushKV("block_info", block_info);
1009 0 : }
1010 0 : } else {
1011 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1012 : }
1013 0 : return ret;
1014 0 : },
1015 : };
1016 0 : }
1017 :
1018 2 : static RPCHelpMan gettxout()
1019 : {
1020 4 : return RPCHelpMan{"gettxout",
1021 2 : "\nReturns details about an unspent transaction output.\n",
1022 8 : {
1023 2 : {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
1024 2 : {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
1025 2 : {"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."},
1026 : },
1027 6 : {
1028 2 : RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "", ""},
1029 12 : RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", {
1030 2 : {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
1031 2 : {RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
1032 2 : {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
1033 12 : {RPCResult::Type::OBJ, "scriptPubKey", "", {
1034 2 : {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
1035 2 : {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
1036 2 : {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
1037 2 : {RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
1038 2 : {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
1039 : }},
1040 2 : {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
1041 : }},
1042 : },
1043 2 : RPCExamples{
1044 : "\nGet unspent transactions\n"
1045 2 : + HelpExampleCli("listunspent", "") +
1046 : "\nView the details\n"
1047 2 : + HelpExampleCli("gettxout", "\"txid\" 1") +
1048 : "\nAs a JSON-RPC call\n"
1049 2 : + HelpExampleRpc("gettxout", "\"txid\", 1")
1050 : },
1051 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1052 : {
1053 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
1054 0 : ChainstateManager& chainman = EnsureChainman(node);
1055 0 : LOCK(cs_main);
1056 :
1057 0 : UniValue ret(UniValue::VOBJ);
1058 :
1059 0 : uint256 hash(ParseHashV(request.params[0], "txid"));
1060 0 : COutPoint out{hash, request.params[1].getInt<uint32_t>()};
1061 0 : bool fMempool = true;
1062 0 : if (!request.params[2].isNull())
1063 0 : fMempool = request.params[2].get_bool();
1064 :
1065 0 : Coin coin;
1066 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
1067 0 : CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
1068 :
1069 0 : if (fMempool) {
1070 0 : const CTxMemPool& mempool = EnsureMemPool(node);
1071 0 : LOCK(mempool.cs);
1072 0 : CCoinsViewMemPool view(coins_view, mempool);
1073 0 : if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
1074 0 : return UniValue::VNULL;
1075 : }
1076 0 : } else {
1077 0 : if (!coins_view->GetCoin(out, coin)) {
1078 0 : return UniValue::VNULL;
1079 : }
1080 : }
1081 :
1082 0 : const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
1083 0 : ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
1084 0 : if (coin.nHeight == MEMPOOL_HEIGHT) {
1085 0 : ret.pushKV("confirmations", 0);
1086 0 : } else {
1087 0 : ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1));
1088 : }
1089 0 : ret.pushKV("value", ValueFromAmount(coin.out.nValue));
1090 0 : UniValue o(UniValue::VOBJ);
1091 0 : ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
1092 0 : ret.pushKV("scriptPubKey", o);
1093 0 : ret.pushKV("coinbase", (bool)coin.fCoinBase);
1094 :
1095 0 : return ret;
1096 0 : },
1097 : };
1098 0 : }
1099 :
1100 2 : static RPCHelpMan verifychain()
1101 : {
1102 4 : return RPCHelpMan{"verifychain",
1103 2 : "\nVerifies blockchain database.\n",
1104 6 : {
1105 4 : {"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
1106 2 : strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))},
1107 2 : {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
1108 : },
1109 2 : RPCResult{
1110 2 : RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug.log for reason."},
1111 2 : RPCExamples{
1112 2 : HelpExampleCli("verifychain", "")
1113 2 : + HelpExampleRpc("verifychain", "")
1114 : },
1115 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1116 : {
1117 0 : const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()};
1118 0 : const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()};
1119 :
1120 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1121 0 : LOCK(cs_main);
1122 :
1123 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
1124 0 : return CVerifyDB(chainman.GetNotifications()).VerifyDB(
1125 0 : active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS;
1126 0 : },
1127 : };
1128 0 : }
1129 :
1130 0 : static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep)
1131 : {
1132 : // For buried deployments.
1133 :
1134 0 : if (!DeploymentEnabled(chainman, dep)) return;
1135 :
1136 0 : UniValue rv(UniValue::VOBJ);
1137 0 : rv.pushKV("type", "buried");
1138 : // getdeploymentinfo reports the softfork as active from when the chain height is
1139 : // one below the activation height
1140 0 : rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep));
1141 0 : rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep));
1142 0 : softforks.pushKV(DeploymentName(dep), rv);
1143 0 : }
1144 :
1145 0 : static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id)
1146 : {
1147 : // For BIP9 deployments.
1148 :
1149 0 : if (!DeploymentEnabled(chainman, id)) return;
1150 0 : if (blockindex == nullptr) return;
1151 :
1152 0 : auto get_state_name = [](const ThresholdState state) -> std::string {
1153 0 : switch (state) {
1154 0 : case ThresholdState::DEFINED: return "defined";
1155 0 : case ThresholdState::STARTED: return "started";
1156 0 : case ThresholdState::LOCKED_IN: return "locked_in";
1157 0 : case ThresholdState::ACTIVE: return "active";
1158 0 : case ThresholdState::FAILED: return "failed";
1159 : }
1160 0 : return "invalid";
1161 0 : };
1162 :
1163 0 : UniValue bip9(UniValue::VOBJ);
1164 :
1165 0 : const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id);
1166 0 : const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id);
1167 :
1168 0 : const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
1169 :
1170 : // BIP9 parameters
1171 0 : if (has_signal) {
1172 0 : bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit);
1173 0 : }
1174 0 : bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime);
1175 0 : bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout);
1176 0 : bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height);
1177 :
1178 : // BIP9 status
1179 0 : bip9.pushKV("status", get_state_name(current_state));
1180 0 : bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id));
1181 0 : bip9.pushKV("status_next", get_state_name(next_state));
1182 :
1183 : // BIP9 signalling status, if applicable
1184 0 : if (has_signal) {
1185 0 : UniValue statsUV(UniValue::VOBJ);
1186 0 : std::vector<bool> signals;
1187 0 : BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals);
1188 0 : statsUV.pushKV("period", statsStruct.period);
1189 0 : statsUV.pushKV("elapsed", statsStruct.elapsed);
1190 0 : statsUV.pushKV("count", statsStruct.count);
1191 0 : if (ThresholdState::LOCKED_IN != current_state) {
1192 0 : statsUV.pushKV("threshold", statsStruct.threshold);
1193 0 : statsUV.pushKV("possible", statsStruct.possible);
1194 0 : }
1195 0 : bip9.pushKV("statistics", statsUV);
1196 :
1197 0 : std::string sig;
1198 0 : sig.reserve(signals.size());
1199 0 : for (const bool s : signals) {
1200 0 : sig.push_back(s ? '#' : '-');
1201 : }
1202 0 : bip9.pushKV("signalling", sig);
1203 0 : }
1204 :
1205 0 : UniValue rv(UniValue::VOBJ);
1206 0 : rv.pushKV("type", "bip9");
1207 0 : if (ThresholdState::ACTIVE == next_state) {
1208 0 : rv.pushKV("height", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id));
1209 0 : }
1210 0 : rv.pushKV("active", ThresholdState::ACTIVE == next_state);
1211 0 : rv.pushKV("bip9", bip9);
1212 :
1213 0 : softforks.pushKV(DeploymentName(id), rv);
1214 0 : }
1215 :
1216 : // used by rest.cpp:rest_chaininfo, so cannot be static
1217 2 : RPCHelpMan getblockchaininfo()
1218 : {
1219 4 : return RPCHelpMan{"getblockchaininfo",
1220 2 : "Returns an object containing various state info regarding blockchain processing.\n",
1221 2 : {},
1222 2 : RPCResult{
1223 2 : RPCResult::Type::OBJ, "", "",
1224 34 : {
1225 2 : {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
1226 2 : {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
1227 2 : {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
1228 2 : {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
1229 2 : {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
1230 2 : {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
1231 2 : {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
1232 2 : {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
1233 2 : {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
1234 2 : {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
1235 2 : {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
1236 2 : {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
1237 2 : {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"},
1238 2 : {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
1239 2 : {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
1240 2 : {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
1241 : }},
1242 2 : RPCExamples{
1243 2 : HelpExampleCli("getblockchaininfo", "")
1244 2 : + HelpExampleRpc("getblockchaininfo", "")
1245 : },
1246 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1247 : {
1248 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1249 0 : LOCK(cs_main);
1250 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
1251 :
1252 0 : const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
1253 0 : const int height{tip.nHeight};
1254 0 : UniValue obj(UniValue::VOBJ);
1255 0 : obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
1256 0 : obj.pushKV("blocks", height);
1257 0 : obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
1258 0 : obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
1259 0 : obj.pushKV("difficulty", GetDifficulty(&tip));
1260 0 : obj.pushKV("time", tip.GetBlockTime());
1261 0 : obj.pushKV("mediantime", tip.GetMedianTimePast());
1262 0 : obj.pushKV("verificationprogress", GuessVerificationProgress(chainman.GetParams().TxData(), &tip));
1263 0 : obj.pushKV("initialblockdownload", chainman.IsInitialBlockDownload());
1264 0 : obj.pushKV("chainwork", tip.nChainWork.GetHex());
1265 0 : obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
1266 0 : obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
1267 0 : if (chainman.m_blockman.IsPruneMode()) {
1268 0 : bool has_tip_data = tip.nStatus & BLOCK_HAVE_DATA;
1269 0 : obj.pushKV("pruneheight", has_tip_data ? chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight : tip.nHeight + 1);
1270 :
1271 0 : const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
1272 0 : obj.pushKV("automatic_pruning", automatic_pruning);
1273 0 : if (automatic_pruning) {
1274 0 : obj.pushKV("prune_target_size", chainman.m_blockman.GetPruneTarget());
1275 0 : }
1276 0 : }
1277 :
1278 0 : obj.pushKV("warnings", GetWarnings(false).original);
1279 0 : return obj;
1280 0 : },
1281 : };
1282 0 : }
1283 :
1284 : namespace {
1285 10 : const std::vector<RPCResult> RPCHelpForDeployment{
1286 2 : {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1287 2 : {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
1288 2 : {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
1289 4 : {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
1290 20 : {
1291 2 : {RPCResult::Type::NUM, "bit", /*optional=*/true, "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"},
1292 2 : {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
1293 2 : {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1294 2 : {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1295 2 : {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
1296 2 : {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1297 2 : {RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
1298 4 : {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
1299 12 : {
1300 2 : {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
1301 2 : {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
1302 2 : {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
1303 2 : {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
1304 2 : {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
1305 : }},
1306 2 : {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"},
1307 : }},
1308 : };
1309 :
1310 0 : UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman)
1311 : {
1312 0 : UniValue softforks(UniValue::VOBJ);
1313 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB);
1314 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG);
1315 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV);
1316 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV);
1317 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
1318 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
1319 0 : SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
1320 0 : return softforks;
1321 0 : }
1322 : } // anon namespace
1323 :
1324 2 : RPCHelpMan getdeploymentinfo()
1325 : {
1326 4 : return RPCHelpMan{"getdeploymentinfo",
1327 2 : "Returns an object containing various state info regarding deployments of consensus changes.",
1328 4 : {
1329 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"},
1330 : },
1331 2 : RPCResult{
1332 8 : RPCResult::Type::OBJ, "", "", {
1333 2 : {RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
1334 2 : {RPCResult::Type::NUM, "height", "requested block height (or tip)"},
1335 4 : {RPCResult::Type::OBJ_DYN, "deployments", "", {
1336 2 : {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
1337 : }},
1338 : }
1339 : },
1340 2 : RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") },
1341 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1342 : {
1343 0 : const ChainstateManager& chainman = EnsureAnyChainman(request.context);
1344 0 : LOCK(cs_main);
1345 0 : const Chainstate& active_chainstate = chainman.ActiveChainstate();
1346 :
1347 : const CBlockIndex* blockindex;
1348 0 : if (request.params[0].isNull()) {
1349 0 : blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
1350 0 : } else {
1351 0 : const uint256 hash(ParseHashV(request.params[0], "blockhash"));
1352 0 : blockindex = chainman.m_blockman.LookupBlockIndex(hash);
1353 0 : if (!blockindex) {
1354 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1355 : }
1356 : }
1357 :
1358 0 : UniValue deploymentinfo(UniValue::VOBJ);
1359 0 : deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
1360 0 : deploymentinfo.pushKV("height", blockindex->nHeight);
1361 0 : deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman));
1362 0 : return deploymentinfo;
1363 0 : },
1364 : };
1365 0 : }
1366 :
1367 : /** Comparison function for sorting the getchaintips heads. */
1368 : struct CompareBlocksByHeight
1369 : {
1370 0 : bool operator()(const CBlockIndex* a, const CBlockIndex* b) const
1371 : {
1372 : /* Make sure that unequal blocks with the same height do not compare
1373 : equal. Use the pointers themselves to make a distinction. */
1374 :
1375 0 : if (a->nHeight != b->nHeight)
1376 0 : return (a->nHeight > b->nHeight);
1377 :
1378 0 : return a < b;
1379 0 : }
1380 : };
1381 :
1382 2 : static RPCHelpMan getchaintips()
1383 : {
1384 4 : return RPCHelpMan{"getchaintips",
1385 2 : "Return information about all known tips in the block tree,"
1386 : " including the main chain as well as orphaned branches.\n",
1387 2 : {},
1388 2 : RPCResult{
1389 2 : RPCResult::Type::ARR, "", "",
1390 4 : {{RPCResult::Type::OBJ, "", "",
1391 10 : {
1392 2 : {RPCResult::Type::NUM, "height", "height of the chain tip"},
1393 2 : {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
1394 2 : {RPCResult::Type::NUM, "branchlen", "zero for main chain, otherwise length of branch connecting the tip to the main chain"},
1395 2 : {RPCResult::Type::STR, "status", "status of the chain, \"active\" for the main chain\n"
1396 : "Possible values for status:\n"
1397 : "1. \"invalid\" This branch contains at least one invalid block\n"
1398 : "2. \"headers-only\" Not all blocks for this branch are available, but the headers are valid\n"
1399 : "3. \"valid-headers\" All blocks are available for this branch, but they were never fully validated\n"
1400 : "4. \"valid-fork\" This branch is not part of the active chain, but is fully validated\n"
1401 : "5. \"active\" This is the tip of the active main chain, which is certainly valid"},
1402 : }}}},
1403 2 : RPCExamples{
1404 2 : HelpExampleCli("getchaintips", "")
1405 2 : + HelpExampleRpc("getchaintips", "")
1406 : },
1407 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1408 : {
1409 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1410 0 : LOCK(cs_main);
1411 0 : CChain& active_chain = chainman.ActiveChain();
1412 :
1413 : /*
1414 : * Idea: The set of chain tips is the active chain tip, plus orphan blocks which do not have another orphan building off of them.
1415 : * Algorithm:
1416 : * - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
1417 : * - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
1418 : * - Add the active chain tip
1419 : */
1420 0 : std::set<const CBlockIndex*, CompareBlocksByHeight> setTips;
1421 0 : std::set<const CBlockIndex*> setOrphans;
1422 0 : std::set<const CBlockIndex*> setPrevs;
1423 :
1424 0 : for (const auto& [_, block_index] : chainman.BlockIndex()) {
1425 0 : if (!active_chain.Contains(&block_index)) {
1426 0 : setOrphans.insert(&block_index);
1427 0 : setPrevs.insert(block_index.pprev);
1428 0 : }
1429 : }
1430 :
1431 0 : for (std::set<const CBlockIndex*>::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) {
1432 0 : if (setPrevs.erase(*it) == 0) {
1433 0 : setTips.insert(*it);
1434 0 : }
1435 0 : }
1436 :
1437 : // Always report the currently active tip.
1438 0 : setTips.insert(active_chain.Tip());
1439 :
1440 : /* Construct the output array. */
1441 0 : UniValue res(UniValue::VARR);
1442 0 : for (const CBlockIndex* block : setTips) {
1443 0 : UniValue obj(UniValue::VOBJ);
1444 0 : obj.pushKV("height", block->nHeight);
1445 0 : obj.pushKV("hash", block->phashBlock->GetHex());
1446 :
1447 0 : const int branchLen = block->nHeight - active_chain.FindFork(block)->nHeight;
1448 0 : obj.pushKV("branchlen", branchLen);
1449 :
1450 0 : std::string status;
1451 0 : if (active_chain.Contains(block)) {
1452 : // This block is part of the currently active chain.
1453 0 : status = "active";
1454 0 : } else if (block->nStatus & BLOCK_FAILED_MASK) {
1455 : // This block or one of its ancestors is invalid.
1456 0 : status = "invalid";
1457 0 : } else if (!block->HaveTxsDownloaded()) {
1458 : // This block cannot be connected because full block data for it or one of its parents is missing.
1459 0 : status = "headers-only";
1460 0 : } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) {
1461 : // This block is fully validated, but no longer part of the active chain. It was probably the active block once, but was reorganized.
1462 0 : status = "valid-fork";
1463 0 : } else if (block->IsValid(BLOCK_VALID_TREE)) {
1464 : // The headers for this block are valid, but it has not been validated. It was probably never part of the most-work chain.
1465 0 : status = "valid-headers";
1466 0 : } else {
1467 : // No clue.
1468 0 : status = "unknown";
1469 : }
1470 0 : obj.pushKV("status", status);
1471 :
1472 0 : res.push_back(obj);
1473 0 : }
1474 :
1475 0 : return res;
1476 0 : },
1477 : };
1478 0 : }
1479 :
1480 2 : static RPCHelpMan preciousblock()
1481 : {
1482 4 : return RPCHelpMan{"preciousblock",
1483 2 : "\nTreats a block as if it were received before others with the same work.\n"
1484 : "\nA later preciousblock call can override the effect of an earlier one.\n"
1485 : "\nThe effects of preciousblock are not retained across restarts.\n",
1486 4 : {
1487 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as precious"},
1488 : },
1489 2 : RPCResult{RPCResult::Type::NONE, "", ""},
1490 2 : RPCExamples{
1491 2 : HelpExampleCli("preciousblock", "\"blockhash\"")
1492 2 : + HelpExampleRpc("preciousblock", "\"blockhash\"")
1493 : },
1494 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1495 : {
1496 0 : uint256 hash(ParseHashV(request.params[0], "blockhash"));
1497 : CBlockIndex* pblockindex;
1498 :
1499 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1500 : {
1501 0 : LOCK(cs_main);
1502 0 : pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
1503 0 : if (!pblockindex) {
1504 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1505 : }
1506 0 : }
1507 :
1508 0 : BlockValidationState state;
1509 0 : chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
1510 :
1511 0 : if (!state.IsValid()) {
1512 0 : throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
1513 : }
1514 :
1515 0 : return UniValue::VNULL;
1516 0 : },
1517 : };
1518 0 : }
1519 :
1520 2 : static RPCHelpMan invalidateblock()
1521 : {
1522 4 : return RPCHelpMan{"invalidateblock",
1523 2 : "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n",
1524 4 : {
1525 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"},
1526 : },
1527 2 : RPCResult{RPCResult::Type::NONE, "", ""},
1528 2 : RPCExamples{
1529 2 : HelpExampleCli("invalidateblock", "\"blockhash\"")
1530 2 : + HelpExampleRpc("invalidateblock", "\"blockhash\"")
1531 : },
1532 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1533 : {
1534 0 : uint256 hash(ParseHashV(request.params[0], "blockhash"));
1535 0 : BlockValidationState state;
1536 :
1537 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1538 : CBlockIndex* pblockindex;
1539 : {
1540 0 : LOCK(cs_main);
1541 0 : pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
1542 0 : if (!pblockindex) {
1543 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1544 : }
1545 0 : }
1546 0 : chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
1547 :
1548 0 : if (state.IsValid()) {
1549 0 : chainman.ActiveChainstate().ActivateBestChain(state);
1550 0 : }
1551 :
1552 0 : if (!state.IsValid()) {
1553 0 : throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
1554 : }
1555 :
1556 0 : return UniValue::VNULL;
1557 0 : },
1558 : };
1559 0 : }
1560 :
1561 2 : static RPCHelpMan reconsiderblock()
1562 : {
1563 4 : return RPCHelpMan{"reconsiderblock",
1564 2 : "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n"
1565 : "This can be used to undo the effects of invalidateblock.\n",
1566 4 : {
1567 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to reconsider"},
1568 : },
1569 2 : RPCResult{RPCResult::Type::NONE, "", ""},
1570 2 : RPCExamples{
1571 2 : HelpExampleCli("reconsiderblock", "\"blockhash\"")
1572 2 : + HelpExampleRpc("reconsiderblock", "\"blockhash\"")
1573 : },
1574 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1575 : {
1576 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1577 0 : uint256 hash(ParseHashV(request.params[0], "blockhash"));
1578 :
1579 : {
1580 0 : LOCK(cs_main);
1581 0 : CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
1582 0 : if (!pblockindex) {
1583 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1584 : }
1585 :
1586 0 : chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
1587 0 : }
1588 :
1589 0 : BlockValidationState state;
1590 0 : chainman.ActiveChainstate().ActivateBestChain(state);
1591 :
1592 0 : if (!state.IsValid()) {
1593 0 : throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
1594 : }
1595 :
1596 0 : return UniValue::VNULL;
1597 0 : },
1598 : };
1599 0 : }
1600 :
1601 2 : static RPCHelpMan getchaintxstats()
1602 : {
1603 4 : return RPCHelpMan{"getchaintxstats",
1604 2 : "\nCompute statistics about the total number and rate of transactions in the chain.\n",
1605 6 : {
1606 2 : {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"}, "Size of the window in number of blocks"},
1607 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"chain tip"}, "The hash of the block that ends the window."},
1608 : },
1609 2 : RPCResult{
1610 2 : RPCResult::Type::OBJ, "", "",
1611 18 : {
1612 2 : {RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME},
1613 2 : {RPCResult::Type::NUM, "txcount", "The total number of transactions in the chain up to that point"},
1614 2 : {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
1615 2 : {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
1616 2 : {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
1617 2 : {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"},
1618 2 : {RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
1619 2 : {RPCResult::Type::NUM, "txrate", /*optional=*/true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"},
1620 : }},
1621 2 : RPCExamples{
1622 2 : HelpExampleCli("getchaintxstats", "")
1623 2 : + HelpExampleRpc("getchaintxstats", "2016")
1624 : },
1625 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1626 : {
1627 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1628 : const CBlockIndex* pindex;
1629 0 : int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month
1630 :
1631 0 : if (request.params[1].isNull()) {
1632 0 : LOCK(cs_main);
1633 0 : pindex = chainman.ActiveChain().Tip();
1634 0 : } else {
1635 0 : uint256 hash(ParseHashV(request.params[1], "blockhash"));
1636 0 : LOCK(cs_main);
1637 0 : pindex = chainman.m_blockman.LookupBlockIndex(hash);
1638 0 : if (!pindex) {
1639 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1640 : }
1641 0 : if (!chainman.ActiveChain().Contains(pindex)) {
1642 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");
1643 : }
1644 0 : }
1645 :
1646 0 : CHECK_NONFATAL(pindex != nullptr);
1647 :
1648 0 : if (request.params[0].isNull()) {
1649 0 : blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
1650 0 : } else {
1651 0 : blockcount = request.params[0].getInt<int>();
1652 :
1653 0 : if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) {
1654 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1");
1655 : }
1656 : }
1657 :
1658 0 : const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
1659 0 : const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
1660 0 : const int nTxDiff = pindex->nChainTx - past_block.nChainTx;
1661 :
1662 0 : UniValue ret(UniValue::VOBJ);
1663 0 : ret.pushKV("time", (int64_t)pindex->nTime);
1664 0 : ret.pushKV("txcount", (int64_t)pindex->nChainTx);
1665 0 : ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
1666 0 : ret.pushKV("window_final_block_height", pindex->nHeight);
1667 0 : ret.pushKV("window_block_count", blockcount);
1668 0 : if (blockcount > 0) {
1669 0 : ret.pushKV("window_tx_count", nTxDiff);
1670 0 : ret.pushKV("window_interval", nTimeDiff);
1671 0 : if (nTimeDiff > 0) {
1672 0 : ret.pushKV("txrate", ((double)nTxDiff) / nTimeDiff);
1673 0 : }
1674 0 : }
1675 :
1676 0 : return ret;
1677 0 : },
1678 : };
1679 0 : }
1680 :
1681 : template<typename T>
1682 0 : static T CalculateTruncatedMedian(std::vector<T>& scores)
1683 : {
1684 0 : size_t size = scores.size();
1685 0 : if (size == 0) {
1686 0 : return 0;
1687 : }
1688 :
1689 0 : std::sort(scores.begin(), scores.end());
1690 0 : if (size % 2 == 0) {
1691 0 : return (scores[size / 2 - 1] + scores[size / 2]) / 2;
1692 : } else {
1693 0 : return scores[size / 2];
1694 : }
1695 0 : }
1696 :
1697 0 : void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight)
1698 : {
1699 0 : if (scores.empty()) {
1700 0 : return;
1701 : }
1702 :
1703 0 : std::sort(scores.begin(), scores.end());
1704 :
1705 : // 10th, 25th, 50th, 75th, and 90th percentile weight units.
1706 0 : const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = {
1707 0 : total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0
1708 : };
1709 :
1710 0 : int64_t next_percentile_index = 0;
1711 0 : int64_t cumulative_weight = 0;
1712 0 : for (const auto& element : scores) {
1713 0 : cumulative_weight += element.second;
1714 0 : while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) {
1715 0 : result[next_percentile_index] = element.first;
1716 0 : ++next_percentile_index;
1717 : }
1718 : }
1719 :
1720 : // Fill any remaining percentiles with the last value.
1721 0 : for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
1722 0 : result[i] = scores.back().first;
1723 0 : }
1724 0 : }
1725 :
1726 : template<typename T>
1727 0 : static inline bool SetHasKeys(const std::set<T>& set) {return false;}
1728 : template<typename T, typename Tk, typename... Args>
1729 0 : static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&... args)
1730 : {
1731 0 : return (set.count(key) != 0) || SetHasKeys(set, args...);
1732 0 : }
1733 :
1734 : // outpoint (needed for the utxo index) + nHeight + fCoinBase
1735 : static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
1736 :
1737 2 : static RPCHelpMan getblockstats()
1738 : {
1739 4 : return RPCHelpMan{"getblockstats",
1740 2 : "\nCompute per block statistics for a given window. All amounts are in satoshis.\n"
1741 : "It won't work for some heights with pruning.\n",
1742 6 : {
1743 4 : {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block",
1744 6 : RPCArgOptions{
1745 : .skip_type_check = true,
1746 2 : .type_str = {"", "string or numeric"},
1747 : }},
1748 4 : {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)",
1749 6 : {
1750 2 : {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
1751 2 : {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
1752 : },
1753 2 : RPCArgOptions{.oneline_description="stats"}},
1754 : },
1755 2 : RPCResult{
1756 2 : RPCResult::Type::OBJ, "", "",
1757 64 : {
1758 2 : {RPCResult::Type::NUM, "avgfee", /*optional=*/true, "Average fee in the block"},
1759 2 : {RPCResult::Type::NUM, "avgfeerate", /*optional=*/true, "Average feerate (in satoshis per virtual byte)"},
1760 2 : {RPCResult::Type::NUM, "avgtxsize", /*optional=*/true, "Average transaction size"},
1761 2 : {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash (to check for potential reorgs)"},
1762 4 : {RPCResult::Type::ARR_FIXED, "feerate_percentiles", /*optional=*/true, "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
1763 12 : {
1764 2 : {RPCResult::Type::NUM, "10th_percentile_feerate", "The 10th percentile feerate"},
1765 2 : {RPCResult::Type::NUM, "25th_percentile_feerate", "The 25th percentile feerate"},
1766 2 : {RPCResult::Type::NUM, "50th_percentile_feerate", "The 50th percentile feerate"},
1767 2 : {RPCResult::Type::NUM, "75th_percentile_feerate", "The 75th percentile feerate"},
1768 2 : {RPCResult::Type::NUM, "90th_percentile_feerate", "The 90th percentile feerate"},
1769 : }},
1770 2 : {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the block"},
1771 2 : {RPCResult::Type::NUM, "ins", /*optional=*/true, "The number of inputs (excluding coinbase)"},
1772 2 : {RPCResult::Type::NUM, "maxfee", /*optional=*/true, "Maximum fee in the block"},
1773 2 : {RPCResult::Type::NUM, "maxfeerate", /*optional=*/true, "Maximum feerate (in satoshis per virtual byte)"},
1774 2 : {RPCResult::Type::NUM, "maxtxsize", /*optional=*/true, "Maximum transaction size"},
1775 2 : {RPCResult::Type::NUM, "medianfee", /*optional=*/true, "Truncated median fee in the block"},
1776 2 : {RPCResult::Type::NUM, "mediantime", /*optional=*/true, "The block median time past"},
1777 2 : {RPCResult::Type::NUM, "mediantxsize", /*optional=*/true, "Truncated median transaction size"},
1778 2 : {RPCResult::Type::NUM, "minfee", /*optional=*/true, "Minimum fee in the block"},
1779 2 : {RPCResult::Type::NUM, "minfeerate", /*optional=*/true, "Minimum feerate (in satoshis per virtual byte)"},
1780 2 : {RPCResult::Type::NUM, "mintxsize", /*optional=*/true, "Minimum transaction size"},
1781 2 : {RPCResult::Type::NUM, "outs", /*optional=*/true, "The number of outputs"},
1782 2 : {RPCResult::Type::NUM, "subsidy", /*optional=*/true, "The block subsidy"},
1783 2 : {RPCResult::Type::NUM, "swtotal_size", /*optional=*/true, "Total size of all segwit transactions"},
1784 2 : {RPCResult::Type::NUM, "swtotal_weight", /*optional=*/true, "Total weight of all segwit transactions"},
1785 2 : {RPCResult::Type::NUM, "swtxs", /*optional=*/true, "The number of segwit transactions"},
1786 2 : {RPCResult::Type::NUM, "time", /*optional=*/true, "The block time"},
1787 2 : {RPCResult::Type::NUM, "total_out", /*optional=*/true, "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"},
1788 2 : {RPCResult::Type::NUM, "total_size", /*optional=*/true, "Total size of all non-coinbase transactions"},
1789 2 : {RPCResult::Type::NUM, "total_weight", /*optional=*/true, "Total weight of all non-coinbase transactions"},
1790 2 : {RPCResult::Type::NUM, "totalfee", /*optional=*/true, "The fee total"},
1791 2 : {RPCResult::Type::NUM, "txs", /*optional=*/true, "The number of transactions (including coinbase)"},
1792 2 : {RPCResult::Type::NUM, "utxo_increase", /*optional=*/true, "The increase/decrease in the number of unspent outputs (not discounting op_return and similar)"},
1793 2 : {RPCResult::Type::NUM, "utxo_size_inc", /*optional=*/true, "The increase/decrease in size for the utxo index (not discounting op_return and similar)"},
1794 2 : {RPCResult::Type::NUM, "utxo_increase_actual", /*optional=*/true, "The increase/decrease in the number of unspent outputs, not counting unspendables"},
1795 2 : {RPCResult::Type::NUM, "utxo_size_inc_actual", /*optional=*/true, "The increase/decrease in size for the utxo index, not counting unspendables"},
1796 : }},
1797 2 : RPCExamples{
1798 4 : HelpExampleCli("getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
1799 4 : HelpExampleCli("getblockstats", R"(1000 '["minfeerate","avgfeerate"]')") +
1800 4 : HelpExampleRpc("getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
1801 2 : HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])")
1802 : },
1803 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1804 : {
1805 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
1806 0 : const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))};
1807 :
1808 0 : std::set<std::string> stats;
1809 0 : if (!request.params[1].isNull()) {
1810 0 : const UniValue stats_univalue = request.params[1].get_array();
1811 0 : for (unsigned int i = 0; i < stats_univalue.size(); i++) {
1812 0 : const std::string stat = stats_univalue[i].get_str();
1813 0 : stats.insert(stat);
1814 0 : }
1815 0 : }
1816 :
1817 0 : const CBlock& block = GetBlockChecked(chainman.m_blockman, &pindex);
1818 0 : const CBlockUndo& blockUndo = GetUndoChecked(chainman.m_blockman, &pindex);
1819 :
1820 0 : const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
1821 0 : const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
1822 0 : const bool do_medianfee = do_all || stats.count("medianfee") != 0;
1823 0 : const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
1824 0 : const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles ||
1825 0 : SetHasKeys(stats, "utxo_increase", "utxo_increase_actual", "utxo_size_inc", "utxo_size_inc_actual", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate");
1826 0 : const bool loop_outputs = do_all || loop_inputs || stats.count("total_out");
1827 0 : const bool do_calculate_size = do_mediantxsize ||
1828 0 : SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size");
1829 0 : const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate");
1830 0 : const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");
1831 :
1832 0 : CAmount maxfee = 0;
1833 0 : CAmount maxfeerate = 0;
1834 0 : CAmount minfee = MAX_MONEY;
1835 0 : CAmount minfeerate = MAX_MONEY;
1836 0 : CAmount total_out = 0;
1837 0 : CAmount totalfee = 0;
1838 0 : int64_t inputs = 0;
1839 0 : int64_t maxtxsize = 0;
1840 0 : int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE;
1841 0 : int64_t outputs = 0;
1842 0 : int64_t swtotal_size = 0;
1843 0 : int64_t swtotal_weight = 0;
1844 0 : int64_t swtxs = 0;
1845 0 : int64_t total_size = 0;
1846 0 : int64_t total_weight = 0;
1847 0 : int64_t utxos = 0;
1848 0 : int64_t utxo_size_inc = 0;
1849 0 : int64_t utxo_size_inc_actual = 0;
1850 0 : std::vector<CAmount> fee_array;
1851 0 : std::vector<std::pair<CAmount, int64_t>> feerate_array;
1852 0 : std::vector<int64_t> txsize_array;
1853 :
1854 0 : for (size_t i = 0; i < block.vtx.size(); ++i) {
1855 0 : const auto& tx = block.vtx.at(i);
1856 0 : outputs += tx->vout.size();
1857 :
1858 0 : CAmount tx_total_out = 0;
1859 0 : if (loop_outputs) {
1860 0 : for (const CTxOut& out : tx->vout) {
1861 0 : tx_total_out += out.nValue;
1862 :
1863 0 : size_t out_size = GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
1864 0 : utxo_size_inc += out_size;
1865 :
1866 : // The Genesis block and the repeated BIP30 block coinbases don't change the UTXO
1867 : // set counts, so they have to be excluded from the statistics
1868 0 : if (pindex.nHeight == 0 || (IsBIP30Repeat(pindex) && tx->IsCoinBase())) continue;
1869 : // Skip unspendable outputs since they are not included in the UTXO set
1870 0 : if (out.scriptPubKey.IsUnspendable()) continue;
1871 :
1872 0 : ++utxos;
1873 0 : utxo_size_inc_actual += out_size;
1874 : }
1875 0 : }
1876 :
1877 0 : if (tx->IsCoinBase()) {
1878 0 : continue;
1879 : }
1880 :
1881 0 : inputs += tx->vin.size(); // Don't count coinbase's fake input
1882 0 : total_out += tx_total_out; // Don't count coinbase reward
1883 :
1884 0 : int64_t tx_size = 0;
1885 0 : if (do_calculate_size) {
1886 :
1887 0 : tx_size = tx->GetTotalSize();
1888 0 : if (do_mediantxsize) {
1889 0 : txsize_array.push_back(tx_size);
1890 0 : }
1891 0 : maxtxsize = std::max(maxtxsize, tx_size);
1892 0 : mintxsize = std::min(mintxsize, tx_size);
1893 0 : total_size += tx_size;
1894 0 : }
1895 :
1896 0 : int64_t weight = 0;
1897 0 : if (do_calculate_weight) {
1898 0 : weight = GetTransactionWeight(*tx);
1899 0 : total_weight += weight;
1900 0 : }
1901 :
1902 0 : if (do_calculate_sw && tx->HasWitness()) {
1903 0 : ++swtxs;
1904 0 : swtotal_size += tx_size;
1905 0 : swtotal_weight += weight;
1906 0 : }
1907 :
1908 0 : if (loop_inputs) {
1909 0 : CAmount tx_total_in = 0;
1910 0 : const auto& txundo = blockUndo.vtxundo.at(i - 1);
1911 0 : for (const Coin& coin: txundo.vprevout) {
1912 0 : const CTxOut& prevoutput = coin.out;
1913 :
1914 0 : tx_total_in += prevoutput.nValue;
1915 0 : size_t prevout_size = GetSerializeSize(prevoutput, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
1916 0 : utxo_size_inc -= prevout_size;
1917 0 : utxo_size_inc_actual -= prevout_size;
1918 : }
1919 :
1920 0 : CAmount txfee = tx_total_in - tx_total_out;
1921 0 : CHECK_NONFATAL(MoneyRange(txfee));
1922 0 : if (do_medianfee) {
1923 0 : fee_array.push_back(txfee);
1924 0 : }
1925 0 : maxfee = std::max(maxfee, txfee);
1926 0 : minfee = std::min(minfee, txfee);
1927 0 : totalfee += txfee;
1928 :
1929 : // New feerate uses satoshis per virtual byte instead of per serialized byte
1930 0 : CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0;
1931 0 : if (do_feerate_percentiles) {
1932 0 : feerate_array.emplace_back(std::make_pair(feerate, weight));
1933 0 : }
1934 0 : maxfeerate = std::max(maxfeerate, feerate);
1935 0 : minfeerate = std::min(minfeerate, feerate);
1936 0 : }
1937 0 : }
1938 :
1939 0 : CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
1940 0 : CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight);
1941 :
1942 0 : UniValue feerates_res(UniValue::VARR);
1943 0 : for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
1944 0 : feerates_res.push_back(feerate_percentiles[i]);
1945 0 : }
1946 :
1947 0 : UniValue ret_all(UniValue::VOBJ);
1948 0 : ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
1949 0 : ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
1950 0 : ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
1951 0 : ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex());
1952 0 : ret_all.pushKV("feerate_percentiles", feerates_res);
1953 0 : ret_all.pushKV("height", (int64_t)pindex.nHeight);
1954 0 : ret_all.pushKV("ins", inputs);
1955 0 : ret_all.pushKV("maxfee", maxfee);
1956 0 : ret_all.pushKV("maxfeerate", maxfeerate);
1957 0 : ret_all.pushKV("maxtxsize", maxtxsize);
1958 0 : ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
1959 0 : ret_all.pushKV("mediantime", pindex.GetMedianTimePast());
1960 0 : ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
1961 0 : ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
1962 0 : ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate);
1963 0 : ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize);
1964 0 : ret_all.pushKV("outs", outputs);
1965 0 : ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus()));
1966 0 : ret_all.pushKV("swtotal_size", swtotal_size);
1967 0 : ret_all.pushKV("swtotal_weight", swtotal_weight);
1968 0 : ret_all.pushKV("swtxs", swtxs);
1969 0 : ret_all.pushKV("time", pindex.GetBlockTime());
1970 0 : ret_all.pushKV("total_out", total_out);
1971 0 : ret_all.pushKV("total_size", total_size);
1972 0 : ret_all.pushKV("total_weight", total_weight);
1973 0 : ret_all.pushKV("totalfee", totalfee);
1974 0 : ret_all.pushKV("txs", (int64_t)block.vtx.size());
1975 0 : ret_all.pushKV("utxo_increase", outputs - inputs);
1976 0 : ret_all.pushKV("utxo_size_inc", utxo_size_inc);
1977 0 : ret_all.pushKV("utxo_increase_actual", utxos - inputs);
1978 0 : ret_all.pushKV("utxo_size_inc_actual", utxo_size_inc_actual);
1979 :
1980 0 : if (do_all) {
1981 0 : return ret_all;
1982 : }
1983 :
1984 0 : UniValue ret(UniValue::VOBJ);
1985 0 : for (const std::string& stat : stats) {
1986 0 : const UniValue& value = ret_all[stat];
1987 0 : if (value.isNull()) {
1988 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic '%s'", stat));
1989 : }
1990 0 : ret.pushKV(stat, value);
1991 : }
1992 0 : return ret;
1993 0 : },
1994 : };
1995 0 : }
1996 :
1997 : namespace {
1998 : //! Search for a given set of pubkey scripts
1999 0 : bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
2000 : {
2001 0 : scan_progress = 0;
2002 0 : count = 0;
2003 0 : while (cursor->Valid()) {
2004 0 : COutPoint key;
2005 0 : Coin coin;
2006 0 : if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
2007 0 : if (++count % 8192 == 0) {
2008 0 : interruption_point();
2009 0 : if (should_abort) {
2010 : // allow to abort the scan via the abort reference
2011 0 : return false;
2012 : }
2013 0 : }
2014 0 : if (count % 256 == 0) {
2015 : // update progress reference every 256 item
2016 0 : uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1);
2017 0 : scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
2018 0 : }
2019 0 : if (needles.count(coin.out.scriptPubKey)) {
2020 0 : out_results.emplace(key, coin);
2021 0 : }
2022 0 : cursor->Next();
2023 0 : }
2024 0 : scan_progress = 100;
2025 0 : return true;
2026 0 : }
2027 : } // namespace
2028 :
2029 : /** RAII object to prevent concurrency issue when scanning the txout set */
2030 : static std::atomic<int> g_scan_progress;
2031 : static std::atomic<bool> g_scan_in_progress;
2032 : static std::atomic<bool> g_should_abort_scan;
2033 : class CoinsViewScanReserver
2034 : {
2035 : private:
2036 0 : bool m_could_reserve{false};
2037 : public:
2038 0 : explicit CoinsViewScanReserver() = default;
2039 :
2040 0 : bool reserve() {
2041 0 : CHECK_NONFATAL(!m_could_reserve);
2042 0 : if (g_scan_in_progress.exchange(true)) {
2043 0 : return false;
2044 : }
2045 0 : CHECK_NONFATAL(g_scan_progress == 0);
2046 0 : m_could_reserve = true;
2047 0 : return true;
2048 0 : }
2049 :
2050 0 : ~CoinsViewScanReserver() {
2051 0 : if (m_could_reserve) {
2052 0 : g_scan_in_progress = false;
2053 0 : g_scan_progress = 0;
2054 0 : }
2055 0 : }
2056 : };
2057 :
2058 2 : static const auto scan_action_arg_desc = RPCArg{
2059 2 : "action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"
2060 : "\"start\" for starting a scan\n"
2061 : "\"abort\" for aborting the current scan (returns true when abort was successful)\n"
2062 : "\"status\" for progress report (in %) of the current scan"
2063 : };
2064 :
2065 2 : static const auto scan_objects_arg_desc = RPCArg{
2066 2 : "scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"
2067 : "Every scan object is either a string descriptor or an object:",
2068 6 : {
2069 2 : {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
2070 4 : {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
2071 6 : {
2072 2 : {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
2073 2 : {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},
2074 : }},
2075 : },
2076 2 : RPCArgOptions{.oneline_description="[scanobjects,...]"},
2077 : };
2078 :
2079 2 : static const auto scan_result_abort = RPCResult{
2080 2 : "when action=='abort'", RPCResult::Type::BOOL, "success",
2081 2 : "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"
2082 : };
2083 2 : static const auto scan_result_status_none = RPCResult{
2084 2 : "when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""
2085 : };
2086 2 : static const auto scan_result_status_some = RPCResult{
2087 2 : "when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
2088 2 : {{RPCResult::Type::NUM, "progress", "Approximate percent complete"},}
2089 : };
2090 :
2091 :
2092 2 : static RPCHelpMan scantxoutset()
2093 : {
2094 : // scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
2095 2 : const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";
2096 :
2097 4 : return RPCHelpMan{"scantxoutset",
2098 2 : "\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
2099 : "Examples of output descriptors are:\n"
2100 : " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
2101 : " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
2102 : " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
2103 : " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
2104 : " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
2105 : " tr(<pubkey>) P2TR\n"
2106 : " tr(<pubkey>,{pk(<pubkey>)}) P2TR with single fallback pubkey in tapscript\n"
2107 : " rawtr(<pubkey>) P2TR with the specified key as output key rather than inner\n"
2108 : " wsh(and_v(v:pk(<pubkey>),after(2))) P2WSH miniscript with mandatory pubkey and a timelock\n"
2109 : "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
2110 : "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
2111 : "unhardened or hardened child keys.\n"
2112 : "In the latter case, a range needs to be specified by below if different from 1000.\n"
2113 : "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n",
2114 2 : {
2115 2 : scan_action_arg_desc,
2116 2 : scan_objects_arg_desc,
2117 : },
2118 4 : {
2119 14 : RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2120 2 : {RPCResult::Type::BOOL, "success", "Whether the scan was completed"},
2121 2 : {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},
2122 2 : {RPCResult::Type::NUM, "height", "The current block height (index)"},
2123 2 : {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
2124 4 : {RPCResult::Type::ARR, "unspents", "",
2125 4 : {
2126 4 : {RPCResult::Type::OBJ, "", "",
2127 16 : {
2128 2 : {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
2129 2 : {RPCResult::Type::NUM, "vout", "The vout value"},
2130 2 : {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"},
2131 2 : {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"},
2132 2 : {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
2133 2 : {RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
2134 2 : {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
2135 : }},
2136 : }},
2137 2 : {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
2138 : }},
2139 2 : scan_result_abort,
2140 2 : scan_result_status_some,
2141 2 : scan_result_status_none,
2142 : },
2143 2 : RPCExamples{
2144 4 : HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
2145 4 : HelpExampleCli("scantxoutset", "status") +
2146 4 : HelpExampleCli("scantxoutset", "abort") +
2147 4 : HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") +
2148 4 : HelpExampleRpc("scantxoutset", "\"status\"") +
2149 2 : HelpExampleRpc("scantxoutset", "\"abort\"")
2150 : },
2151 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2152 : {
2153 0 : UniValue result(UniValue::VOBJ);
2154 0 : if (request.params[0].get_str() == "status") {
2155 0 : CoinsViewScanReserver reserver;
2156 0 : if (reserver.reserve()) {
2157 : // no scan in progress
2158 0 : return UniValue::VNULL;
2159 : }
2160 0 : result.pushKV("progress", g_scan_progress.load());
2161 0 : return result;
2162 0 : } else if (request.params[0].get_str() == "abort") {
2163 0 : CoinsViewScanReserver reserver;
2164 0 : if (reserver.reserve()) {
2165 : // reserve was possible which means no scan was running
2166 0 : return false;
2167 : }
2168 : // set the abort flag
2169 0 : g_should_abort_scan = true;
2170 0 : return true;
2171 0 : } else if (request.params[0].get_str() == "start") {
2172 0 : CoinsViewScanReserver reserver;
2173 0 : if (!reserver.reserve()) {
2174 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
2175 : }
2176 :
2177 0 : if (request.params.size() < 2) {
2178 0 : throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action");
2179 : }
2180 :
2181 0 : std::set<CScript> needles;
2182 0 : std::map<CScript, std::string> descriptors;
2183 0 : CAmount total_in = 0;
2184 :
2185 : // loop through the scan objects
2186 0 : for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
2187 0 : FlatSigningProvider provider;
2188 0 : auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
2189 0 : for (CScript& script : scripts) {
2190 0 : std::string inferred = InferDescriptor(script, provider)->ToString();
2191 0 : needles.emplace(script);
2192 0 : descriptors.emplace(std::move(script), std::move(inferred));
2193 0 : }
2194 0 : }
2195 :
2196 : // Scan the unspent transaction output set for inputs
2197 0 : UniValue unspents(UniValue::VARR);
2198 0 : std::vector<CTxOut> input_txos;
2199 0 : std::map<COutPoint, Coin> coins;
2200 0 : g_should_abort_scan = false;
2201 0 : int64_t count = 0;
2202 0 : std::unique_ptr<CCoinsViewCursor> pcursor;
2203 : const CBlockIndex* tip;
2204 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
2205 : {
2206 0 : ChainstateManager& chainman = EnsureChainman(node);
2207 0 : LOCK(cs_main);
2208 0 : Chainstate& active_chainstate = chainman.ActiveChainstate();
2209 0 : active_chainstate.ForceFlushStateToDisk();
2210 0 : pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
2211 0 : tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
2212 0 : }
2213 0 : bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);
2214 0 : result.pushKV("success", res);
2215 0 : result.pushKV("txouts", count);
2216 0 : result.pushKV("height", tip->nHeight);
2217 0 : result.pushKV("bestblock", tip->GetBlockHash().GetHex());
2218 :
2219 0 : for (const auto& it : coins) {
2220 0 : const COutPoint& outpoint = it.first;
2221 0 : const Coin& coin = it.second;
2222 0 : const CTxOut& txo = coin.out;
2223 0 : input_txos.push_back(txo);
2224 0 : total_in += txo.nValue;
2225 :
2226 0 : UniValue unspent(UniValue::VOBJ);
2227 0 : unspent.pushKV("txid", outpoint.hash.GetHex());
2228 0 : unspent.pushKV("vout", (int32_t)outpoint.n);
2229 0 : unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
2230 0 : unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
2231 0 : unspent.pushKV("amount", ValueFromAmount(txo.nValue));
2232 0 : unspent.pushKV("coinbase", coin.IsCoinBase());
2233 0 : unspent.pushKV("height", (int32_t)coin.nHeight);
2234 :
2235 0 : unspents.push_back(unspent);
2236 0 : }
2237 0 : result.pushKV("unspents", unspents);
2238 0 : result.pushKV("total_amount", ValueFromAmount(total_in));
2239 0 : } else {
2240 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
2241 : }
2242 0 : return result;
2243 0 : },
2244 : };
2245 2 : }
2246 :
2247 : /** RAII object to prevent concurrency issue when scanning blockfilters */
2248 : static std::atomic<int> g_scanfilter_progress;
2249 : static std::atomic<int> g_scanfilter_progress_height;
2250 : static std::atomic<bool> g_scanfilter_in_progress;
2251 : static std::atomic<bool> g_scanfilter_should_abort_scan;
2252 : class BlockFiltersScanReserver
2253 : {
2254 : private:
2255 0 : bool m_could_reserve{false};
2256 : public:
2257 0 : explicit BlockFiltersScanReserver() = default;
2258 :
2259 0 : bool reserve() {
2260 0 : CHECK_NONFATAL(!m_could_reserve);
2261 0 : if (g_scanfilter_in_progress.exchange(true)) {
2262 0 : return false;
2263 : }
2264 0 : m_could_reserve = true;
2265 0 : return true;
2266 0 : }
2267 :
2268 0 : ~BlockFiltersScanReserver() {
2269 0 : if (m_could_reserve) {
2270 0 : g_scanfilter_in_progress = false;
2271 0 : }
2272 0 : }
2273 : };
2274 :
2275 0 : static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles)
2276 : {
2277 0 : const CBlock block{GetBlockChecked(blockman, &blockindex)};
2278 0 : const CBlockUndo block_undo{GetUndoChecked(blockman, &blockindex)};
2279 :
2280 : // Check if any of the outputs match the scriptPubKey
2281 0 : for (const auto& tx : block.vtx) {
2282 0 : if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) {
2283 0 : return needles.count(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end())) != 0;
2284 0 : })) {
2285 0 : return true;
2286 : }
2287 : }
2288 : // Check if any of the inputs match the scriptPubKey
2289 0 : for (const auto& txundo : block_undo.vtxundo) {
2290 0 : if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) {
2291 0 : return needles.count(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end())) != 0;
2292 0 : })) {
2293 0 : return true;
2294 : }
2295 : }
2296 :
2297 0 : return false;
2298 0 : }
2299 :
2300 2 : static RPCHelpMan scanblocks()
2301 : {
2302 4 : return RPCHelpMan{"scanblocks",
2303 2 : "\nReturn relevant blockhashes for given descriptors (requires blockfilterindex).\n"
2304 : "This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",
2305 10 : {
2306 2 : scan_action_arg_desc,
2307 2 : scan_objects_arg_desc,
2308 2 : RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},
2309 2 : RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},
2310 2 : RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
2311 4 : RPCArg{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
2312 4 : {
2313 2 : {"filter_false_positives", RPCArg::Type::BOOL, RPCArg::Default{false}, "Filter false positives (slower and may fail on pruned nodes). Otherwise they may occur at a rate of 1/M"},
2314 : },
2315 2 : RPCArgOptions{.oneline_description="options"}},
2316 : },
2317 6 : {
2318 2 : scan_result_status_none,
2319 10 : RPCResult{"When action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2320 2 : {RPCResult::Type::NUM, "from_height", "The height we started the scan from"},
2321 2 : {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"},
2322 4 : {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {
2323 2 : {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},
2324 : }},
2325 2 : {RPCResult::Type::BOOL, "completed", "true if the scan process was not aborted"}
2326 : }},
2327 6 : RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", {
2328 2 : {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
2329 2 : {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"},
2330 : },
2331 : },
2332 2 : scan_result_abort,
2333 : },
2334 2 : RPCExamples{
2335 4 : HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") +
2336 4 : HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") +
2337 4 : HelpExampleCli("scanblocks", "status") +
2338 4 : HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") +
2339 4 : HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") +
2340 2 : HelpExampleRpc("scanblocks", "\"status\"")
2341 : },
2342 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2343 : {
2344 0 : UniValue ret(UniValue::VOBJ);
2345 0 : if (request.params[0].get_str() == "status") {
2346 0 : BlockFiltersScanReserver reserver;
2347 0 : if (reserver.reserve()) {
2348 : // no scan in progress
2349 0 : return NullUniValue;
2350 : }
2351 0 : ret.pushKV("progress", g_scanfilter_progress.load());
2352 0 : ret.pushKV("current_height", g_scanfilter_progress_height.load());
2353 0 : return ret;
2354 0 : } else if (request.params[0].get_str() == "abort") {
2355 0 : BlockFiltersScanReserver reserver;
2356 0 : if (reserver.reserve()) {
2357 : // reserve was possible which means no scan was running
2358 0 : return false;
2359 : }
2360 : // set the abort flag
2361 0 : g_scanfilter_should_abort_scan = true;
2362 0 : return true;
2363 0 : } else if (request.params[0].get_str() == "start") {
2364 0 : BlockFiltersScanReserver reserver;
2365 0 : if (!reserver.reserve()) {
2366 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
2367 : }
2368 0 : const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()};
2369 :
2370 : BlockFilterType filtertype;
2371 0 : if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
2372 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
2373 : }
2374 :
2375 0 : UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]};
2376 0 : bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false};
2377 :
2378 0 : BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
2379 0 : if (!index) {
2380 0 : throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
2381 : }
2382 :
2383 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
2384 0 : ChainstateManager& chainman = EnsureChainman(node);
2385 :
2386 : // set the start-height
2387 0 : const CBlockIndex* start_index = nullptr;
2388 0 : const CBlockIndex* stop_block = nullptr;
2389 : {
2390 0 : LOCK(cs_main);
2391 0 : CChain& active_chain = chainman.ActiveChain();
2392 0 : start_index = active_chain.Genesis();
2393 0 : stop_block = active_chain.Tip(); // If no stop block is provided, stop at the chain tip.
2394 0 : if (!request.params[2].isNull()) {
2395 0 : start_index = active_chain[request.params[2].getInt<int>()];
2396 0 : if (!start_index) {
2397 0 : throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");
2398 : }
2399 0 : }
2400 0 : if (!request.params[3].isNull()) {
2401 0 : stop_block = active_chain[request.params[3].getInt<int>()];
2402 0 : if (!stop_block || stop_block->nHeight < start_index->nHeight) {
2403 0 : throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");
2404 : }
2405 0 : }
2406 0 : }
2407 0 : CHECK_NONFATAL(start_index);
2408 0 : CHECK_NONFATAL(stop_block);
2409 :
2410 : // loop through the scan objects, add scripts to the needle_set
2411 0 : GCSFilter::ElementSet needle_set;
2412 0 : for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
2413 0 : FlatSigningProvider provider;
2414 0 : std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
2415 0 : for (const CScript& script : scripts) {
2416 0 : needle_set.emplace(script.begin(), script.end());
2417 : }
2418 0 : }
2419 0 : UniValue blocks(UniValue::VARR);
2420 0 : const int amount_per_chunk = 10000;
2421 0 : std::vector<BlockFilter> filters;
2422 0 : int start_block_height = start_index->nHeight; // for progress reporting
2423 0 : const int total_blocks_to_process = stop_block->nHeight - start_block_height;
2424 :
2425 0 : g_scanfilter_should_abort_scan = false;
2426 0 : g_scanfilter_progress = 0;
2427 0 : g_scanfilter_progress_height = start_block_height;
2428 0 : bool completed = true;
2429 :
2430 0 : const CBlockIndex* end_range = nullptr;
2431 0 : do {
2432 0 : node.rpc_interruption_point(); // allow a clean shutdown
2433 0 : if (g_scanfilter_should_abort_scan) {
2434 0 : completed = false;
2435 0 : break;
2436 : }
2437 :
2438 : // split the lookup range in chunks if we are deeper than 'amount_per_chunk' blocks from the stopping block
2439 0 : int start_block = !end_range ? start_index->nHeight : start_index->nHeight + 1; // to not include the previous round 'end_range' block
2440 0 : end_range = (start_block + amount_per_chunk < stop_block->nHeight) ?
2441 0 : WITH_LOCK(::cs_main, return chainman.ActiveChain()[start_block + amount_per_chunk]) :
2442 0 : stop_block;
2443 :
2444 0 : if (index->LookupFilterRange(start_block, end_range, filters)) {
2445 0 : for (const BlockFilter& filter : filters) {
2446 : // compare the elements-set with each filter
2447 0 : if (filter.GetFilter().MatchAny(needle_set)) {
2448 0 : if (filter_false_positives) {
2449 : // Double check the filter matches by scanning the block
2450 0 : const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash())));
2451 :
2452 0 : if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) {
2453 0 : continue;
2454 : }
2455 0 : }
2456 :
2457 0 : blocks.push_back(filter.GetBlockHash().GetHex());
2458 0 : }
2459 : }
2460 0 : }
2461 0 : start_index = end_range;
2462 :
2463 : // update progress
2464 0 : int blocks_processed = end_range->nHeight - start_block_height;
2465 0 : if (total_blocks_to_process > 0) { // avoid division by zero
2466 0 : g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
2467 0 : } else {
2468 0 : g_scanfilter_progress = 100;
2469 : }
2470 0 : g_scanfilter_progress_height = end_range->nHeight;
2471 :
2472 : // Finish if we reached the stop block
2473 0 : } while (start_index != stop_block);
2474 :
2475 0 : ret.pushKV("from_height", start_block_height);
2476 0 : ret.pushKV("to_height", start_index->nHeight); // start_index is always the last scanned block here
2477 0 : ret.pushKV("relevant_blocks", blocks);
2478 0 : ret.pushKV("completed", completed);
2479 0 : }
2480 : else {
2481 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
2482 : }
2483 0 : return ret;
2484 0 : },
2485 : };
2486 0 : }
2487 :
2488 2 : static RPCHelpMan getblockfilter()
2489 : {
2490 4 : return RPCHelpMan{"getblockfilter",
2491 2 : "\nRetrieve a BIP 157 content filter for a particular block.\n",
2492 6 : {
2493 2 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},
2494 2 : {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
2495 : },
2496 2 : RPCResult{
2497 2 : RPCResult::Type::OBJ, "", "",
2498 6 : {
2499 2 : {RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"},
2500 2 : {RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"},
2501 : }},
2502 2 : RPCExamples{
2503 4 : HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") +
2504 2 : HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\", \"basic\"")
2505 : },
2506 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2507 : {
2508 0 : uint256 block_hash = ParseHashV(request.params[0], "blockhash");
2509 0 : std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC);
2510 0 : if (!request.params[1].isNull()) {
2511 0 : filtertype_name = request.params[1].get_str();
2512 0 : }
2513 :
2514 : BlockFilterType filtertype;
2515 0 : if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
2516 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
2517 : }
2518 :
2519 0 : BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
2520 0 : if (!index) {
2521 0 : throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
2522 : }
2523 :
2524 : const CBlockIndex* block_index;
2525 : bool block_was_connected;
2526 : {
2527 0 : ChainstateManager& chainman = EnsureAnyChainman(request.context);
2528 0 : LOCK(cs_main);
2529 0 : block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
2530 0 : if (!block_index) {
2531 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2532 : }
2533 0 : block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
2534 0 : }
2535 :
2536 0 : bool index_ready = index->BlockUntilSyncedToCurrentChain();
2537 :
2538 0 : BlockFilter filter;
2539 0 : uint256 filter_header;
2540 0 : if (!index->LookupFilter(block_index, filter) ||
2541 0 : !index->LookupFilterHeader(block_index, filter_header)) {
2542 : int err_code;
2543 0 : std::string errmsg = "Filter not found.";
2544 :
2545 0 : if (!block_was_connected) {
2546 0 : err_code = RPC_INVALID_ADDRESS_OR_KEY;
2547 0 : errmsg += " Block was not connected to active chain.";
2548 0 : } else if (!index_ready) {
2549 0 : err_code = RPC_MISC_ERROR;
2550 0 : errmsg += " Block filters are still in the process of being indexed.";
2551 0 : } else {
2552 0 : err_code = RPC_INTERNAL_ERROR;
2553 0 : errmsg += " This error is unexpected and indicates index corruption.";
2554 : }
2555 :
2556 0 : throw JSONRPCError(err_code, errmsg);
2557 0 : }
2558 :
2559 0 : UniValue ret(UniValue::VOBJ);
2560 0 : ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
2561 0 : ret.pushKV("header", filter_header.GetHex());
2562 0 : return ret;
2563 0 : },
2564 : };
2565 0 : }
2566 :
2567 : /**
2568 : * Serialize the UTXO set to a file for loading elsewhere.
2569 : *
2570 : * @see SnapshotMetadata
2571 : */
2572 2 : static RPCHelpMan dumptxoutset()
2573 : {
2574 2 : return RPCHelpMan{
2575 2 : "dumptxoutset",
2576 2 : "Write the serialized UTXO set to disk.",
2577 4 : {
2578 2 : {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
2579 : },
2580 2 : RPCResult{
2581 2 : RPCResult::Type::OBJ, "", "",
2582 14 : {
2583 2 : {RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"},
2584 2 : {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"},
2585 2 : {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
2586 2 : {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"},
2587 2 : {RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"},
2588 2 : {RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"},
2589 : }
2590 : },
2591 2 : RPCExamples{
2592 2 : HelpExampleCli("dumptxoutset", "utxo.dat")
2593 : },
2594 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2595 : {
2596 0 : const ArgsManager& args{EnsureAnyArgsman(request.context)};
2597 0 : const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
2598 : // Write to a temporary path and then move into `path` on completion
2599 : // to avoid confusion due to an interruption.
2600 0 : const fs::path temppath = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete"));
2601 :
2602 0 : if (fs::exists(path)) {
2603 0 : throw JSONRPCError(
2604 : RPC_INVALID_PARAMETER,
2605 0 : path.u8string() + " already exists. If you are sure this is what you want, "
2606 : "move it out of the way first");
2607 : }
2608 :
2609 0 : FILE* file{fsbridge::fopen(temppath, "wb")};
2610 0 : AutoFile afile{file};
2611 0 : if (afile.IsNull()) {
2612 0 : throw JSONRPCError(
2613 : RPC_INVALID_PARAMETER,
2614 0 : "Couldn't open file " + temppath.u8string() + " for writing.");
2615 : }
2616 :
2617 0 : NodeContext& node = EnsureAnyNodeContext(request.context);
2618 0 : UniValue result = CreateUTXOSnapshot(
2619 0 : node, node.chainman->ActiveChainstate(), afile, path, temppath);
2620 0 : fs::rename(temppath, path);
2621 :
2622 0 : result.pushKV("path", path.u8string());
2623 0 : return result;
2624 0 : },
2625 : };
2626 0 : }
2627 :
2628 0 : UniValue CreateUTXOSnapshot(
2629 : NodeContext& node,
2630 : Chainstate& chainstate,
2631 : AutoFile& afile,
2632 : const fs::path& path,
2633 : const fs::path& temppath)
2634 : {
2635 0 : std::unique_ptr<CCoinsViewCursor> pcursor;
2636 0 : std::optional<CCoinsStats> maybe_stats;
2637 : const CBlockIndex* tip;
2638 :
2639 : {
2640 : // We need to lock cs_main to ensure that the coinsdb isn't written to
2641 : // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
2642 : // based upon the coinsdb, and (iii) constructing a cursor to the
2643 : // coinsdb for use below this block.
2644 : //
2645 : // Cursors returned by leveldb iterate over snapshots, so the contents
2646 : // of the pcursor will not be affected by simultaneous writes during
2647 : // use below this block.
2648 : //
2649 : // See discussion here:
2650 : // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
2651 : //
2652 0 : LOCK(::cs_main);
2653 :
2654 0 : chainstate.ForceFlushStateToDisk();
2655 :
2656 0 : maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point);
2657 0 : if (!maybe_stats) {
2658 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
2659 : }
2660 :
2661 0 : pcursor = chainstate.CoinsDB().Cursor();
2662 0 : tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
2663 0 : }
2664 :
2665 0 : LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
2666 : tip->nHeight, tip->GetBlockHash().ToString(),
2667 : fs::PathToString(path), fs::PathToString(temppath)));
2668 :
2669 0 : SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx};
2670 :
2671 0 : afile << metadata;
2672 :
2673 0 : COutPoint key;
2674 0 : Coin coin;
2675 0 : unsigned int iter{0};
2676 :
2677 0 : while (pcursor->Valid()) {
2678 0 : if (iter % 5000 == 0) node.rpc_interruption_point();
2679 0 : ++iter;
2680 0 : if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
2681 0 : afile << key;
2682 0 : afile << coin;
2683 0 : }
2684 :
2685 0 : pcursor->Next();
2686 : }
2687 :
2688 0 : afile.fclose();
2689 :
2690 0 : UniValue result(UniValue::VOBJ);
2691 0 : result.pushKV("coins_written", maybe_stats->coins_count);
2692 0 : result.pushKV("base_hash", tip->GetBlockHash().ToString());
2693 0 : result.pushKV("base_height", tip->nHeight);
2694 0 : result.pushKV("path", path.u8string());
2695 0 : result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
2696 : // Cast required because univalue doesn't have serialization specified for
2697 : // `unsigned int`, nChainTx's type.
2698 0 : result.pushKV("nchaintx", uint64_t{tip->nChainTx});
2699 0 : return result;
2700 0 : }
2701 :
2702 1 : void RegisterBlockchainRPCCommands(CRPCTable& t)
2703 : {
2704 28 : static const CRPCCommand commands[]{
2705 1 : {"blockchain", &getblockchaininfo},
2706 1 : {"blockchain", &getchaintxstats},
2707 1 : {"blockchain", &getblockstats},
2708 1 : {"blockchain", &getbestblockhash},
2709 1 : {"blockchain", &getblockcount},
2710 1 : {"blockchain", &getblock},
2711 1 : {"blockchain", &getblockfrompeer},
2712 1 : {"blockchain", &getblockhash},
2713 1 : {"blockchain", &getblockheader},
2714 1 : {"blockchain", &getchaintips},
2715 1 : {"blockchain", &getdifficulty},
2716 1 : {"blockchain", &getdeploymentinfo},
2717 1 : {"blockchain", &gettxout},
2718 1 : {"blockchain", &gettxoutsetinfo},
2719 1 : {"blockchain", &pruneblockchain},
2720 1 : {"blockchain", &verifychain},
2721 1 : {"blockchain", &preciousblock},
2722 1 : {"blockchain", &scantxoutset},
2723 1 : {"blockchain", &scanblocks},
2724 1 : {"blockchain", &getblockfilter},
2725 1 : {"hidden", &invalidateblock},
2726 1 : {"hidden", &reconsiderblock},
2727 1 : {"hidden", &waitfornewblock},
2728 1 : {"hidden", &waitforblock},
2729 1 : {"hidden", &waitforblockheight},
2730 1 : {"hidden", &syncwithvalidationinterfacequeue},
2731 1 : {"hidden", &dumptxoutset},
2732 : };
2733 28 : for (const auto& c : commands) {
2734 27 : t.appendCommand(c.name, &c);
2735 : }
2736 1 : }
|