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