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