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