blockchain.cpp raw
1 // Copyright (c) 2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2022 The Limenka 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 <limenka-build-config.h> // IWYU pragma: keep
7
8 #include <rpc/blockchain.h>
9
10 #include <blockfilter.h>
11 #include <chain.h>
12 #include <chainparams.h>
13 #include <chainparamsbase.h>
14 #include <clientversion.h>
15 #include <coins.h>
16 #include <common/args.h>
17 #include <consensus/amount.h>
18 #include <consensus/params.h>
19 #include <consensus/validation.h>
20 #include <core_io.h>
21 #include <deploymentinfo.h>
22 #include <deploymentstatus.h>
23 #include <flatfile.h>
24 #include <hash.h>
25 #include <index/blockfilterindex.h>
26 #include <index/coinstatsindex.h>
27 #include <interfaces/mining.h>
28 #include <kernel/coinstats.h>
29 #include <key_io.h>
30 #include <logging/timer.h>
31 #include <net.h>
32 #include <net_processing.h>
33 #include <node/blockstorage.h>
34 #include <node/context.h>
35 #include <node/transaction.h>
36 #include <node/utxo_snapshot.h>
37 #include <node/warnings.h>
38 #include <primitives/transaction.h>
39 #include <policy/settings.h>
40 #include <rpc/server.h>
41 #include <rpc/server_util.h>
42 #include <rpc/util.h>
43 #include <script/descriptor.h>
44 #include <script/sign.h>
45 #include <serialize.h>
46 #include <streams.h>
47 #include <sync.h>
48 #include <txdb.h>
49 #include <txmempool.h>
50 #include <undo.h>
51 #include <univalue.h>
52 #include <util/check.h>
53 #include <util/fs.h>
54 #include <util/strencodings.h>
55 #include <util/string.h>
56 #include <util/syserror.h>
57 #include <validation.h>
58
59 #ifdef ENABLE_WALLET
60 #include <interfaces/wallet.h>
61 #include <wallet/coincontrol.h>
62 #include <wallet/fees.h>
63 #include <wallet/rpc/util.h>
64 #include <wallet/types.h>
65 #include <wallet/wallet.h>
66 #endif
67
68 #include <util/translation.h>
69 #include <validation.h>
70 #include <validationinterface.h>
71 #include <versionbits.h>
72
73 #include <stdint.h>
74
75 #include <condition_variable>
76 #include <iterator>
77 #include <memory>
78 #include <mutex>
79 #include <optional>
80 #include <vector>
81
82 using kernel::CCoinsStats;
83 using kernel::CoinStatsHashType;
84
85 using interfaces::BlockRef;
86 using interfaces::Mining;
87 using node::BlockManager;
88 using node::NodeContext;
89 using node::SnapshotMetadata;
90 using util::MakeUnorderedList;
91
92 std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
93 PrepareUTXOSnapshot(
94 Chainstate& chainstate,
95 const std::function<void()>& interruption_point = {})
96 EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
97
98 UniValue WriteUTXOSnapshot(
99 const bool is_human_readable,
100 const bool show_header,
101 const Span<const std::byte>& separator,
102 const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
103 Chainstate& chainstate,
104 CCoinsViewCursor* pcursor,
105 CCoinsStats* maybe_stats,
106 const CBlockIndex* tip,
107 AutoFile&& afile,
108 const fs::path& path,
109 const fs::path& temppath,
110 const std::function<void()>& interruption_point = {});
111
112 /* Calculate the difficulty for a given block index.
113 */
114 double GetDifficulty(const CBlockIndex& blockindex)
115 {
116 int nShift = (blockindex.nBits >> 24) & 0xff;
117 double dDiff =
118 (double)0x0000ffff / (double)(blockindex.nBits & 0x00ffffff);
119
120 while (nShift < 29)
121 {
122 dDiff *= 256.0;
123 nShift++;
124 }
125 while (nShift > 29)
126 {
127 dDiff /= 256.0;
128 nShift--;
129 }
130
131 return dDiff;
132 }
133
134 static int ComputeNextBlockAndDepth(const CBlockIndex& tip, const CBlockIndex& blockindex, const CBlockIndex*& next)
135 {
136 next = tip.GetAncestor(blockindex.nHeight + 1);
137 if (next && next->pprev == &blockindex) {
138 return tip.nHeight - blockindex.nHeight + 1;
139 }
140 next = nullptr;
141 return &blockindex == &tip ? 1 : -1;
142 }
143
144 static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman)
145 {
146 LOCK(::cs_main);
147 CChain& active_chain = chainman.ActiveChain();
148
149 if (param.isNum()) {
150 const int height{param.getInt<int>()};
151 if (height < 0) {
152 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
153 }
154 const int current_tip{active_chain.Height()};
155 if (height > current_tip) {
156 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
157 }
158
159 return active_chain[height];
160 } else {
161 const uint256 hash{ParseHashV(param, "hash_or_height")};
162 const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
163
164 if (!pindex) {
165 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
166 }
167
168 return pindex;
169 }
170 }
171
172 UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex, const uint256 pow_limit)
173 {
174 // Serialize passed information without accessing chain state of the active chain!
175 AssertLockNotHeld(cs_main); // For performance reasons
176
177 UniValue result(UniValue::VOBJ);
178 result.pushKV("hash", blockindex.GetBlockHash().GetHex());
179 const CBlockIndex* pnext;
180 int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
181 result.pushKV("confirmations", confirmations);
182 result.pushKV("height", blockindex.nHeight);
183 result.pushKV("version", blockindex.nVersion);
184 result.pushKV("versionHex", strprintf("%08x", blockindex.nVersion));
185 result.pushKV("merkleroot", blockindex.hashMerkleRoot.GetHex());
186 result.pushKV("time", blockindex.nTime);
187 result.pushKV("mediantime", blockindex.GetMedianTimePast());
188 result.pushKV("nonce", blockindex.nNonce);
189 result.pushKV("bits", strprintf("%08x", blockindex.nBits));
190 result.pushKV("target", GetTarget(blockindex, pow_limit).GetHex());
191 result.pushKV("difficulty", GetDifficulty(blockindex));
192 result.pushKV("chainwork", blockindex.nChainWork.GetHex());
193 result.pushKV("nTx", blockindex.nTx);
194
195 if (blockindex.pprev)
196 result.pushKV("previousblockhash", blockindex.pprev->GetBlockHash().GetHex());
197 if (pnext)
198 result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
199 return result;
200 }
201
202 UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity, const uint256 pow_limit)
203 {
204 UniValue result = blockheaderToJSON(tip, blockindex, pow_limit);
205
206 result.pushKV("strippedsize", (int)::GetSerializeSize(TX_NO_WITNESS(block)));
207 result.pushKV("size", (int)::GetSerializeSize(TX_WITH_WITNESS(block)));
208 result.pushKV("weight", (int)::GetBlockWeight(block));
209 UniValue txs(UniValue::VARR);
210 txs.reserve(block.vtx.size());
211
212 switch (verbosity) {
213 case TxVerbosity::SHOW_TXID:
214 for (const CTransactionRef& tx : block.vtx) {
215 txs.push_back(tx->GetHash().GetHex());
216 }
217 break;
218
219 case TxVerbosity::SHOW_DETAILS:
220 case TxVerbosity::SHOW_DETAILS_AND_PREVOUT:
221 CBlockUndo blockUndo;
222 const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
223 bool have_undo{is_not_pruned && WITH_LOCK(::cs_main, return blockindex.nStatus & BLOCK_HAVE_UNDO)};
224 if (have_undo && !blockman.ReadBlockUndo(blockUndo, blockindex)) {
225 throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
226 }
227 for (size_t i = 0; i < block.vtx.size(); ++i) {
228 const CTransactionRef& tx = block.vtx.at(i);
229 // coinbase transaction (i.e. i == 0) doesn't have undo data
230 const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
231 UniValue objTx(UniValue::VOBJ);
232 TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, txundo, verbosity);
233 txs.push_back(std::move(objTx));
234 }
235 break;
236 }
237
238 result.pushKV("tx", std::move(txs));
239
240 return result;
241 }
242
243 static RPCHelpMan getblockcount()
244 {
245 return RPCHelpMan{"getblockcount",
246 "\nReturns the height of the most-work fully-validated chain.\n"
247 "The genesis block has height 0.\n",
248 {},
249 RPCResult{
250 RPCResult::Type::NUM, "", "The current block count"},
251 RPCExamples{
252 HelpExampleCli("getblockcount", "")
253 + HelpExampleRpc("getblockcount", "")
254 },
255 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
256 {
257 ChainstateManager& chainman = EnsureAnyChainman(request.context);
258 LOCK(cs_main);
259 return chainman.ActiveChain().Height();
260 },
261 };
262 }
263
264 static RPCHelpMan getbestblockhash()
265 {
266 return RPCHelpMan{"getbestblockhash",
267 "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n",
268 {},
269 RPCResult{
270 RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
271 RPCExamples{
272 HelpExampleCli("getbestblockhash", "")
273 + HelpExampleRpc("getbestblockhash", "")
274 },
275 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
276 {
277 ChainstateManager& chainman = EnsureAnyChainman(request.context);
278 LOCK(cs_main);
279 return chainman.ActiveChain().Tip()->GetBlockHash().GetHex();
280 },
281 };
282 }
283
284 static RPCHelpMan waitfornewblock()
285 {
286 return RPCHelpMan{"waitfornewblock",
287 "\nWaits for any new block and returns useful info about it.\n"
288 "\nReturns the current block on timeout or exit.\n"
289 "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
290 {
291 {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
292 {"current_tip", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "Method waits for the chain tip to differ from this."},
293 },
294 RPCResult{
295 RPCResult::Type::OBJ, "", "",
296 {
297 {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
298 {RPCResult::Type::NUM, "height", "Block height"},
299 }},
300 RPCExamples{
301 HelpExampleCli("waitfornewblock", "1000")
302 + HelpExampleRpc("waitfornewblock", "1000")
303 },
304 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
305 {
306 int timeout = 0;
307 if (!request.params[0].isNull())
308 timeout = request.params[0].getInt<int>();
309 if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
310
311 NodeContext& node = EnsureAnyNodeContext(request.context);
312 Mining& miner = EnsureMining(node);
313
314 // If the caller provided a current_tip value, pass it to waitTipChanged().
315 //
316 // If the caller did not provide a current tip hash, call getTip() to get
317 // one and wait for the tip to be different from this value. This mode is
318 // less reliable because if the tip changed between waitfornewblock calls,
319 // it will need to change a second time before this call returns.
320 auto block{CHECK_NONFATAL(miner.getTip()).value()};
321
322 uint256 tip_hash{request.params[1].isNull()
323 ? block.hash
324 : ParseHashV(request.params[1], "current_tip")};
325
326 // If the user provided an invalid current_tip then this call immediately
327 // returns the current tip.
328 std::optional<BlockRef> new_block = timeout ? miner.waitTipChanged(tip_hash, std::chrono::milliseconds(timeout)) :
329 miner.waitTipChanged(tip_hash);
330
331 // Return current block upon shutdown
332 if (new_block) block = *new_block;
333
334 UniValue ret(UniValue::VOBJ);
335 ret.pushKV("hash", block.hash.GetHex());
336 ret.pushKV("height", block.height);
337 return ret;
338 },
339 };
340 }
341
342 static RPCHelpMan waitforblock()
343 {
344 return RPCHelpMan{"waitforblock",
345 "\nWaits for a specific new block and returns useful info about it.\n"
346 "\nReturns the current block on timeout or exit.\n"
347 "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
348 {
349 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash to wait for."},
350 {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
351 },
352 RPCResult{
353 RPCResult::Type::OBJ, "", "",
354 {
355 {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
356 {RPCResult::Type::NUM, "height", "Block height"},
357 }},
358 RPCExamples{
359 HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\" 1000")
360 + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000")
361 },
362 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
363 {
364 int timeout = 0;
365
366 uint256 hash(ParseHashV(request.params[0], "blockhash"));
367
368 if (!request.params[1].isNull())
369 timeout = request.params[1].getInt<int>();
370 if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
371
372 NodeContext& node = EnsureAnyNodeContext(request.context);
373 Mining& miner = EnsureMining(node);
374
375 auto block{CHECK_NONFATAL(miner.getTip()).value()};
376 const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
377 while (block.hash != hash) {
378 std::optional<BlockRef> new_block;
379 if (timeout) {
380 auto now{std::chrono::steady_clock::now()};
381 if (now >= deadline) break;
382 const MillisecondsDouble remaining{deadline - now};
383 new_block = miner.waitTipChanged(block.hash, remaining);
384 } else {
385 new_block = miner.waitTipChanged(block.hash);
386 }
387 // Return current block upon shutdown
388 if (!new_block) break;
389 block = *new_block;
390 }
391
392 UniValue ret(UniValue::VOBJ);
393 ret.pushKV("hash", block.hash.GetHex());
394 ret.pushKV("height", block.height);
395 return ret;
396 },
397 };
398 }
399
400 static RPCHelpMan waitforblockheight()
401 {
402 return RPCHelpMan{"waitforblockheight",
403 "\nWaits for (at least) block height and returns the height and hash\n"
404 "of the current tip.\n"
405 "\nReturns the current block on timeout or exit.\n"
406 "\nMake sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
407 {
408 {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "Block height to wait for."},
409 {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
410 },
411 RPCResult{
412 RPCResult::Type::OBJ, "", "",
413 {
414 {RPCResult::Type::STR_HEX, "hash", "The blockhash"},
415 {RPCResult::Type::NUM, "height", "Block height"},
416 }},
417 RPCExamples{
418 HelpExampleCli("waitforblockheight", "100 1000")
419 + HelpExampleRpc("waitforblockheight", "100, 1000")
420 },
421 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
422 {
423 int timeout = 0;
424
425 int height = request.params[0].getInt<int>();
426
427 if (!request.params[1].isNull())
428 timeout = request.params[1].getInt<int>();
429 if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
430
431 NodeContext& node = EnsureAnyNodeContext(request.context);
432 Mining& miner = EnsureMining(node);
433
434 auto block{CHECK_NONFATAL(miner.getTip()).value()};
435 const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
436
437 while (block.height < height) {
438 std::optional<BlockRef> new_block;
439 if (timeout) {
440 auto now{std::chrono::steady_clock::now()};
441 if (now >= deadline) break;
442 const MillisecondsDouble remaining{deadline - now};
443 new_block = miner.waitTipChanged(block.hash, remaining);
444 } else {
445 new_block = miner.waitTipChanged(block.hash);
446 }
447 // Return current block on shutdown
448 if (!new_block) break;
449 block = *new_block;
450 }
451
452 UniValue ret(UniValue::VOBJ);
453 ret.pushKV("hash", block.hash.GetHex());
454 ret.pushKV("height", block.height);
455 return ret;
456 },
457 };
458 }
459
460 static RPCHelpMan syncwithvalidationinterfacequeue()
461 {
462 return RPCHelpMan{"syncwithvalidationinterfacequeue",
463 "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n",
464 {},
465 RPCResult{RPCResult::Type::NONE, "", ""},
466 RPCExamples{
467 HelpExampleCli("syncwithvalidationinterfacequeue","")
468 + HelpExampleRpc("syncwithvalidationinterfacequeue","")
469 },
470 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
471 {
472 NodeContext& node = EnsureAnyNodeContext(request.context);
473 CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
474 return UniValue::VNULL;
475 },
476 };
477 }
478
479 static RPCHelpMan getdifficulty()
480 {
481 return RPCHelpMan{"getdifficulty",
482 "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n",
483 {},
484 RPCResult{
485 RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum difficulty."},
486 RPCExamples{
487 HelpExampleCli("getdifficulty", "")
488 + HelpExampleRpc("getdifficulty", "")
489 },
490 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
491 {
492 ChainstateManager& chainman = EnsureAnyChainman(request.context);
493 LOCK(cs_main);
494 return GetDifficulty(*CHECK_NONFATAL(chainman.ActiveChain().Tip()));
495 },
496 };
497 }
498
499 static RPCHelpMan getblockfrompeer()
500 {
501 return RPCHelpMan{
502 "getblockfrompeer",
503 "Attempt to fetch block from a given peer.\n\n"
504 "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"
505 "Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
506 "Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
507 "When a peer does not respond with a block, we will disconnect.\n"
508 "Note: The block could be re-pruned as soon as it is received.\n\n"
509 "Returns an empty JSON object if the request was successfully scheduled.",
510 {
511 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
512 {"peer_id|nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
513 },
514 RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
515 RPCExamples{
516 HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
517 + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
518 },
519 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
520 {
521 const NodeContext& node = EnsureAnyNodeContext(request.context);
522 ChainstateManager& chainman = EnsureChainman(node);
523 PeerManager& peerman = EnsurePeerman(node);
524
525 const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
526 const NodeId peer_id{request.params[1].getInt<int64_t>()};
527
528 const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
529
530 #if 0
531 // Fetching blocks before the node has syncing past their height can prevent block files from
532 // being pruned, so we avoid it if the node is in prune mode.
533 if (chainman.m_blockman.IsPruneMode() && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
534 throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer");
535 }
536 #endif
537
538 const bool block_has_data = index && WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA);
539 if (block_has_data) {
540 throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
541 }
542
543 if (const auto err{peerman.FetchBlock(peer_id, block_hash, index)}) {
544 throw JSONRPCError(RPC_MISC_ERROR, err.value());
545 }
546 return UniValue::VOBJ;
547 },
548 };
549 }
550
551 static RPCHelpMan getblockhash()
552 {
553 return RPCHelpMan{"getblockhash",
554 "\nReturns hash of block in best-block-chain at height provided.\n",
555 {
556 {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"},
557 },
558 RPCResult{
559 RPCResult::Type::STR_HEX, "", "The block hash"},
560 RPCExamples{
561 HelpExampleCli("getblockhash", "1000")
562 + HelpExampleRpc("getblockhash", "1000")
563 },
564 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
565 {
566 ChainstateManager& chainman = EnsureAnyChainman(request.context);
567 LOCK(cs_main);
568 const CChain& active_chain = chainman.ActiveChain();
569
570 int nHeight = request.params[0].getInt<int>();
571 if (nHeight < 0 || nHeight > active_chain.Height())
572 throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
573
574 const CBlockIndex* pblockindex = active_chain[nHeight];
575 return pblockindex->GetBlockHash().GetHex();
576 },
577 };
578 }
579
580 #ifdef ENABLE_WALLET
581 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);
582
583 static RPCHelpMan sweepprivkeys()
584 {
585 return RPCHelpMan{"sweepprivkeys",
586 "\nSends limenkas controlled by private key to specified destinations.\n",
587 {
588 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::NO, "",
589 {
590 {"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of WIF private key(s)",
591 {
592 {"privkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
593 },
594 },
595
596 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Label for received limenkas"},
597 },
598 RPCArgOptions{.oneline_description="options"}},
599 },
600 RPCResult{RPCResult::Type::STR_HEX, "", "The transaction id."},
601 RPCExamples{""},
602 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
603 {
604 NodeContext& node = EnsureAnyNodeContext(request.context);
605
606 JSONRPCRequest wallet_req = request;
607 CHECK_NONFATAL(node.wallet_loader && node.wallet_loader->context());
608 node.wallet_loader->assignContextHACK(wallet_req.context);
609 std::shared_ptr<wallet::CWallet> const wallet = wallet::GetWalletForJSONRPCRequest(wallet_req);
610 if (!wallet) return NullUniValue;
611 wallet::CWallet* const pwallet = wallet.get();
612
613 // NOTE: It isn't safe to sweep-and-send in a single action, since this would leave the send missing from the transaction history
614
615 // Parse options
616 std::set<CScript> needles;
617 wallet::CCoinControl coin_control;
618 FlatSigningProvider temp_keystore;
619 CMutableTransaction tx;
620 std::string label;
621 CAmount total_in = 0;
622 for (const std::string& optname : request.params[0].getKeys()) {
623 const UniValue& optval = request.params[0][optname];
624 if (optname == "privkeys") {
625 const UniValue& privkeys_a = optval.get_array();
626 for (size_t privkey_i = 0; privkey_i < privkeys_a.size(); ++privkey_i) {
627 const UniValue& privkey_wif = privkeys_a[privkey_i];
628 std::string wif_secret = privkey_wif.get_str();
629 CKey key = DecodeSecret(wif_secret);
630 if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
631 CPubKey pubkey = key.GetPubKey();
632 CHECK_NONFATAL(key.VerifyPubKey(pubkey));
633
634 temp_keystore.keys[pubkey.GetID()] = key;
635 temp_keystore.pubkeys[pubkey.GetID()] = pubkey;
636 CKeyID address = pubkey.GetID();
637 CScript script = GetScriptForDestination(PKHash(address));
638 if (!script.empty()) {
639 needles.insert(script);
640 }
641 script = GetScriptForRawPubKey(pubkey);
642 if (!script.empty()) {
643 needles.insert(script);
644 }
645 if (pubkey.IsCompressed()) {
646 CScript p2wpkh_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
647 if (!p2wpkh_script.empty()) {
648 needles.insert(p2wpkh_script);
649 }
650 script = GetScriptForDestination(ScriptHash(p2wpkh_script));
651 if (!script.empty()) {
652 needles.insert(script);
653 temp_keystore.scripts[CScriptID(p2wpkh_script)] = p2wpkh_script;
654 }
655 auto tap_tweak = XOnlyPubKey(pubkey).CreateTapTweak(nullptr);
656 if (tap_tweak) {
657 WitnessV1Taproot output_key{tap_tweak->first};
658 needles.insert(GetScriptForDestination(output_key));
659 TaprootBuilder builder;
660 builder.Finalize(XOnlyPubKey(pubkey));
661 temp_keystore.tr_trees[output_key] = builder;
662 }
663 }
664 }
665 } else if (optname == "label") {
666 label = wallet::LabelFromValue(optval.get_str());
667 } else {
668 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unrecognised option '%s'", optname));
669 }
670 }
671
672 std::unique_ptr<wallet::ReserveDestination> reservedest;
673 CTxDestination dest;
674 {
675 LOCK(pwallet->cs_wallet);
676
677 // Reserve the key we will be using
678 reservedest.reset(new wallet::ReserveDestination(pwallet, pwallet->TransactionChangeType(pwallet->m_default_change_type, std::vector<wallet::CRecipient>())));
679 auto op_dest = reservedest->GetReservedDestination(false);
680 if (!op_dest) {
681 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
682 }
683 dest = *op_dest;
684 }
685
686 // Scan UTXO set for inputs
687 std::vector<CTxOut> input_txos;
688 {
689 // Collect all possible inputs
690 std::map<COutPoint, Coin> coins;
691 {
692 std::unique_ptr<CCoinsViewCursor> pcursor;
693 {
694 ChainstateManager& chainman = EnsureAnyChainman(request.context);
695 LOCK(cs_main);
696 if (node.mempool) {
697 node.mempool->FindScriptPubKey(needles, coins);
698 }
699 Chainstate& active_chainstate = chainman.ActiveChainstate();
700 active_chainstate.ForceFlushStateToDisk();
701 pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor());
702 CHECK_NONFATAL(pcursor);
703 }
704 std::atomic<int> scan_progress;
705 const std::atomic<bool> should_abort{false};
706 int64_t count;
707 if (!FindScriptPubKey(scan_progress, should_abort, count, pcursor.get(), needles, coins, node.rpc_interruption_point)) {
708 throw JSONRPCError(RPC_MISC_ERROR, "UTXO FindScriptPubKey failed");
709 }
710 }
711
712 // Add them as inputs to the transaction, and count the total value
713 for (auto& it : coins) {
714 const COutPoint& outpoint = it.first;
715 const Coin& coin = it.second;
716 const CTxOut& txo = coin.out;
717 tx.vin.emplace_back(outpoint.hash, outpoint.n);
718 input_txos.push_back(txo);
719 total_in += txo.nValue;
720 }
721 }
722
723 if (total_in == 0) {
724 throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "No value to sweep");
725 }
726
727 tx.vout.emplace_back(total_in, GetScriptForDestination(dest));
728
729 while (true) {
730 if (IsDust(tx.vout[0], pwallet->chain().relayDustFee())) {
731 throw JSONRPCError(RPC_VERIFY_REJECTED, "Swept value would be dust");
732 }
733 PrecomputedTransactionData txdata;
734 txdata.Init(tx, std::vector<CTxOut>(input_txos.begin(), input_txos.end()), true);
735 for (size_t input_index = 0; input_index < tx.vin.size(); ++input_index) {
736 const auto& utxo = input_txos[input_index];
737 SignatureData sig_data;
738 MutableTransactionSignatureCreator creator(tx, input_index, utxo.nValue, &txdata, SIGHASH_ALL);
739 if (!ProduceSignature(temp_keystore, creator, utxo.scriptPubKey, sig_data)) {
740 throw JSONRPCError(RPC_MISC_ERROR, "Failed to sign");
741 }
742 UpdateInput(tx.vin.at(input_index), sig_data);
743 }
744 int64_t tx_vsize = GetVirtualTransactionSize(CTransaction(tx));
745 CAmount fee_needed = GetMinimumFee(*wallet, tx_vsize, coin_control, nullptr /* FeeCalculation */);
746 const CAmount total_out = tx.vout[0].nValue;
747 if (fee_needed <= total_in - total_out) {
748 break;
749 }
750 tx.vout[0].nValue = total_in - fee_needed;
751 }
752
753 CTransactionRef final_tx(MakeTransactionRef(std::move(tx)));
754 pwallet->SetAddressBook(dest, label, wallet::AddressPurpose::RECEIVE);
755
756 std::string err_string;
757 const node::TransactionError err = BroadcastTransaction(node, final_tx, err_string, pwallet->m_default_max_tx_fee, true /* relay */, true /* wait_callback */);
758 if (node::TransactionError::OK != err) {
759 pwallet->DelAddressBook(dest);
760 throw JSONRPCTransactionError(err, err_string);
761 }
762 reservedest->KeepDestination();
763
764 return final_tx->GetHash().GetHex();
765 },
766 };
767 }
768 #endif // ENABLE_WALLET
769
770 static RPCHelpMan getblockheader()
771 {
772 return RPCHelpMan{"getblockheader",
773 "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n"
774 "If verbose is true, returns an Object with information about blockheader <hash>.\n",
775 {
776 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
777 {"verbose", RPCArg::Type::BOOL, RPCArg::Default{true}, "true for a json object, false for the hex-encoded data"},
778 },
779 {
780 RPCResult{"for verbose = true",
781 RPCResult::Type::OBJ, "", "",
782 {
783 {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
784 {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
785 {RPCResult::Type::NUM, "height", "The block height or index"},
786 {RPCResult::Type::NUM, "version", "The block version"},
787 {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
788 {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
789 {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
790 {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
791 {RPCResult::Type::NUM, "nonce", "The nonce"},
792 {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
793 {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
794 {RPCResult::Type::NUM, "difficulty", "The difficulty"},
795 {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the current chain"},
796 {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
797 {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
798 {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
799 }},
800 RPCResult{"for verbose=false",
801 RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
802 },
803 RPCExamples{
804 HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
805 + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
806 },
807 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
808 {
809 uint256 hash(ParseHashV(request.params[0], "hash"));
810
811 bool fVerbose = true;
812 if (!request.params[1].isNull())
813 fVerbose = request.params[1].get_bool();
814
815 const CBlockIndex* pblockindex;
816 const CBlockIndex* tip;
817 ChainstateManager& chainman = EnsureAnyChainman(request.context);
818 {
819 LOCK(cs_main);
820 pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
821 tip = chainman.ActiveChain().Tip();
822 }
823
824 if (!pblockindex) {
825 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
826 }
827
828 if (!fVerbose)
829 {
830 DataStream ssBlock{};
831 ssBlock << pblockindex->GetBlockHeader();
832 std::string strHex = HexStr(ssBlock);
833 return strHex;
834 }
835
836 return blockheaderToJSON(*tip, *pblockindex, chainman.GetConsensus().powLimit);
837 },
838 };
839 }
840
841 void CheckBlockDataAvailability(BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo)
842 {
843 AssertLockHeld(cs_main);
844 uint32_t flag = check_for_undo ? BLOCK_HAVE_UNDO : BLOCK_HAVE_DATA;
845 if (!(blockindex.nStatus & flag)) {
846 if (blockman.IsBlockPruned(blockindex)) {
847 throw JSONRPCError(RPC_MISC_ERROR, strprintf("%s not available (pruned data)", check_for_undo ? "Undo data" : "Block"));
848 }
849 if (check_for_undo) {
850 throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available");
851 }
852 throw JSONRPCError(RPC_MISC_ERROR, "Block not available (not fully downloaded)");
853 }
854 }
855
856 static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
857 {
858 CBlock block;
859 {
860 LOCK(cs_main);
861 CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
862 }
863
864 if (!blockman.ReadBlock(block, blockindex)) {
865 // Block not found on disk. This shouldn't normally happen unless the block was
866 // pruned right after we released the lock above.
867 throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
868 }
869
870 return block;
871 }
872
873 static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
874 {
875 std::vector<uint8_t> data{};
876 FlatFilePos pos{};
877 {
878 LOCK(cs_main);
879 CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false);
880 pos = blockindex.GetBlockPos();
881 }
882
883 if (!blockman.ReadRawBlock(data, pos)) {
884 // Block not found on disk. This shouldn't normally happen unless the block was
885 // pruned right after we released the lock above.
886 throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
887 }
888
889 return data;
890 }
891
892 static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& blockindex)
893 {
894 CBlockUndo blockUndo;
895
896 // The Genesis block does not have undo data
897 if (blockindex.nHeight == 0) return blockUndo;
898
899 {
900 LOCK(cs_main);
901 CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/true);
902 }
903
904 if (!blockman.ReadBlockUndo(blockUndo, blockindex)) {
905 throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
906 }
907
908 return blockUndo;
909 }
910
911 const RPCResult& GetBlockVin()
912 {
913 static const RPCResult getblock_vin{
914 RPCResult::Type::ARR, "vin", "",
915 {
916 {RPCResult::Type::OBJ, "", "",
917 {
918 {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"},
919 {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)",
920 {
921 {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
922 {RPCResult::Type::NUM, "height", "The height of the prevout"},
923 {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
924 {RPCResult::Type::OBJ, "scriptPubKey", "",
925 {
926 {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
927 {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
928 {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
929 {RPCResult::Type::STR, "address", /*optional=*/true, "The Limenka address (only if a well-defined address exists)"},
930 {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
931 }},
932 }},
933 }},
934 }
935 };
936 return getblock_vin;
937 }
938
939 static RPCHelpMan getblock()
940 {
941 return RPCHelpMan{"getblock",
942 "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
943 "If verbosity is 1, returns an Object with information about block <hash>.\n"
944 "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction.\n"
945 "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",
946 {
947 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
948 {"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",
949 RPCArgOptions{.skip_type_check = true}},
950 },
951 {
952 RPCResult{"for verbosity = 0",
953 RPCResult::Type::STR_HEX, "", "A string that is serialized, hex-encoded data for block 'hash'"},
954 RPCResult{"for verbosity = 1",
955 RPCResult::Type::OBJ, "", "",
956 {
957 {RPCResult::Type::STR_HEX, "hash", "the block hash (same as provided)"},
958 {RPCResult::Type::NUM, "confirmations", "The number of confirmations, or -1 if the block is not on the main chain"},
959 {RPCResult::Type::NUM, "size", "The block size"},
960 {RPCResult::Type::NUM, "strippedsize", "The block size excluding witness data"},
961 {RPCResult::Type::NUM, "weight", "The block weight as defined in BIP 141"},
962 {RPCResult::Type::NUM, "height", "The block height or index"},
963 {RPCResult::Type::NUM, "version", "The block version"},
964 {RPCResult::Type::STR_HEX, "versionHex", "The block version formatted in hexadecimal"},
965 {RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
966 {RPCResult::Type::ARR, "tx", "The transaction ids",
967 {{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
968 {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
969 {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
970 {RPCResult::Type::NUM, "nonce", "The nonce"},
971 {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
972 {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
973 {RPCResult::Type::NUM, "difficulty", "The difficulty"},
974 {RPCResult::Type::STR_HEX, "chainwork", "Expected number of hashes required to produce the chain up to this block (in hex)"},
975 {RPCResult::Type::NUM, "nTx", "The number of transactions in the block"},
976 {RPCResult::Type::STR_HEX, "previousblockhash", /*optional=*/true, "The hash of the previous block (if available)"},
977 {RPCResult::Type::STR_HEX, "nextblockhash", /*optional=*/true, "The hash of the next block (if available)"},
978 }},
979 RPCResult{"for verbosity = 2",
980 RPCResult::Type::OBJ, "", "",
981 {
982 {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
983 {RPCResult::Type::ARR, "tx", "",
984 {
985 {RPCResult::Type::OBJ, "", "",
986 {
987 {RPCResult::Type::ELISION, "", "The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 \"tx\" result"},
988 {RPCResult::Type::NUM, "fee", /*optional=*/true, "The transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
989 }},
990 }},
991 }},
992 RPCResult{"for verbosity = 3",
993 RPCResult::Type::OBJ, "", "",
994 {
995 {RPCResult::Type::ELISION, "", "Same output as verbosity = 2"},
996 {RPCResult::Type::ARR, "tx", "",
997 {
998 {RPCResult::Type::OBJ, "", "",
999 {
1000 GetBlockVin(),
1001 }},
1002 }},
1003 }},
1004 },
1005 RPCExamples{
1006 HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
1007 + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
1008 },
1009 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1010 {
1011 uint256 hash(ParseHashV(request.params[0], "blockhash"));
1012
1013 int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/1, /*allow_bool=*/true)};
1014
1015 const CBlockIndex* pblockindex;
1016 const CBlockIndex* tip;
1017 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1018 {
1019 LOCK(cs_main);
1020 pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
1021 tip = chainman.ActiveChain().Tip();
1022
1023 if (!pblockindex) {
1024 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1025 }
1026 }
1027
1028 const std::vector<uint8_t> block_data{GetRawBlockChecked(chainman.m_blockman, *pblockindex)};
1029
1030 if (verbosity <= 0) {
1031 return HexStr(block_data);
1032 }
1033
1034 DataStream block_stream{block_data};
1035 CBlock block{};
1036 block_stream >> TX_WITH_WITNESS(block);
1037
1038 TxVerbosity tx_verbosity;
1039 if (verbosity == 1) {
1040 tx_verbosity = TxVerbosity::SHOW_TXID;
1041 } else if (verbosity == 2) {
1042 tx_verbosity = TxVerbosity::SHOW_DETAILS;
1043 } else {
1044 tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT;
1045 }
1046
1047 return blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity, chainman.GetConsensus().powLimit);
1048 },
1049 };
1050 }
1051
1052 //! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
1053 std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& chain) {
1054 AssertLockHeld(::cs_main);
1055
1056 // Search for the last block missing block data or undo data. Don't let the
1057 // search consider the genesis block, because the genesis block does not
1058 // have undo data, but should not be considered pruned.
1059 const CBlockIndex* first_block{chain[1]};
1060 const CBlockIndex* chain_tip{chain.Tip()};
1061
1062 // If there are no blocks after the genesis block, or no blocks at all, nothing is pruned.
1063 if (!first_block || !chain_tip) return std::nullopt;
1064
1065 // If the chain tip is pruned, everything is pruned.
1066 if (!((chain_tip->nStatus & BLOCK_HAVE_MASK) == BLOCK_HAVE_MASK)) return chain_tip->nHeight;
1067
1068 const auto& first_unpruned{*CHECK_NONFATAL(blockman.GetFirstBlock(*chain_tip, /*status_mask=*/BLOCK_HAVE_MASK, first_block))};
1069 if (&first_unpruned == first_block) {
1070 // All blocks between first_block and chain_tip have data, so nothing is pruned.
1071 return std::nullopt;
1072 }
1073
1074 // Block before the first unpruned block is the last pruned block.
1075 return CHECK_NONFATAL(first_unpruned.pprev)->nHeight;
1076 }
1077
1078 static RPCHelpMan listprunelocks()
1079 {
1080 return RPCHelpMan{"listprunelocks",
1081 "\nReturns a list of pruning locks.\n",
1082 {},
1083 RPCResult{
1084 RPCResult::Type::OBJ, "", "",
1085 {
1086 {RPCResult::Type::ARR, "prune_locks", "",
1087 {
1088 {RPCResult::Type::OBJ, "", "",
1089 {
1090 {RPCResult::Type::STR, "id", "A unique identifier for the lock"},
1091 {RPCResult::Type::STR, "desc", "A description of the lock's purpose"},
1092 {RPCResult::Type::ARR_FIXED, "height", "Range of blocks prevented from being pruned",
1093 {
1094 {RPCResult::Type::NUM, "height_first", "Height of first block that may not be pruned"},
1095 {RPCResult::Type::NUM, "height_last", "Height of last block that may not be pruned (omitted if unbounded)"},
1096 }},
1097 {RPCResult::Type::BOOL, "temporary", "Indicates the lock will not remain after a restart of the node"},
1098 }},
1099 }},
1100 }
1101 },
1102 RPCExamples{
1103 HelpExampleCli("listprunelocks", "")
1104 + HelpExampleRpc("listprunelocks", "")
1105 },
1106 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1107 {
1108 NodeContext& node = EnsureAnyNodeContext(request.context);
1109 ChainstateManager& chainman = EnsureChainman(node);
1110 Chainstate& active_chainstate = chainman.ActiveChainstate();
1111
1112 UniValue locks_uv(UniValue::VARR);
1113 {
1114 LOCK(::cs_main);
1115 BlockManager * const blockman = &active_chainstate.m_blockman;
1116 for (const auto& prune_lock : blockman->m_prune_locks) {
1117 UniValue prune_lock_uv(UniValue::VOBJ);
1118 const auto& lock_info = prune_lock.second;
1119 prune_lock_uv.pushKV("id", prune_lock.first);
1120 prune_lock_uv.pushKV("desc", lock_info.desc);
1121 UniValue heights_uv(UniValue::VARR);
1122 heights_uv.push_back(lock_info.height_first);
1123 if (lock_info.height_last < std::numeric_limits<uint64_t>::max()) {
1124 heights_uv.push_back(lock_info.height_last);
1125 }
1126 prune_lock_uv.pushKV("height", heights_uv);
1127 prune_lock_uv.pushKV("temporary", lock_info.temporary);
1128 locks_uv.push_back(prune_lock_uv);
1129 }
1130 }
1131
1132 UniValue result(UniValue::VOBJ);
1133 result.pushKV("prune_locks", locks_uv);
1134 return result;
1135 },
1136 };
1137 }
1138
1139 static RPCHelpMan setprunelock()
1140 {
1141 return RPCHelpMan{"setprunelock",
1142 "\nManipulate pruning locks.\n",
1143 {
1144 {"id", RPCArg::Type::STR, RPCArg::Optional::NO, "The unique id of the manipulated prune lock (or \"*\" if deleting all)"},
1145 {"lock_info", RPCArg::Type::OBJ, RPCArg::Optional::NO, "An object describing the desired lock",
1146 {
1147 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Description of the lock"},
1148 {"height", RPCArg::Type::RANGE, RPCArg::DefaultHint("deletes the lock"), "The range of block heights to prevent pruning"},
1149 {"sync", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, success indicates the lock change was stored to disk (if non-temporary). If false, it is possible for a subsequent node crash to lose the lock."},
1150 {"temporary", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, the lock will not persist across node restart."},
1151 },
1152 },
1153 },
1154 RPCResult{
1155 RPCResult::Type::OBJ, "", "",
1156 {
1157 {RPCResult::Type::BOOL, "success", "Whether the change was successful"},
1158 }},
1159 RPCExamples{
1160 HelpExampleCli("setprunelock", "\"test\" \"{\\\"desc\\\": \\\"Just a test\\\", \\\"height\\\": [0,100]}\"")
1161 + HelpExampleCli("setprunelock", "\"test-2\" \"{\\\"desc\\\": \\\"Second RPC-created prunelock test\\\", \\\"height\\\": [100]}\"")
1162 + HelpExampleRpc("setprunelock", "\"test\", {\"desc\": \"Just a test\", \"height\": [0,100]}")
1163 },
1164 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1165 {
1166 NodeContext& node = EnsureAnyNodeContext(request.context);
1167 ChainstateManager& chainman = EnsureChainman(node);
1168 Chainstate& active_chainstate = chainman.ActiveChainstate();
1169
1170 const auto& lock_info_json = request.params[1];
1171 RPCTypeCheckObj(lock_info_json,
1172 {
1173 {"desc", UniValueType(UniValue::VSTR)},
1174 {"height", UniValueType()}, // will be checked below
1175 {"sync", UniValueType(UniValue::VBOOL)},
1176 {"temporary", UniValueType(UniValue::VBOOL)},
1177 },
1178 /*fAllowNull=*/ true, /*fStrict=*/ true);
1179
1180 const auto& lockid = request.params[0].get_str();
1181
1182 node::PruneLockInfo lock_info;
1183
1184 auto height_param = lock_info_json["height"];
1185 if (!height_param.isArray()) {
1186 UniValue new_height_param(UniValue::VARR);
1187 new_height_param.push_back(std::move(height_param));
1188 height_param = std::move(new_height_param);
1189 }
1190 bool success;
1191 if (height_param[0].isNull() && height_param[1].isNull()) {
1192 // Delete
1193 LOCK(::cs_main);
1194 BlockManager * const blockman = &active_chainstate.m_blockman;
1195 if (lockid == "*") {
1196 // Delete all
1197 success = true;
1198 std::vector<std::string> all_ids;
1199 all_ids.reserve(blockman->m_prune_locks.size());
1200 for (const auto& prune_lock : blockman->m_prune_locks) {
1201 all_ids.push_back(prune_lock.first);
1202 }
1203 for (auto& lockid : all_ids) {
1204 success |= blockman->DeletePruneLock(lockid);
1205 }
1206 } else {
1207 success = blockman->PruneLockExists(lockid) && blockman->DeletePruneLock(lockid);
1208 }
1209 } else {
1210 if (lockid == "*") throw JSONRPCError(RPC_INVALID_PARAMETER, "id \"*\" only makes sense when deleting");
1211 if (!height_param[0].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid start height");
1212 lock_info.height_first = height_param[0].getInt<uint64_t>();
1213 if (!height_param[1].isNull()) {
1214 if (!height_param[1].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid end height");
1215 lock_info.height_last = height_param[1].getInt<uint64_t>();
1216 }
1217 lock_info.desc = lock_info_json["desc"].get_str();
1218 if (lock_info_json["temporary"].isNull()) {
1219 lock_info.temporary = false;
1220 } else {
1221 lock_info.temporary = lock_info_json["temporary"].get_bool();
1222 }
1223 bool sync = false;
1224 if (!lock_info_json["sync"].isNull()) {
1225 sync = lock_info_json["sync"].get_bool();
1226 }
1227 LOCK(::cs_main);
1228 BlockManager * const blockman = &active_chainstate.m_blockman;
1229 success = blockman->UpdatePruneLock(lockid, lock_info, sync);
1230 }
1231
1232 UniValue result(UniValue::VOBJ);
1233 result.pushKV("success", success);
1234 return result;
1235 },
1236 };
1237 }
1238
1239 static RPCHelpMan pruneblockchain()
1240 {
1241 return RPCHelpMan{"pruneblockchain",
1242 "Attempts to delete block and undo data up to a specified height or timestamp, if eligible for pruning.\n"
1243 "Requires `-prune` to be enabled at startup. While pruned data may be re-fetched in some cases (e.g., via `getblockfrompeer`), local deletion is irreversible.\n",
1244 {
1245 {"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"
1246 " to prune blocks whose block time is at least 2 hours older than the provided timestamp."},
1247 },
1248 RPCResult{
1249 RPCResult::Type::NUM, "", "Height of the last block pruned"},
1250 RPCExamples{
1251 HelpExampleCli("pruneblockchain", "1000")
1252 + HelpExampleRpc("pruneblockchain", "1000")
1253 },
1254 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1255 {
1256 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1257 if (!chainman.m_blockman.IsPruneMode()) {
1258 throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode.");
1259 }
1260
1261 LOCK(cs_main);
1262 Chainstate& active_chainstate = chainman.ActiveChainstate();
1263 CChain& active_chain = active_chainstate.m_chain;
1264
1265 int heightParam = request.params[0].getInt<int>();
1266 if (heightParam < 0) {
1267 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height.");
1268 }
1269 if (heightParam == 0) {
1270 // Nothing to do here
1271 return uint64_t(0);
1272 }
1273
1274 // Height value more than a billion is too high to be a block height, and
1275 // too low to be a block time (corresponds to timestamp from Sep 2001).
1276 if (heightParam > 1000000000) {
1277 // Add a 2 hour buffer to include blocks which might have had old timestamps
1278 const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
1279 if (!pindex) {
1280 throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp.");
1281 }
1282 heightParam = pindex->nHeight;
1283 }
1284
1285 unsigned int height = (unsigned int) heightParam;
1286 unsigned int chainHeight = (unsigned int) active_chain.Height();
1287 if (chainHeight < chainman.GetParams().PruneAfterHeight()) {
1288 throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning.");
1289 } else if (height > chainHeight) {
1290 throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height.");
1291 } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
1292 LogDebug(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n");
1293 height = chainHeight - MIN_BLOCKS_TO_KEEP;
1294 }
1295
1296 PruneBlockFilesManual(active_chainstate, height);
1297 return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
1298 },
1299 };
1300 }
1301
1302 CoinStatsHashType ParseHashType(const std::string& hash_type_input)
1303 {
1304 if (hash_type_input == "hash_serialized_3") {
1305 return CoinStatsHashType::HASH_SERIALIZED;
1306 } else if (hash_type_input == "muhash") {
1307 return CoinStatsHashType::MUHASH;
1308 } else if (hash_type_input == "none") {
1309 return CoinStatsHashType::NONE;
1310 } else {
1311 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("'%s' is not a valid hash_type", hash_type_input));
1312 }
1313 }
1314
1315 /**
1316 * Calculate statistics about the unspent transaction output set
1317 *
1318 * @param[in] index_requested Signals if the coinstatsindex should be used (when available).
1319 */
1320 static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman,
1321 kernel::CoinStatsHashType hash_type,
1322 const std::function<void()>& interruption_point = {},
1323 const CBlockIndex* pindex = nullptr,
1324 bool index_requested = true)
1325 {
1326 // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
1327 if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) {
1328 if (pindex) {
1329 return g_coin_stats_index->LookUpStats(*pindex);
1330 } else {
1331 CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())));
1332 return g_coin_stats_index->LookUpStats(block_index);
1333 }
1334 }
1335
1336 // If the coinstats index isn't requested or is otherwise not usable, the
1337 // pindex should either be null or equal to the view's best block. This is
1338 // because without the coinstats index we can only get coinstats about the
1339 // best block.
1340 CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock());
1341
1342 return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
1343 }
1344
1345 static RPCHelpMan gettxoutsetinfo()
1346 {
1347 return RPCHelpMan{"gettxoutsetinfo",
1348 "\nReturns statistics about the unspent transaction output set.\n"
1349 "Note this call may take some time if you are not using coinstatsindex.\n",
1350 {
1351 {"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'."},
1352 {"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).",
1353 RPCArgOptions{
1354 .skip_type_check = true,
1355 .type_str = {"", "string or numeric"},
1356 }},
1357 {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
1358 },
1359 RPCResult{
1360 RPCResult::Type::OBJ, "", "",
1361 {
1362 {RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"},
1363 {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"},
1364 {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
1365 {RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
1366 {RPCResult::Type::STR_HEX, "hash_serialized_3", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_3' hash_type is chosen)"},
1367 {RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
1368 {RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
1369 {RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
1370 {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"},
1371 {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)"},
1372 {RPCResult::Type::OBJ, "block_info", /*optional=*/true, "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
1373 {
1374 {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
1375 {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
1376 {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
1377 {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
1378 {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
1379 {
1380 {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
1381 {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
1382 {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
1383 {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
1384 }}
1385 }},
1386 }},
1387 RPCExamples{
1388 HelpExampleCli("gettxoutsetinfo", "") +
1389 HelpExampleCli("gettxoutsetinfo", R"("none")") +
1390 HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
1391 HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
1392 HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") +
1393 HelpExampleRpc("gettxoutsetinfo", "") +
1394 HelpExampleRpc("gettxoutsetinfo", R"("none")") +
1395 HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
1396 HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")
1397 },
1398 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1399 {
1400 UniValue ret(UniValue::VOBJ);
1401
1402 const CBlockIndex* pindex{nullptr};
1403 const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
1404 bool index_requested = request.params[2].isNull() || request.params[2].get_bool();
1405
1406 NodeContext& node = EnsureAnyNodeContext(request.context);
1407 ChainstateManager& chainman = EnsureChainman(node);
1408 Chainstate& active_chainstate = chainman.ActiveChainstate();
1409 active_chainstate.ForceFlushStateToDisk();
1410
1411 CCoinsView* coins_view;
1412 BlockManager* blockman;
1413 {
1414 LOCK(::cs_main);
1415 coins_view = &active_chainstate.CoinsDB();
1416 blockman = &active_chainstate.m_blockman;
1417 pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
1418 }
1419
1420 if (!request.params[1].isNull()) {
1421 if (!g_coin_stats_index) {
1422 throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
1423 }
1424
1425 if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1426 throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_3 hash type cannot be queried for a specific block");
1427 }
1428
1429 if (!index_requested) {
1430 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block");
1431 }
1432 pindex = ParseHashOrHeight(request.params[1], chainman);
1433 }
1434
1435 if (index_requested && g_coin_stats_index) {
1436 if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
1437 const IndexSummary summary{g_coin_stats_index->GetSummary()};
1438
1439 // If a specific block was requested and the index has already synced past that height, we can return the
1440 // data already even though the index is not fully synced yet.
1441 if (pindex->nHeight > summary.best_block_height) {
1442 throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
1443 }
1444 }
1445 }
1446
1447 const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
1448 if (maybe_stats.has_value()) {
1449 const CCoinsStats& stats = maybe_stats.value();
1450 ret.pushKV("height", (int64_t)stats.nHeight);
1451 ret.pushKV("bestblock", stats.hashBlock.GetHex());
1452 ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
1453 ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1454 if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
1455 ret.pushKV("hash_serialized_3", stats.hashSerialized.GetHex());
1456 }
1457 if (hash_type == CoinStatsHashType::MUHASH) {
1458 ret.pushKV("muhash", stats.hashSerialized.GetHex());
1459 }
1460 CHECK_NONFATAL(stats.total_amount.has_value());
1461 ret.pushKV("total_amount", ValueFromAmount(stats.total_amount.value()));
1462 if (!stats.index_used) {
1463 ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
1464 ret.pushKV("disk_size", stats.nDiskSize);
1465 } else {
1466 ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
1467
1468 CCoinsStats prev_stats{};
1469 if (pindex->nHeight > 0) {
1470 const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested);
1471 if (!maybe_prev_stats) {
1472 throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1473 }
1474 prev_stats = maybe_prev_stats.value();
1475 }
1476
1477 UniValue block_info(UniValue::VOBJ);
1478 block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
1479 block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
1480 block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
1481 block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
1482
1483 UniValue unspendables(UniValue::VOBJ);
1484 unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
1485 unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
1486 unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
1487 unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
1488 block_info.pushKV("unspendables", std::move(unspendables));
1489
1490 ret.pushKV("block_info", std::move(block_info));
1491 }
1492 } else {
1493 throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1494 }
1495 return ret;
1496 },
1497 };
1498 }
1499
1500 static RPCHelpMan gettxout()
1501 {
1502 return RPCHelpMan{"gettxout",
1503 "\nReturns details about an unspent transaction output.\n",
1504 {
1505 {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
1506 {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
1507 {"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."},
1508 },
1509 {
1510 RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "", ""},
1511 RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", {
1512 {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
1513 {RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
1514 {RPCResult::Type::NUM, "confirmations_assumed", /*optional=*/true, "The number of unverified confirmations (eg, in an assumed-valid UTXO set)"},
1515 {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
1516 {RPCResult::Type::OBJ, "scriptPubKey", "", {
1517 {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
1518 {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
1519 {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
1520 {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
1521 {RPCResult::Type::STR, "address", /*optional=*/true, "The Limenka address (only if a well-defined address exists)"},
1522 }},
1523 {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
1524 }},
1525 },
1526 RPCExamples{
1527 "\nGet unspent transactions\n"
1528 + HelpExampleCli("listunspent", "") +
1529 "\nView the details\n"
1530 + HelpExampleCli("gettxout", "\"txid\" 1") +
1531 "\nAs a JSON-RPC call\n"
1532 + HelpExampleRpc("gettxout", "\"txid\", 1")
1533 },
1534 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1535 {
1536 NodeContext& node = EnsureAnyNodeContext(request.context);
1537 ChainstateManager& chainman = EnsureChainman(node);
1538 LOCK(cs_main);
1539
1540 UniValue ret(UniValue::VOBJ);
1541
1542 auto hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
1543 COutPoint out{hash, request.params[1].getInt<uint32_t>()};
1544 bool fMempool = true;
1545 if (!request.params[2].isNull())
1546 fMempool = request.params[2].get_bool();
1547
1548 Chainstate& active_chainstate = chainman.ActiveChainstate();
1549 CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
1550
1551 std::optional<Coin> coin;
1552 if (fMempool) {
1553 const CTxMemPool& mempool = EnsureMemPool(node);
1554 LOCK(mempool.cs);
1555 CCoinsViewMemPool view(coins_view, mempool);
1556 if (!mempool.isSpent(out)) coin = view.GetCoin(out);
1557 } else {
1558 coin = coins_view->GetCoin(out);
1559 }
1560 if (!coin) return UniValue::VNULL;
1561
1562 const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
1563 ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
1564 if (coin->nHeight == MEMPOOL_HEIGHT) {
1565 ret.pushKV("confirmations", 0);
1566 } else {
1567 const auto assumed_base_height = chainman.GetSnapshotBaseHeight();
1568 if (assumed_base_height && coin->nHeight < *assumed_base_height) {
1569 ret.pushKV("confirmations", 0);
1570 ret.pushKV("confirmations_assumed", (int64_t)(pindex->nHeight - coin->nHeight + 1));
1571 } else {
1572 ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin->nHeight + 1));
1573 }
1574 }
1575 ret.pushKV("value", ValueFromAmount(coin->out.nValue));
1576 UniValue o(UniValue::VOBJ);
1577 ScriptToUniv(coin->out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
1578 ret.pushKV("scriptPubKey", std::move(o));
1579 ret.pushKV("coinbase", (bool)coin->fCoinBase);
1580
1581 return ret;
1582 },
1583 };
1584 }
1585
1586 static RPCHelpMan verifychain()
1587 {
1588 return RPCHelpMan{"verifychain",
1589 "\nVerifies blockchain database.\n",
1590 {
1591 {"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
1592 strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))},
1593 {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
1594 },
1595 RPCResult{
1596 RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug log for reason."},
1597 RPCExamples{
1598 HelpExampleCli("verifychain", "")
1599 + HelpExampleRpc("verifychain", "")
1600 },
1601 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1602 {
1603 const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()};
1604 const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()};
1605
1606 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1607 LOCK(cs_main);
1608
1609 Chainstate& active_chainstate = chainman.ActiveChainstate();
1610 return CVerifyDB(chainman.GetNotifications()).VerifyDB(
1611 active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS;
1612 },
1613 };
1614 }
1615
1616 static RPCHelpMan scriptthreadsinfo()
1617 {
1618 return RPCHelpMan{"scriptthreadsinfo",
1619 "\nShow information about the script verification threads.\n",
1620 {},
1621 RPCResult{
1622 RPCResult::Type::OBJ, "", "",
1623 {
1624 {RPCResult::Type::BOOL, "enabled", "true if script verification threads are enabled (see setscriptthreadsenabled)."},
1625 {RPCResult::Type::NUM, "num_script_check_threads", "The total number of script verification threads, when enabled."},
1626 },
1627 },
1628 RPCExamples{
1629 HelpExampleCli("scriptthreadsinfo", "")
1630 + HelpExampleRpc("scriptthreadsinfo", "")
1631 },
1632 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1633 {
1634 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1635 UniValue ret(UniValue::VOBJ);
1636 size_t thread_count{chainman.m_script_check_queue_enabled ? chainman.GetCheckQueue().ThreadCount() : 0};
1637 ret.pushKV("enabled", (bool)thread_count);
1638 ret.pushKV("num_script_check_threads", (int64_t)thread_count + 1);
1639 return ret;
1640 },
1641 };
1642 }
1643
1644 static RPCHelpMan setscriptthreadsenabled()
1645 {
1646 return RPCHelpMan{"setscriptthreadsenabled",
1647 "\nDisable/enable script verification threads, thereby reducing CPU usage on multicore systems on demand.\n"
1648 "Disabling script verification threads may result in a significant slow-down during synchronisation.\n"
1649 "Has no effect on single core machines or if started with -par=<-<numcores>\n",
1650 {
1651 {"state", RPCArg::Type::BOOL, RPCArg::Optional::NO, "false if script verification threads should be disabled (true for re-enabling)"},
1652 },
1653 RPCResult{RPCResult::Type::NONE, "", ""},
1654 RPCExamples{
1655 HelpExampleCli("setscriptthreadsenabled", "false")
1656 + HelpExampleRpc("setscriptthreadsenabled", "false")
1657 },
1658 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1659 {
1660 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1661 LOCK(cs_main);
1662
1663 const bool parallel_script_checks{request.params[0].get_bool()};
1664 if (parallel_script_checks) {
1665 if (!chainman.GetCheckQueue().HasThreads()) {
1666 throw JSONRPCError(RPC_MISC_ERROR, "Script verification threads are disabled (single core machine or -par=<-<numcores>)");
1667 }
1668
1669 chainman.m_script_check_queue_enabled = true;
1670 } else {
1671 chainman.m_script_check_queue_enabled = false;
1672 }
1673
1674 return NullUniValue;
1675 },
1676 };
1677 }
1678
1679 static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep)
1680 {
1681 // For buried deployments.
1682
1683 if (!DeploymentEnabled(chainman, dep)) return;
1684
1685 UniValue rv(UniValue::VOBJ);
1686 rv.pushKV("type", "buried");
1687 // getdeploymentinfo reports the softfork as active from when the chain height is
1688 // one below the activation height
1689 rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep));
1690 rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep));
1691 softforks.pushKV(DeploymentName(dep), std::move(rv));
1692 }
1693
1694 static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id)
1695 {
1696 // For BIP9 deployments.
1697
1698 if (!DeploymentEnabled(chainman, id)) return;
1699 if (blockindex == nullptr) return;
1700
1701 auto get_state_name = [](const ThresholdState state) -> std::string {
1702 switch (state) {
1703 case ThresholdState::DEFINED: return "defined";
1704 case ThresholdState::STARTED: return "started";
1705 case ThresholdState::LOCKED_IN: return "locked_in";
1706 case ThresholdState::ACTIVE: return "active";
1707 case ThresholdState::FAILED: return "failed";
1708 case ThresholdState::EXPIRED: return "expired";
1709 }
1710 return "invalid";
1711 };
1712
1713 UniValue bip9(UniValue::VOBJ);
1714
1715 const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id);
1716 const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id);
1717
1718 const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state);
1719
1720 // BIP9 parameters
1721 if (has_signal) {
1722 bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit);
1723 }
1724 bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime);
1725 bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout);
1726 bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height);
1727 if (chainman.GetConsensus().vDeployments[id].max_activation_height < std::numeric_limits<int>::max()) {
1728 bip9.pushKV("max_activation_height", chainman.GetConsensus().vDeployments[id].max_activation_height);
1729 }
1730
1731 // BIP9 status
1732 bip9.pushKV("status", get_state_name(current_state));
1733 bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id));
1734 bip9.pushKV("status_next", get_state_name(next_state));
1735
1736 // BIP9 signalling status, if applicable
1737 if (has_signal) {
1738 UniValue statsUV(UniValue::VOBJ);
1739 std::vector<bool> signals;
1740 BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals);
1741 statsUV.pushKV("period", statsStruct.period);
1742 statsUV.pushKV("period_start", blockindex->nHeight + 1 - statsStruct.elapsed);
1743 statsUV.pushKV("elapsed", statsStruct.elapsed);
1744 statsUV.pushKV("count", statsStruct.count);
1745 if (ThresholdState::LOCKED_IN != current_state) {
1746 statsUV.pushKV("threshold", statsStruct.threshold);
1747 statsUV.pushKV("possible", statsStruct.possible);
1748 }
1749 bip9.pushKV("statistics", std::move(statsUV));
1750
1751 std::string sig;
1752 sig.reserve(signals.size());
1753 for (const bool s : signals) {
1754 sig.push_back(s ? '#' : '-');
1755 }
1756 bip9.pushKV("signalling", sig);
1757 }
1758
1759 UniValue rv(UniValue::VOBJ);
1760 rv.pushKV("type", "bip9");
1761 if (ThresholdState::ACTIVE == next_state) {
1762 const int activation_height = chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id);
1763 rv.pushKV("height", activation_height);
1764 // Add height_end for temporary softforks
1765 const auto& deployment = chainman.GetConsensus().vDeployments[id];
1766 if (deployment.active_duration < std::numeric_limits<int>::max()) {
1767 rv.pushKV("height_end", activation_height + deployment.active_duration - 1);
1768 }
1769 }
1770 rv.pushKV("active", ThresholdState::ACTIVE == next_state);
1771 rv.pushKV("bip9", std::move(bip9));
1772
1773 softforks.pushKV(DeploymentName(id), std::move(rv));
1774 }
1775
1776 // used by rest.cpp:rest_chaininfo, so cannot be static
1777 RPCHelpMan getblockchaininfo()
1778 {
1779 return RPCHelpMan{"getblockchaininfo",
1780 "Returns an object containing various state info regarding blockchain processing.\n",
1781 {},
1782 RPCResult{
1783 RPCResult::Type::OBJ, "", "",
1784 {
1785 {RPCResult::Type::STR, "chain", "current network name (" LIST_CHAIN_NAMES ")"},
1786 {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
1787 {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
1788 {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
1789 {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
1790 {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
1791 {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
1792 {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
1793 {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
1794 {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
1795 {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
1796 {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
1797 {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
1798 {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
1799 {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"},
1800 {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
1801 {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
1802 {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "the block challenge (aka. block script), in hexadecimal (only present if the current network is a signet)"},
1803 {RPCResult::Type::NUM, "fork_min_interval", /*optional=*/true, "rolling minimum fork block interval in seconds over the last 2016 fork blocks - the empirical sequential-delay floor (only present on the fork chain)"},
1804 {RPCResult::Type::NUM, "fork_delay_steps", /*optional=*/true, "the current sequential-delay step count (only present on the fork chain)"},
1805 (IsDeprecatedRPCEnabled("warnings") ?
1806 RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} :
1807 RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)",
1808 {
1809 {RPCResult::Type::STR, "", "warning"},
1810 }
1811 }
1812 ),
1813 }},
1814 RPCExamples{
1815 HelpExampleCli("getblockchaininfo", "")
1816 + HelpExampleRpc("getblockchaininfo", "")
1817 },
1818 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1819 {
1820 ChainstateManager& chainman = EnsureAnyChainman(request.context);
1821 LOCK(cs_main);
1822 Chainstate& active_chainstate = chainman.ActiveChainstate();
1823
1824 const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
1825 const int height{tip.nHeight};
1826 UniValue obj(UniValue::VOBJ);
1827 obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
1828 obj.pushKV("blocks", height);
1829 obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
1830 obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
1831 obj.pushKV("bits", strprintf("%08x", tip.nBits));
1832 obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex());
1833 obj.pushKV("difficulty", GetDifficulty(tip));
1834 obj.pushKV("time", tip.GetBlockTime());
1835 obj.pushKV("mediantime", tip.GetMedianTimePast());
1836 obj.pushKV("verificationprogress", chainman.GuessVerificationProgress(&tip));
1837 obj.pushKV("initialblockdownload", chainman.IsInitialBlockDownload());
1838 obj.pushKV("chainwork", tip.nChainWork.GetHex());
1839 obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
1840 obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
1841 if (chainman.m_blockman.IsPruneMode()) {
1842 const auto prune_height{GetPruneHeight(chainman.m_blockman, active_chainstate.m_chain)};
1843 obj.pushKV("pruneheight", prune_height ? prune_height.value() + 1 : 0);
1844
1845 const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
1846 obj.pushKV("automatic_pruning", automatic_pruning);
1847 if (automatic_pruning) {
1848 obj.pushKV("prune_target_size", chainman.m_blockman.GetPruneTarget());
1849 }
1850 }
1851 if (chainman.GetParams().GetChainType() == ChainType::SIGNET) {
1852 const std::vector<uint8_t>& signet_challenge =
1853 chainman.GetParams().GetConsensus().signet_challenge;
1854 obj.pushKV("signet_challenge", HexStr(signet_challenge));
1855 }
1856 if (chainman.GetParams().GetChainType() == ChainType::FORK) {
1857 // Empirical delay-floor measurement: the rolling minimum fork
1858 // block interval maps the fastest delay hardware in service.
1859 // When this drops far below the 60s nominal, a consensus K
1860 // revision is indicated (see PLAN_LOCKED.md).
1861 const auto min_interval = chainman.GetForkMinInterval(2016);
1862 if (min_interval.has_value()) {
1863 obj.pushKV("fork_min_interval", *min_interval);
1864 }
1865 obj.pushKV("fork_delay_steps", uint64_t{chainman.GetConsensus().nForkDelaySteps});
1866 }
1867
1868 NodeContext& node = EnsureAnyNodeContext(request.context);
1869 obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings")));
1870 return obj;
1871 },
1872 };
1873 }
1874
1875 namespace {
1876 const std::vector<RPCResult> RPCHelpForDeployment{
1877 {RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1878 {RPCResult::Type::NUM, "height", /*optional=*/true, "height of the first block which enforces the rules (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
1879 {RPCResult::Type::NUM, "height_end", /*optional=*/true, "height of the last block which enforces the rules (only for \"bip9\" type with \"active\" status and temporary deployments)"},
1880 {RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
1881 {RPCResult::Type::OBJ, "bip9", /*optional=*/true, "status of bip9 softforks (only for \"bip9\" type)",
1882 {
1883 {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)"},
1884 {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
1885 {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
1886 {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
1887 {RPCResult::Type::NUM, "max_activation_height", /*optional=*/true, "height at which the deployment will unconditionally activate (absent for miner-vetoable deployments)"},
1888 {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\", \"expired\")"},
1889 {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1890 {RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
1891 {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
1892 {
1893 {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
1894 {RPCResult::Type::NUM, "period_start", "height of the first block of this signalling period"},
1895 {RPCResult::Type::NUM, "threshold", /*optional=*/true, "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"},
1896 {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"},
1897 {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
1898 {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)"},
1899 }},
1900 {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"},
1901 }},
1902 };
1903
1904 UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman)
1905 {
1906 UniValue softforks(UniValue::VOBJ);
1907 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB);
1908 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG);
1909 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV);
1910 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV);
1911 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
1912 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
1913 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
1914 SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_REDUCED_DATA);
1915 return softforks;
1916 }
1917 } // anon namespace
1918
1919 RPCHelpMan getdeploymentinfo()
1920 {
1921 return RPCHelpMan{"getdeploymentinfo",
1922 "Returns an object containing various state info regarding deployments of consensus changes.",
1923 {
1924 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Default{"hash of current chain tip"}, "The block hash at which to query deployment state"},
1925 },
1926 RPCResult{
1927 RPCResult::Type::OBJ, "", "", {
1928 {RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
1929 {RPCResult::Type::NUM, "height", "requested block height (or tip)"},
1930 {RPCResult::Type::OBJ_DYN, "deployments", "", {
1931 {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
1932 }},
1933 }
1934 },
1935 RPCExamples{ HelpExampleCli("getdeploymentinfo", "") + HelpExampleRpc("getdeploymentinfo", "") },
1936 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1937 {
1938 const ChainstateManager& chainman = EnsureAnyChainman(request.context);
1939 LOCK(cs_main);
1940 const Chainstate& active_chainstate = chainman.ActiveChainstate();
1941
1942 const CBlockIndex* blockindex;
1943 if (request.params[0].isNull()) {
1944 blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
1945 } else {
1946 const uint256 hash(ParseHashV(request.params[0], "blockhash"));
1947 blockindex = chainman.m_blockman.LookupBlockIndex(hash);
1948 if (!blockindex) {
1949 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1950 }
1951 }
1952
1953 UniValue deploymentinfo(UniValue::VOBJ);
1954 deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString());
1955 deploymentinfo.pushKV("height", blockindex->nHeight);
1956 deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman));
1957 return deploymentinfo;
1958 },
1959 };
1960 }
1961
1962 /** Comparison function for sorting the getchaintips heads. */
1963 struct CompareBlocksByHeight
1964 {
1965 bool operator()(const CBlockIndex* a, const CBlockIndex* b) const
1966 {
1967 /* Make sure that unequal blocks with the same height do not compare
1968 equal. Use the pointers themselves to make a distinction. */
1969
1970 if (a->nHeight != b->nHeight)
1971 return (a->nHeight > b->nHeight);
1972
1973 return a < b;
1974 }
1975 };
1976
1977 static RPCHelpMan getchaintips()
1978 {
1979 return RPCHelpMan{"getchaintips",
1980 "Return information about all known tips in the block tree,"
1981 " including the main chain as well as orphaned branches.\n",
1982 {},
1983 RPCResult{
1984 RPCResult::Type::ARR, "", "",
1985 {{RPCResult::Type::OBJ, "", "",
1986 {
1987 {RPCResult::Type::NUM, "height", "height of the chain tip"},
1988 {RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
1989 {RPCResult::Type::NUM, "branchlen", "zero for main chain, otherwise length of branch connecting the tip to the main chain"},
1990 {RPCResult::Type::STR, "status", "status of the chain, \"active\" for the main chain\n"
1991 "Possible values for status:\n"
1992 "1. \"invalid\" This branch contains at least one invalid block\n"
1993 "2. \"headers-only\" Not all blocks for this branch are available, but the headers are valid\n"
1994 "3. \"valid-headers\" All blocks are available for this branch, but they were never fully validated\n"
1995 "4. \"valid-fork\" This branch is not part of the active chain, but is fully validated\n"
1996 "5. \"active\" This is the tip of the active main chain, which is certainly valid"},
1997 }}}},
1998 RPCExamples{
1999 HelpExampleCli("getchaintips", "")
2000 + HelpExampleRpc("getchaintips", "")
2001 },
2002 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2003 {
2004 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2005 LOCK(cs_main);
2006 CChain& active_chain = chainman.ActiveChain();
2007
2008 /*
2009 * Idea: The set of chain tips is the active chain tip, plus orphan blocks which do not have another orphan building off of them.
2010 * Algorithm:
2011 * - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
2012 * - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
2013 * - Add the active chain tip
2014 */
2015 std::set<const CBlockIndex*, CompareBlocksByHeight> setTips;
2016 std::set<const CBlockIndex*> setOrphans;
2017 std::set<const CBlockIndex*> setPrevs;
2018
2019 for (const auto& [_, block_index] : chainman.BlockIndex()) {
2020 if (!active_chain.Contains(&block_index)) {
2021 setOrphans.insert(&block_index);
2022 setPrevs.insert(block_index.pprev);
2023 }
2024 }
2025
2026 for (std::set<const CBlockIndex*>::iterator it = setOrphans.begin(); it != setOrphans.end(); ++it) {
2027 if (setPrevs.erase(*it) == 0) {
2028 setTips.insert(*it);
2029 }
2030 }
2031
2032 // Always report the currently active tip.
2033 setTips.insert(active_chain.Tip());
2034
2035 /* Construct the output array. */
2036 UniValue res(UniValue::VARR);
2037 for (const CBlockIndex* block : setTips) {
2038 UniValue obj(UniValue::VOBJ);
2039 obj.pushKV("height", block->nHeight);
2040 obj.pushKV("hash", block->phashBlock->GetHex());
2041
2042 const int branchLen = block->nHeight - active_chain.FindFork(block)->nHeight;
2043 obj.pushKV("branchlen", branchLen);
2044
2045 std::string status;
2046 if (active_chain.Contains(block)) {
2047 // This block is part of the currently active chain.
2048 status = "active";
2049 } else if (block->nStatus & BLOCK_FAILED_MASK) {
2050 // This block or one of its ancestors is invalid.
2051 status = "invalid";
2052 } else if (!block->HaveNumChainTxs()) {
2053 // This block cannot be connected because full block data for it or one of its parents is missing.
2054 status = "headers-only";
2055 } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) {
2056 // This block is fully validated, but no longer part of the active chain. It was probably the active block once, but was reorganized.
2057 status = "valid-fork";
2058 } else if (block->IsValid(BLOCK_VALID_TREE)) {
2059 // The headers for this block are valid, but it has not been validated. It was probably never part of the most-work chain.
2060 status = "valid-headers";
2061 } else {
2062 // No clue.
2063 status = "unknown";
2064 }
2065 obj.pushKV("status", status);
2066
2067 res.push_back(std::move(obj));
2068 }
2069
2070 return res;
2071 },
2072 };
2073 }
2074
2075 static RPCHelpMan preciousblock()
2076 {
2077 return RPCHelpMan{"preciousblock",
2078 "\nTreats a block as if it were received before others with the same work.\n"
2079 "\nA later preciousblock call can override the effect of an earlier one.\n"
2080 "\nThe effects of preciousblock are not retained across restarts.\n",
2081 {
2082 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as precious"},
2083 },
2084 RPCResult{RPCResult::Type::NONE, "", ""},
2085 RPCExamples{
2086 HelpExampleCli("preciousblock", "\"blockhash\"")
2087 + HelpExampleRpc("preciousblock", "\"blockhash\"")
2088 },
2089 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2090 {
2091 uint256 hash(ParseHashV(request.params[0], "blockhash"));
2092 CBlockIndex* pblockindex;
2093
2094 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2095 {
2096 LOCK(cs_main);
2097 pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
2098 if (!pblockindex) {
2099 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2100 }
2101 }
2102
2103 BlockValidationState state;
2104 chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
2105
2106 if (!state.IsValid()) {
2107 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2108 }
2109
2110 return UniValue::VNULL;
2111 },
2112 };
2113 }
2114
2115 void InvalidateBlock(ChainstateManager& chainman, const uint256 block_hash) {
2116 BlockValidationState state;
2117 CBlockIndex* pblockindex;
2118 {
2119 LOCK(chainman.GetMutex());
2120 pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
2121 if (!pblockindex) {
2122 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2123 }
2124 }
2125 chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
2126
2127 if (state.IsValid()) {
2128 chainman.ActiveChainstate().ActivateBestChain(state);
2129 }
2130
2131 if (!state.IsValid()) {
2132 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2133 }
2134 }
2135
2136 static RPCHelpMan invalidateblock()
2137 {
2138 return RPCHelpMan{"invalidateblock",
2139 "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n",
2140 {
2141 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"},
2142 },
2143 RPCResult{RPCResult::Type::NONE, "", ""},
2144 RPCExamples{
2145 HelpExampleCli("invalidateblock", "\"blockhash\"")
2146 + HelpExampleRpc("invalidateblock", "\"blockhash\"")
2147 },
2148 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2149 {
2150 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2151 uint256 hash(ParseHashV(request.params[0], "blockhash"));
2152
2153 InvalidateBlock(chainman, hash);
2154
2155 return UniValue::VNULL;
2156 },
2157 };
2158 }
2159
2160 void ReconsiderBlock(ChainstateManager& chainman, uint256 block_hash) {
2161 {
2162 LOCK(chainman.GetMutex());
2163 CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
2164 if (!pblockindex) {
2165 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2166 }
2167
2168 chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
2169 chainman.RecalculateBestHeader();
2170 }
2171
2172 BlockValidationState state;
2173 chainman.ActiveChainstate().ActivateBestChain(state);
2174
2175 if (!state.IsValid()) {
2176 throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
2177 }
2178 }
2179
2180 static RPCHelpMan reconsiderblock()
2181 {
2182 return RPCHelpMan{"reconsiderblock",
2183 "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n"
2184 "This can be used to undo the effects of invalidateblock.\n",
2185 {
2186 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to reconsider"},
2187 },
2188 RPCResult{RPCResult::Type::NONE, "", ""},
2189 RPCExamples{
2190 HelpExampleCli("reconsiderblock", "\"blockhash\"")
2191 + HelpExampleRpc("reconsiderblock", "\"blockhash\"")
2192 },
2193 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2194 {
2195 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2196 uint256 hash(ParseHashV(request.params[0], "blockhash"));
2197
2198 ReconsiderBlock(chainman, hash);
2199
2200 return UniValue::VNULL;
2201 },
2202 };
2203 }
2204
2205 static RPCHelpMan getchaintxstats()
2206 {
2207 return RPCHelpMan{"getchaintxstats",
2208 "\nCompute statistics about the total number and rate of transactions in the chain.\n",
2209 {
2210 {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"}, "Size of the window in number of blocks"},
2211 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"chain tip"}, "The hash of the block that ends the window."},
2212 },
2213 RPCResult{
2214 RPCResult::Type::OBJ, "", "",
2215 {
2216 {RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME},
2217 {RPCResult::Type::NUM, "txcount", /*optional=*/true,
2218 "The total number of transactions in the chain up to that point, if known. "
2219 "It may be unknown when using assumeutxo."},
2220 {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
2221 {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
2222 {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
2223 {RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
2224 {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true,
2225 "The number of transactions in the window. "
2226 "Only returned if \"window_block_count\" is > 0 and if txcount exists for the start and end of the window."},
2227 {RPCResult::Type::NUM, "txrate", /*optional=*/true,
2228 "The average rate of transactions per second in the window. "
2229 "Only returned if \"window_interval\" is > 0 and if window_tx_count exists."},
2230 }},
2231 RPCExamples{
2232 HelpExampleCli("getchaintxstats", "")
2233 + HelpExampleRpc("getchaintxstats", "2016")
2234 },
2235 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2236 {
2237 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2238 const CBlockIndex* pindex;
2239 int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month
2240
2241 if (request.params[1].isNull()) {
2242 LOCK(cs_main);
2243 pindex = chainman.ActiveChain().Tip();
2244 } else {
2245 uint256 hash(ParseHashV(request.params[1], "blockhash"));
2246 LOCK(cs_main);
2247 pindex = chainman.m_blockman.LookupBlockIndex(hash);
2248 if (!pindex) {
2249 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2250 }
2251 if (!chainman.ActiveChain().Contains(pindex)) {
2252 throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");
2253 }
2254 }
2255
2256 CHECK_NONFATAL(pindex != nullptr);
2257
2258 if (request.params[0].isNull()) {
2259 blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1));
2260 } else {
2261 blockcount = request.params[0].getInt<int>();
2262
2263 if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) {
2264 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1");
2265 }
2266 }
2267
2268 const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
2269 const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
2270
2271 UniValue ret(UniValue::VOBJ);
2272 ret.pushKV("time", (int64_t)pindex->nTime);
2273 if (pindex->m_chain_tx_count) {
2274 ret.pushKV("txcount", pindex->m_chain_tx_count);
2275 }
2276 ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
2277 ret.pushKV("window_final_block_height", pindex->nHeight);
2278 ret.pushKV("window_block_count", blockcount);
2279 if (blockcount > 0) {
2280 ret.pushKV("window_interval", nTimeDiff);
2281 if (pindex->m_chain_tx_count != 0 && past_block.m_chain_tx_count != 0) {
2282 const auto window_tx_count = pindex->m_chain_tx_count - past_block.m_chain_tx_count;
2283 ret.pushKV("window_tx_count", window_tx_count);
2284 if (nTimeDiff > 0) {
2285 ret.pushKV("txrate", double(window_tx_count) / nTimeDiff);
2286 }
2287 }
2288 }
2289
2290 return ret;
2291 },
2292 };
2293 }
2294
2295 template<typename T>
2296 static T CalculateTruncatedMedian(std::vector<T>& scores)
2297 {
2298 size_t size = scores.size();
2299 if (size == 0) {
2300 return 0;
2301 }
2302
2303 std::sort(scores.begin(), scores.end());
2304 if (size % 2 == 0) {
2305 return (scores[size / 2 - 1] + scores[size / 2]) / 2;
2306 } else {
2307 return scores[size / 2];
2308 }
2309 }
2310
2311 void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight)
2312 {
2313 if (scores.empty()) {
2314 return;
2315 }
2316
2317 std::sort(scores.begin(), scores.end());
2318
2319 // 10th, 25th, 50th, 75th, and 90th percentile weight units.
2320 const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = {
2321 total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0
2322 };
2323
2324 int64_t next_percentile_index = 0;
2325 int64_t cumulative_weight = 0;
2326 for (const auto& element : scores) {
2327 cumulative_weight += element.second;
2328 while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) {
2329 result[next_percentile_index] = element.first;
2330 ++next_percentile_index;
2331 }
2332 }
2333
2334 // Fill any remaining percentiles with the last value.
2335 for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
2336 result[i] = scores.back().first;
2337 }
2338 }
2339
2340 template<typename T>
2341 static inline bool SetHasKeys(const std::set<T>& set) {return false;}
2342 template<typename T, typename Tk, typename... Args>
2343 static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&... args)
2344 {
2345 return (set.count(key) != 0) || SetHasKeys(set, args...);
2346 }
2347
2348 // outpoint (needed for the utxo index) + nHeight + fCoinBase
2349 static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
2350
2351 static RPCHelpMan getblockstats()
2352 {
2353 return RPCHelpMan{"getblockstats",
2354 "\nCompute per block statistics for a given window. All amounts are in satoshis.\n"
2355 "It won't work for some heights with pruning.\n",
2356 {
2357 {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block",
2358 RPCArgOptions{
2359 .skip_type_check = true,
2360 .type_str = {"", "string or numeric"},
2361 }},
2362 {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)",
2363 {
2364 {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
2365 {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
2366 },
2367 RPCArgOptions{.oneline_description="stats"}},
2368 },
2369 RPCResult{
2370 RPCResult::Type::OBJ, "", "",
2371 {
2372 {RPCResult::Type::NUM, "avgfee", /*optional=*/true, "Average fee in the block"},
2373 {RPCResult::Type::NUM, "avgfeerate", /*optional=*/true, "Average feerate (in satoshis per virtual byte)"},
2374 {RPCResult::Type::NUM, "avgtxsize", /*optional=*/true, "Average transaction size"},
2375 {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash (to check for potential reorgs)"},
2376 {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)",
2377 {
2378 {RPCResult::Type::NUM, "10th_percentile_feerate", "The 10th percentile feerate"},
2379 {RPCResult::Type::NUM, "25th_percentile_feerate", "The 25th percentile feerate"},
2380 {RPCResult::Type::NUM, "50th_percentile_feerate", "The 50th percentile feerate"},
2381 {RPCResult::Type::NUM, "75th_percentile_feerate", "The 75th percentile feerate"},
2382 {RPCResult::Type::NUM, "90th_percentile_feerate", "The 90th percentile feerate"},
2383 }},
2384 {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the block"},
2385 {RPCResult::Type::NUM, "ins", /*optional=*/true, "The number of inputs (excluding coinbase)"},
2386 {RPCResult::Type::NUM, "maxfee", /*optional=*/true, "Maximum fee in the block"},
2387 {RPCResult::Type::NUM, "maxfeerate", /*optional=*/true, "Maximum feerate (in satoshis per virtual byte)"},
2388 {RPCResult::Type::NUM, "maxtxsize", /*optional=*/true, "Maximum transaction size"},
2389 {RPCResult::Type::NUM, "medianfee", /*optional=*/true, "Truncated median fee in the block"},
2390 {RPCResult::Type::NUM, "mediantime", /*optional=*/true, "The block median time past"},
2391 {RPCResult::Type::NUM, "mediantxsize", /*optional=*/true, "Truncated median transaction size"},
2392 {RPCResult::Type::NUM, "minfee", /*optional=*/true, "Minimum fee in the block"},
2393 {RPCResult::Type::NUM, "minfeerate", /*optional=*/true, "Minimum feerate (in satoshis per virtual byte)"},
2394 {RPCResult::Type::NUM, "mintxsize", /*optional=*/true, "Minimum transaction size"},
2395 {RPCResult::Type::NUM, "outs", /*optional=*/true, "The number of outputs"},
2396 {RPCResult::Type::NUM, "subsidy", /*optional=*/true, "The block subsidy"},
2397 {RPCResult::Type::NUM, "swtotal_size", /*optional=*/true, "Total size of all segwit transactions"},
2398 {RPCResult::Type::NUM, "swtotal_weight", /*optional=*/true, "Total weight of all segwit transactions"},
2399 {RPCResult::Type::NUM, "swtxs", /*optional=*/true, "The number of segwit transactions"},
2400 {RPCResult::Type::NUM, "time", /*optional=*/true, "The block time"},
2401 {RPCResult::Type::NUM, "total_out", /*optional=*/true, "Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])"},
2402 {RPCResult::Type::NUM, "total_size", /*optional=*/true, "Total size of all non-coinbase transactions"},
2403 {RPCResult::Type::NUM, "total_weight", /*optional=*/true, "Total weight of all non-coinbase transactions"},
2404 {RPCResult::Type::NUM, "totalfee", /*optional=*/true, "The fee total"},
2405 {RPCResult::Type::NUM, "txs", /*optional=*/true, "The number of transactions (including coinbase)"},
2406 {RPCResult::Type::NUM, "utxo_increase", /*optional=*/true, "The increase/decrease in the number of unspent outputs (not discounting op_return and similar)"},
2407 {RPCResult::Type::NUM, "utxo_size_inc", /*optional=*/true, "The increase/decrease in size for the utxo index (not discounting op_return and similar)"},
2408 {RPCResult::Type::NUM, "utxo_increase_actual", /*optional=*/true, "The increase/decrease in the number of unspent outputs, not counting unspendables"},
2409 {RPCResult::Type::NUM, "utxo_size_inc_actual", /*optional=*/true, "The increase/decrease in size for the utxo index, not counting unspendables"},
2410 }},
2411 RPCExamples{
2412 HelpExampleCli("getblockstats", R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
2413 HelpExampleCli("getblockstats", R"(1000 '["minfeerate","avgfeerate"]')") +
2414 HelpExampleRpc("getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
2415 HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])")
2416 },
2417 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2418 {
2419 ChainstateManager& chainman = EnsureAnyChainman(request.context);
2420 const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))};
2421
2422 std::set<std::string> stats;
2423 if (!request.params[1].isNull()) {
2424 const UniValue stats_univalue = request.params[1].get_array();
2425 for (unsigned int i = 0; i < stats_univalue.size(); i++) {
2426 const std::string stat = stats_univalue[i].get_str();
2427 stats.insert(stat);
2428 }
2429 }
2430
2431 const CBlock& block = GetBlockChecked(chainman.m_blockman, pindex);
2432 const CBlockUndo& blockUndo = GetUndoChecked(chainman.m_blockman, pindex);
2433
2434 const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
2435 const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
2436 const bool do_medianfee = do_all || stats.count("medianfee") != 0;
2437 const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
2438 const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles ||
2439 SetHasKeys(stats, "utxo_increase", "utxo_increase_actual", "utxo_size_inc", "utxo_size_inc_actual", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate");
2440 const bool loop_outputs = do_all || loop_inputs || stats.count("total_out");
2441 const bool do_calculate_size = do_mediantxsize ||
2442 SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size");
2443 const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate");
2444 const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");
2445
2446 CAmount maxfee = 0;
2447 CAmount maxfeerate = 0;
2448 CAmount minfee = MAX_MONEY;
2449 CAmount minfeerate = MAX_MONEY;
2450 CAmount total_out = 0;
2451 CAmount totalfee = 0;
2452 int64_t inputs = 0;
2453 int64_t maxtxsize = 0;
2454 int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE;
2455 int64_t outputs = 0;
2456 int64_t swtotal_size = 0;
2457 int64_t swtotal_weight = 0;
2458 int64_t swtxs = 0;
2459 int64_t total_size = 0;
2460 int64_t total_weight = 0;
2461 int64_t utxos = 0;
2462 int64_t utxo_size_inc = 0;
2463 int64_t utxo_size_inc_actual = 0;
2464 std::vector<CAmount> fee_array;
2465 std::vector<std::pair<CAmount, int64_t>> feerate_array;
2466 std::vector<int64_t> txsize_array;
2467
2468 for (size_t i = 0; i < block.vtx.size(); ++i) {
2469 const auto& tx = block.vtx.at(i);
2470 outputs += tx->vout.size();
2471
2472 CAmount tx_total_out = 0;
2473 if (loop_outputs) {
2474 for (const CTxOut& out : tx->vout) {
2475 tx_total_out += out.nValue;
2476
2477 size_t out_size = GetSerializeSize(out) + PER_UTXO_OVERHEAD;
2478 utxo_size_inc += out_size;
2479
2480 // The Genesis block and the repeated BIP30 block coinbases don't change the UTXO
2481 // set counts, so they have to be excluded from the statistics
2482 if (pindex.nHeight == 0 || (IsBIP30Repeat(pindex) && tx->IsCoinBase())) continue;
2483 // Skip unspendable outputs since they are not included in the UTXO set
2484 if (out.scriptPubKey.IsUnspendable()) continue;
2485
2486 ++utxos;
2487 utxo_size_inc_actual += out_size;
2488 }
2489 }
2490
2491 if (tx->IsCoinBase()) {
2492 continue;
2493 }
2494
2495 inputs += tx->vin.size(); // Don't count coinbase's fake input
2496 total_out += tx_total_out; // Don't count coinbase reward
2497
2498 int64_t tx_size = 0;
2499 if (do_calculate_size) {
2500
2501 tx_size = tx->GetTotalSize();
2502 if (do_mediantxsize) {
2503 txsize_array.push_back(tx_size);
2504 }
2505 maxtxsize = std::max(maxtxsize, tx_size);
2506 mintxsize = std::min(mintxsize, tx_size);
2507 total_size += tx_size;
2508 }
2509
2510 int64_t weight = 0;
2511 if (do_calculate_weight) {
2512 weight = GetTransactionWeight(*tx);
2513 total_weight += weight;
2514 }
2515
2516 if (do_calculate_sw && tx->HasWitness()) {
2517 ++swtxs;
2518 swtotal_size += tx_size;
2519 swtotal_weight += weight;
2520 }
2521
2522 if (loop_inputs) {
2523 CAmount tx_total_in = 0;
2524 const auto& txundo = blockUndo.vtxundo.at(i - 1);
2525 for (const Coin& coin: txundo.vprevout) {
2526 const CTxOut& prevoutput = coin.out;
2527
2528 tx_total_in += prevoutput.nValue;
2529 size_t prevout_size = GetSerializeSize(prevoutput) + PER_UTXO_OVERHEAD;
2530 utxo_size_inc -= prevout_size;
2531 utxo_size_inc_actual -= prevout_size;
2532 }
2533
2534 CAmount txfee = tx_total_in - tx_total_out;
2535 CHECK_NONFATAL(MoneyRange(txfee));
2536 if (do_medianfee) {
2537 fee_array.push_back(txfee);
2538 }
2539 maxfee = std::max(maxfee, txfee);
2540 minfee = std::min(minfee, txfee);
2541 totalfee += txfee;
2542
2543 // New feerate uses satoshis per virtual byte instead of per serialized byte
2544 CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0;
2545 if (do_feerate_percentiles) {
2546 feerate_array.emplace_back(feerate, weight);
2547 }
2548 maxfeerate = std::max(maxfeerate, feerate);
2549 minfeerate = std::min(minfeerate, feerate);
2550 }
2551 }
2552
2553 CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
2554 CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight);
2555
2556 UniValue feerates_res(UniValue::VARR);
2557 for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
2558 feerates_res.push_back(feerate_percentiles[i]);
2559 }
2560
2561 UniValue ret_all(UniValue::VOBJ);
2562 ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
2563 ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
2564 ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
2565 ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex());
2566 ret_all.pushKV("feerate_percentiles", std::move(feerates_res));
2567 ret_all.pushKV("height", (int64_t)pindex.nHeight);
2568 ret_all.pushKV("ins", inputs);
2569 ret_all.pushKV("maxfee", maxfee);
2570 ret_all.pushKV("maxfeerate", maxfeerate);
2571 ret_all.pushKV("maxtxsize", maxtxsize);
2572 ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
2573 ret_all.pushKV("mediantime", pindex.GetMedianTimePast());
2574 ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
2575 ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
2576 ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate);
2577 ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize);
2578 ret_all.pushKV("outs", outputs);
2579 ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus()));
2580 ret_all.pushKV("swtotal_size", swtotal_size);
2581 ret_all.pushKV("swtotal_weight", swtotal_weight);
2582 ret_all.pushKV("swtxs", swtxs);
2583 ret_all.pushKV("time", pindex.GetBlockTime());
2584 ret_all.pushKV("total_out", total_out);
2585 ret_all.pushKV("total_size", total_size);
2586 ret_all.pushKV("total_weight", total_weight);
2587 ret_all.pushKV("totalfee", totalfee);
2588 ret_all.pushKV("txs", (int64_t)block.vtx.size());
2589 ret_all.pushKV("utxo_increase", outputs - inputs);
2590 ret_all.pushKV("utxo_size_inc", utxo_size_inc);
2591 ret_all.pushKV("utxo_increase_actual", utxos - inputs);
2592 ret_all.pushKV("utxo_size_inc_actual", utxo_size_inc_actual);
2593
2594 if (do_all) {
2595 return ret_all;
2596 }
2597
2598 UniValue ret(UniValue::VOBJ);
2599 for (const std::string& stat : stats) {
2600 const UniValue& value = ret_all[stat];
2601 if (value.isNull()) {
2602 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic '%s'", stat));
2603 }
2604 ret.pushKV(stat, value);
2605 }
2606 return ret;
2607 },
2608 };
2609 }
2610
2611 //! Search for a given set of pubkey scripts
2612 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)
2613 {
2614 scan_progress = 0;
2615 count = 0;
2616 while (cursor->Valid()) {
2617 COutPoint key;
2618 Coin coin;
2619 if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
2620 if (++count % 8192 == 0) {
2621 interruption_point();
2622 if (should_abort) {
2623 // allow to abort the scan via the abort reference
2624 return false;
2625 }
2626 }
2627 if (count % 256 == 0) {
2628 // update progress reference every 256 item
2629 uint32_t high = 0x100 * *UCharCast(key.hash.begin()) + *(UCharCast(key.hash.begin()) + 1);
2630 scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
2631 }
2632 if (needles.count(coin.out.scriptPubKey)) {
2633 out_results.emplace(key, coin);
2634 }
2635 cursor->Next();
2636 }
2637 scan_progress = 100;
2638 return true;
2639 }
2640
2641 /** RAII object to prevent concurrency issue when scanning the txout set */
2642 static std::atomic<int> g_scan_progress;
2643 static std::atomic<bool> g_scan_in_progress;
2644 static std::atomic<bool> g_should_abort_scan;
2645 class CoinsViewScanReserver
2646 {
2647 private:
2648 bool m_could_reserve{false};
2649 public:
2650 explicit CoinsViewScanReserver() = default;
2651
2652 bool reserve() {
2653 CHECK_NONFATAL(!m_could_reserve);
2654 if (g_scan_in_progress.exchange(true)) {
2655 return false;
2656 }
2657 CHECK_NONFATAL(g_scan_progress == 0);
2658 m_could_reserve = true;
2659 return true;
2660 }
2661
2662 ~CoinsViewScanReserver() {
2663 if (m_could_reserve) {
2664 g_scan_in_progress = false;
2665 g_scan_progress = 0;
2666 }
2667 }
2668 };
2669
2670 static const auto scan_action_arg_desc = RPCArg{
2671 "action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"
2672 "\"start\" for starting a scan\n"
2673 "\"abort\" for aborting the current scan (returns true when abort was successful)\n"
2674 "\"status\" for progress report (in %) of the current scan"
2675 };
2676
2677 static const auto output_descriptor_obj = RPCArg{
2678 "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
2679 {
2680 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
2681 {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},
2682 }
2683 };
2684
2685 static const auto scan_objects_arg_desc = RPCArg{
2686 "scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"
2687 "Every scan object is either a string descriptor or an object:",
2688 {
2689 {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
2690 output_descriptor_obj,
2691 },
2692 RPCArgOptions{.oneline_description="[scanobjects,...]"},
2693 };
2694
2695 static const auto scan_result_abort = RPCResult{
2696 "when action=='abort'", RPCResult::Type::BOOL, "success",
2697 "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"
2698 };
2699 static const auto scan_result_status_none = RPCResult{
2700 "when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""
2701 };
2702 static const auto scan_result_status_some = RPCResult{
2703 "when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
2704 {{RPCResult::Type::NUM, "progress", "Approximate percent complete"},}
2705 };
2706
2707
2708 static RPCHelpMan scantxoutset()
2709 {
2710 // raw() descriptor corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
2711 const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";
2712
2713 return RPCHelpMan{"scantxoutset",
2714 "\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
2715 "Examples of output descriptors are:\n"
2716 " addr(<address>) Outputs whose output script corresponds to the specified address (does not include P2PK)\n"
2717 " raw(<hex script>) Outputs whose output script equals the specified hex-encoded bytes\n"
2718 " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
2719 " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
2720 " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
2721 " tr(<pubkey>) P2TR\n"
2722 " tr(<pubkey>,{pk(<pubkey>)}) P2TR with single fallback pubkey in tapscript\n"
2723 " rawtr(<pubkey>) P2TR with the specified key as output key rather than inner\n"
2724 " wsh(and_v(v:pk(<pubkey>),after(2))) P2WSH miniscript with mandatory pubkey and a timelock\n"
2725 "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
2726 "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
2727 "unhardened or hardened child keys.\n"
2728 "In the latter case, a range needs to be specified by below if different from 1000.\n"
2729 "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n",
2730 {
2731 scan_action_arg_desc,
2732 scan_objects_arg_desc,
2733 },
2734 {
2735 RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2736 {RPCResult::Type::BOOL, "success", "Whether the scan was completed"},
2737 {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},
2738 {RPCResult::Type::NUM, "height", "The block height at which the scan was done"},
2739 {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
2740 {RPCResult::Type::ARR, "unspents", "",
2741 {
2742 {RPCResult::Type::OBJ, "", "",
2743 {
2744 {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
2745 {RPCResult::Type::NUM, "vout", "The vout value"},
2746 {RPCResult::Type::STR_HEX, "scriptPubKey", "The output script"},
2747 {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched output script"},
2748 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
2749 {RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
2750 {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
2751 {RPCResult::Type::STR_HEX, "blockhash", "Blockhash of the unspent transaction output"},
2752 {RPCResult::Type::NUM, "confirmations", "Number of confirmations of the unspent transaction output when the scan was done"},
2753 }},
2754 }},
2755 {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
2756 }},
2757 scan_result_abort,
2758 scan_result_status_some,
2759 scan_result_status_none,
2760 },
2761 RPCExamples{
2762 HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
2763 HelpExampleCli("scantxoutset", "status") +
2764 HelpExampleCli("scantxoutset", "abort") +
2765 HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") +
2766 HelpExampleRpc("scantxoutset", "\"status\"") +
2767 HelpExampleRpc("scantxoutset", "\"abort\"")
2768 },
2769 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2770 {
2771 UniValue result(UniValue::VOBJ);
2772 const auto action{self.Arg<std::string>("action")};
2773 if (action == "status") {
2774 CoinsViewScanReserver reserver;
2775 if (reserver.reserve()) {
2776 // no scan in progress
2777 return UniValue::VNULL;
2778 }
2779 result.pushKV("progress", g_scan_progress.load());
2780 return result;
2781 } else if (action == "abort") {
2782 CoinsViewScanReserver reserver;
2783 if (reserver.reserve()) {
2784 // reserve was possible which means no scan was running
2785 return false;
2786 }
2787 // set the abort flag
2788 g_should_abort_scan = true;
2789 return true;
2790 } else if (action == "start") {
2791 CoinsViewScanReserver reserver;
2792 if (!reserver.reserve()) {
2793 throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
2794 }
2795
2796 if (request.params.size() < 2) {
2797 throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action");
2798 }
2799
2800 std::set<CScript> needles;
2801 std::map<CScript, std::string> descriptors;
2802 CAmount total_in = 0;
2803
2804 // loop through the scan objects
2805 for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
2806 FlatSigningProvider provider;
2807 auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
2808 for (CScript& script : scripts) {
2809 std::string inferred = InferDescriptor(script, provider)->ToString();
2810 needles.emplace(script);
2811 descriptors.emplace(std::move(script), std::move(inferred));
2812 }
2813 }
2814
2815 // Scan the unspent transaction output set for inputs
2816 UniValue unspents(UniValue::VARR);
2817 std::vector<CTxOut> input_txos;
2818 std::map<COutPoint, Coin> coins;
2819 g_should_abort_scan = false;
2820 int64_t count = 0;
2821 std::unique_ptr<CCoinsViewCursor> pcursor;
2822 const CBlockIndex* tip;
2823 NodeContext& node = EnsureAnyNodeContext(request.context);
2824 {
2825 ChainstateManager& chainman = EnsureChainman(node);
2826 LOCK(cs_main);
2827 Chainstate& active_chainstate = chainman.ActiveChainstate();
2828 active_chainstate.ForceFlushStateToDisk();
2829 pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
2830 tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
2831 }
2832 bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);
2833 result.pushKV("success", res);
2834 result.pushKV("txouts", count);
2835 result.pushKV("height", tip->nHeight);
2836 result.pushKV("bestblock", tip->GetBlockHash().GetHex());
2837
2838 for (const auto& it : coins) {
2839 const COutPoint& outpoint = it.first;
2840 const Coin& coin = it.second;
2841 const CTxOut& txo = coin.out;
2842 const CBlockIndex& coinb_block{*CHECK_NONFATAL(tip->GetAncestor(coin.nHeight))};
2843 input_txos.push_back(txo);
2844 total_in += txo.nValue;
2845
2846 UniValue unspent(UniValue::VOBJ);
2847 unspent.pushKV("txid", outpoint.hash.GetHex());
2848 unspent.pushKV("vout", outpoint.n);
2849 unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
2850 unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
2851 unspent.pushKV("amount", ValueFromAmount(txo.nValue));
2852 unspent.pushKV("coinbase", coin.IsCoinBase());
2853 unspent.pushKV("height", coin.nHeight);
2854 unspent.pushKV("blockhash", coinb_block.GetBlockHash().GetHex());
2855 unspent.pushKV("confirmations", tip->nHeight - coin.nHeight + 1);
2856
2857 unspents.push_back(std::move(unspent));
2858 }
2859 result.pushKV("unspents", std::move(unspents));
2860 result.pushKV("total_amount", ValueFromAmount(total_in));
2861 } else {
2862 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action));
2863 }
2864 return result;
2865 },
2866 };
2867 }
2868
2869 /** RAII object to prevent concurrency issue when scanning blockfilters */
2870 static std::atomic<int> g_scanfilter_progress;
2871 static std::atomic<int> g_scanfilter_progress_height;
2872 static std::atomic<bool> g_scanfilter_in_progress;
2873 static std::atomic<bool> g_scanfilter_should_abort_scan;
2874 class BlockFiltersScanReserver
2875 {
2876 private:
2877 bool m_could_reserve{false};
2878 public:
2879 explicit BlockFiltersScanReserver() = default;
2880
2881 bool reserve() {
2882 CHECK_NONFATAL(!m_could_reserve);
2883 if (g_scanfilter_in_progress.exchange(true)) {
2884 return false;
2885 }
2886 m_could_reserve = true;
2887 return true;
2888 }
2889
2890 void release() {
2891 if (!m_could_reserve) {
2892 throw std::runtime_error("Attempt to release unreserved BlockFiltersScanReserver");
2893 }
2894 g_scanfilter_in_progress = false;
2895 m_could_reserve = false;
2896 }
2897
2898 ~BlockFiltersScanReserver() {
2899 if (m_could_reserve) {
2900 release();
2901 }
2902 }
2903 };
2904
2905 static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles)
2906 {
2907 const CBlock block{GetBlockChecked(blockman, blockindex)};
2908 const CBlockUndo block_undo{GetUndoChecked(blockman, blockindex)};
2909
2910 // Check if any of the outputs match the scriptPubKey
2911 for (const auto& tx : block.vtx) {
2912 if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) {
2913 return needles.count(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end())) != 0;
2914 })) {
2915 return true;
2916 }
2917 }
2918 // Check if any of the inputs match the scriptPubKey
2919 for (const auto& txundo : block_undo.vtxundo) {
2920 if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) {
2921 return needles.count(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end())) != 0;
2922 })) {
2923 return true;
2924 }
2925 }
2926
2927 return false;
2928 }
2929
2930 static RPCHelpMan scanblocks()
2931 {
2932 return RPCHelpMan{"scanblocks",
2933 "\nReturn relevant blockhashes for given descriptors (requires blockfilterindex).\n"
2934 "This call may take several minutes. Make sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
2935 {
2936 scan_action_arg_desc,
2937 scan_objects_arg_desc,
2938 RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},
2939 RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},
2940 RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
2941 RPCArg{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
2942 {
2943 {"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"},
2944 },
2945 RPCArgOptions{.oneline_description="options"}},
2946 },
2947 {
2948 scan_result_status_none,
2949 RPCResult{"When action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
2950 {RPCResult::Type::NUM, "from_height", "The height we started the scan from"},
2951 {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"},
2952 {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {
2953 {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},
2954 }},
2955 {RPCResult::Type::BOOL, "completed", "true if the scan process was not aborted"}
2956 }},
2957 RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", {
2958 {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
2959 {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"},
2960 {RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {
2961 {RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},
2962 }},
2963 },
2964 },
2965 scan_result_abort,
2966 },
2967 RPCExamples{
2968 HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") +
2969 HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") +
2970 HelpExampleCli("scanblocks", "status") +
2971 HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") +
2972 HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") +
2973 HelpExampleRpc("scanblocks", "\"status\"")
2974 },
2975 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2976 {
2977 static GlobalMutex cs_relevant_blocks;
2978 static UniValue relevant_blocks GUARDED_BY(cs_relevant_blocks);
2979
2980 UniValue ret(UniValue::VOBJ);
2981 if (request.params[0].get_str() == "status") {
2982 BlockFiltersScanReserver reserver;
2983 LOCK(cs_relevant_blocks);
2984 if (reserver.reserve()) {
2985 // no scan in progress
2986 return NullUniValue;
2987 }
2988 ret.pushKV("progress", g_scanfilter_progress.load());
2989 ret.pushKV("current_height", g_scanfilter_progress_height.load());
2990 ret.pushKV("relevant_blocks", relevant_blocks);
2991 return ret;
2992 } else if (request.params[0].get_str() == "abort") {
2993 BlockFiltersScanReserver reserver;
2994 if (reserver.reserve()) {
2995 // reserve was possible which means no scan was running
2996 return false;
2997 }
2998 // set the abort flag
2999 g_scanfilter_should_abort_scan = true;
3000 return true;
3001 } else if (request.params[0].get_str() == "start") {
3002 {
3003 LOCK(cs_relevant_blocks);
3004 relevant_blocks = UniValue(UniValue::VARR);
3005 }
3006
3007 BlockFiltersScanReserver reserver;
3008 if (!reserver.reserve()) {
3009 throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
3010 }
3011 const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()};
3012
3013 BlockFilterType filtertype;
3014 if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
3015 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
3016 }
3017
3018 UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]};
3019 bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false};
3020
3021 BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
3022 if (!index) {
3023 throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
3024 }
3025
3026 NodeContext& node = EnsureAnyNodeContext(request.context);
3027 ChainstateManager& chainman = EnsureChainman(node);
3028
3029 // set the start-height
3030 const CBlockIndex* start_index = nullptr;
3031 const CBlockIndex* stop_block = nullptr;
3032 {
3033 LOCK(cs_main);
3034 CChain& active_chain = chainman.ActiveChain();
3035 start_index = active_chain.Genesis();
3036 stop_block = active_chain.Tip(); // If no stop block is provided, stop at the chain tip.
3037 if (!request.params[2].isNull()) {
3038 start_index = active_chain[request.params[2].getInt<int>()];
3039 if (!start_index) {
3040 throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");
3041 }
3042 }
3043 if (!request.params[3].isNull()) {
3044 stop_block = active_chain[request.params[3].getInt<int>()];
3045 if (!stop_block || stop_block->nHeight < start_index->nHeight) {
3046 throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");
3047 }
3048 }
3049 }
3050 CHECK_NONFATAL(start_index);
3051 CHECK_NONFATAL(stop_block);
3052
3053 // loop through the scan objects, add scripts to the needle_set
3054 GCSFilter::ElementSet needle_set;
3055 for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
3056 FlatSigningProvider provider;
3057 std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
3058 for (const CScript& script : scripts) {
3059 needle_set.emplace(script.begin(), script.end());
3060 }
3061 }
3062
3063 const int amount_per_chunk = 10000;
3064 std::vector<BlockFilter> filters;
3065 int start_block_height = start_index->nHeight; // for progress reporting
3066 const int total_blocks_to_process = stop_block->nHeight - start_block_height;
3067
3068 g_scanfilter_should_abort_scan = false;
3069 g_scanfilter_progress = 0;
3070 g_scanfilter_progress_height = start_block_height;
3071 bool completed = true;
3072
3073 const CBlockIndex* end_range = nullptr;
3074 do {
3075 node.rpc_interruption_point(); // allow a clean shutdown
3076 if (g_scanfilter_should_abort_scan) {
3077 completed = false;
3078 break;
3079 }
3080
3081 // split the lookup range in chunks if we are deeper than 'amount_per_chunk' blocks from the stopping block
3082 int start_block = !end_range ? start_index->nHeight : start_index->nHeight + 1; // to not include the previous round 'end_range' block
3083 end_range = (start_block + amount_per_chunk < stop_block->nHeight) ?
3084 WITH_LOCK(::cs_main, return chainman.ActiveChain()[start_block + amount_per_chunk]) :
3085 stop_block;
3086
3087 if (index->LookupFilterRange(start_block, end_range, filters)) {
3088 for (const BlockFilter& filter : filters) {
3089 // compare the elements-set with each filter
3090 if (filter.GetFilter().MatchAny(needle_set)) {
3091 if (filter_false_positives) {
3092 // Double check the filter matches by scanning the block
3093 const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash())));
3094
3095 if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) {
3096 continue;
3097 }
3098 }
3099
3100 LOCK(cs_relevant_blocks);
3101 relevant_blocks.push_back(filter.GetBlockHash().GetHex());
3102 }
3103 }
3104 }
3105 start_index = end_range;
3106
3107 // update progress
3108 int blocks_processed = end_range->nHeight - start_block_height;
3109 if (total_blocks_to_process > 0) { // avoid division by zero
3110 g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
3111 } else {
3112 g_scanfilter_progress = 100;
3113 }
3114 g_scanfilter_progress_height = end_range->nHeight;
3115
3116 // Finish if we reached the stop block
3117 } while (start_index != stop_block);
3118
3119 ret.pushKV("from_height", start_block_height);
3120 ret.pushKV("to_height", start_index->nHeight); // start_index is always the last scanned block here
3121 LOCK(cs_relevant_blocks);
3122 ret.pushKV("relevant_blocks", std::move(relevant_blocks));
3123 ret.pushKV("completed", completed);
3124 reserver.release(); // ensure this is before cs_relevant_blocks is released, so status doesn't try to use moved relevant_blocks
3125 }
3126 else {
3127 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
3128 }
3129 return ret;
3130 },
3131 };
3132 }
3133
3134 static RPCHelpMan getdescriptoractivity()
3135 {
3136 return RPCHelpMan{"getdescriptoractivity",
3137 "\nGet spend and receive activity associated with a set of descriptors for a set of blocks. "
3138 "This command pairs well with the `relevant_blocks` output of `scanblocks()`.\n"
3139 "This call may take several minutes. If you encounter timeouts, try specifying no RPC timeout (limenka-cli -rpcclienttimeout=0)",
3140 {
3141 RPCArg{"blockhashes", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of blockhashes to examine for activity. Order doesn't matter. Must be along main chain or an error is thrown.\n", {
3142 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A valid blockhash"},
3143 }},
3144 RPCArg{"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of descriptors (scan objects) to examine for activity. Every scan object is either a string descriptor or an object:",
3145 {
3146 {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
3147 output_descriptor_obj,
3148 },
3149 RPCArgOptions{.oneline_description="[scanobjects,...]"},
3150 },
3151 {"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include unconfirmed activity"},
3152 },
3153 RPCResult{
3154 RPCResult::Type::OBJ, "", "", {
3155 {RPCResult::Type::ARR, "activity", "events", {
3156 {RPCResult::Type::OBJ, "", "", {
3157 {RPCResult::Type::STR, "type", "always 'spend'"},
3158 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the spent output"},
3159 {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The blockhash this spend appears in (omitted if unconfirmed)"},
3160 {RPCResult::Type::NUM, "height", /*optional=*/true, "Height of the spend (omitted if unconfirmed)"},
3161 {RPCResult::Type::STR_HEX, "spend_txid", "The txid of the spending transaction"},
3162 {RPCResult::Type::NUM, "spend_vin", "The input index of the spend"},
3163 {RPCResult::Type::STR_HEX, "prevout_txid", "The txid of the prevout"},
3164 {RPCResult::Type::NUM, "prevout_vout", "The vout of the prevout"},
3165 {RPCResult::Type::OBJ, "prevout_spk", "", ScriptPubKeyDoc()},
3166 }},
3167 {RPCResult::Type::OBJ, "", "", {
3168 {RPCResult::Type::STR, "type", "always 'receive'"},
3169 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the new output"},
3170 {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block that this receive is in (omitted if unconfirmed)"},
3171 {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the receive (omitted if unconfirmed)"},
3172 {RPCResult::Type::STR_HEX, "txid", "The txid of the receiving transaction"},
3173 {RPCResult::Type::NUM, "vout", "The vout of the receiving output"},
3174 {RPCResult::Type::OBJ, "output_spk", "", ScriptPubKeyDoc()},
3175 }},
3176 // TODO is the skip_type_check avoidable with a heterogeneous ARR?
3177 }, /*skip_type_check=*/true},
3178 },
3179 },
3180 RPCExamples{
3181 HelpExampleCli("getdescriptoractivity", "'[\"000000000000000000001347062c12fded7c528943c8ce133987e2e2f5a840ee\"]' '[\"addr(bc1qzl6nsgqzu89a66l50cvwapnkw5shh23zarqkw9)\"]'")
3182 },
3183 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3184 {
3185 UniValue ret(UniValue::VOBJ);
3186 UniValue activity(UniValue::VARR);
3187 NodeContext& node = EnsureAnyNodeContext(request.context);
3188 ChainstateManager& chainman = EnsureChainman(node);
3189
3190 struct CompareByHeightAscending {
3191 bool operator()(const CBlockIndex* a, const CBlockIndex* b) const {
3192 return a->nHeight < b->nHeight;
3193 }
3194 };
3195
3196 std::set<const CBlockIndex*, CompareByHeightAscending> blockindexes_sorted;
3197
3198 {
3199 // Validate all given blockhashes, and ensure blocks are along a single chain.
3200 LOCK(::cs_main);
3201 for (const UniValue& blockhash : request.params[0].get_array().getValues()) {
3202 uint256 bhash = ParseHashV(blockhash, "blockhash");
3203 CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(bhash);
3204 if (!pindex) {
3205 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
3206 }
3207 if (!chainman.ActiveChain().Contains(pindex)) {
3208 throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");
3209 }
3210 blockindexes_sorted.insert(pindex);
3211 }
3212 }
3213
3214 std::set<CScript> scripts_to_watch;
3215
3216 // Determine scripts to watch.
3217 for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
3218 FlatSigningProvider provider;
3219 std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
3220
3221 for (const CScript& script : scripts) {
3222 scripts_to_watch.insert(script);
3223 }
3224 }
3225
3226 const auto AddSpend = [&](
3227 const CScript& spk,
3228 const CAmount val,
3229 const CTransactionRef& tx,
3230 int vin,
3231 const CTxIn& txin,
3232 const CBlockIndex* index
3233 ) {
3234 UniValue event(UniValue::VOBJ);
3235 UniValue spkUv(UniValue::VOBJ);
3236 ScriptToUniv(spk, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);
3237
3238 event.pushKV("type", "spend");
3239 event.pushKV("amount", ValueFromAmount(val));
3240 if (index) {
3241 event.pushKV("blockhash", index->GetBlockHash().ToString());
3242 event.pushKV("height", index->nHeight);
3243 }
3244 event.pushKV("spend_txid", tx->GetHash().ToString());
3245 event.pushKV("spend_vin", vin);
3246 event.pushKV("prevout_txid", txin.prevout.hash.ToString());
3247 event.pushKV("prevout_vout", txin.prevout.n);
3248 event.pushKV("prevout_spk", spkUv);
3249
3250 return event;
3251 };
3252
3253 const auto AddReceive = [&](const CTxOut& txout, const CBlockIndex* index, int vout, const CTransactionRef& tx) {
3254 UniValue event(UniValue::VOBJ);
3255 UniValue spkUv(UniValue::VOBJ);
3256 ScriptToUniv(txout.scriptPubKey, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);
3257
3258 event.pushKV("type", "receive");
3259 event.pushKV("amount", ValueFromAmount(txout.nValue));
3260 if (index) {
3261 event.pushKV("blockhash", index->GetBlockHash().ToString());
3262 event.pushKV("height", index->nHeight);
3263 }
3264 event.pushKV("txid", tx->GetHash().ToString());
3265 event.pushKV("vout", vout);
3266 event.pushKV("output_spk", spkUv);
3267
3268 return event;
3269 };
3270
3271 BlockManager* blockman;
3272 Chainstate& active_chainstate = chainman.ActiveChainstate();
3273 {
3274 LOCK(::cs_main);
3275 blockman = CHECK_NONFATAL(&active_chainstate.m_blockman);
3276 }
3277
3278 for (const CBlockIndex* blockindex : blockindexes_sorted) {
3279 const CBlock block{GetBlockChecked(chainman.m_blockman, *blockindex)};
3280 const CBlockUndo block_undo{GetUndoChecked(*blockman, *blockindex)};
3281
3282 for (size_t i = 0; i < block.vtx.size(); ++i) {
3283 const auto& tx = block.vtx.at(i);
3284
3285 if (!tx->IsCoinBase()) {
3286 // skip coinbase; spends can't happen there.
3287 const auto& txundo = block_undo.vtxundo.at(i - 1);
3288
3289 for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {
3290 const auto& coin = txundo.vprevout.at(vin_idx);
3291 const auto& txin = tx->vin.at(vin_idx);
3292 if (scripts_to_watch.contains(coin.out.scriptPubKey)) {
3293 activity.push_back(AddSpend(
3294 coin.out.scriptPubKey, coin.out.nValue, tx, vin_idx, txin, blockindex));
3295 }
3296 }
3297 }
3298
3299 for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {
3300 const auto& vout = tx->vout.at(vout_idx);
3301 if (scripts_to_watch.contains(vout.scriptPubKey)) {
3302 activity.push_back(AddReceive(vout, blockindex, vout_idx, tx));
3303 }
3304 }
3305 }
3306 }
3307
3308 bool search_mempool = true;
3309 if (!request.params[2].isNull()) {
3310 search_mempool = request.params[2].get_bool();
3311 }
3312
3313 if (search_mempool) {
3314 const CTxMemPool& mempool = EnsureMemPool(node);
3315 LOCK(::cs_main);
3316 LOCK(mempool.cs);
3317 const CCoinsViewCache& coins_view = &active_chainstate.CoinsTip();
3318
3319 for (const CTxMemPoolEntry& e : mempool.entryAll()) {
3320 const auto& tx = e.GetSharedTx();
3321
3322 for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {
3323 CScript scriptPubKey;
3324 CAmount value;
3325 const auto& txin = tx->vin.at(vin_idx);
3326 std::optional<Coin> coin = coins_view.GetCoin(txin.prevout);
3327
3328 // Check if the previous output is in the chain
3329 if (!coin) {
3330 // If not found in the chain, check the mempool. Likely, this is a
3331 // child transaction of another transaction in the mempool.
3332 CTransactionRef prev_tx = CHECK_NONFATAL(mempool.get(txin.prevout.hash));
3333
3334 if (txin.prevout.n >= prev_tx->vout.size()) {
3335 throw std::runtime_error("Invalid output index");
3336 }
3337 const CTxOut& out = prev_tx->vout[txin.prevout.n];
3338 scriptPubKey = out.scriptPubKey;
3339 value = out.nValue;
3340 } else {
3341 // Coin found in the chain
3342 const CTxOut& out = coin->out;
3343 scriptPubKey = out.scriptPubKey;
3344 value = out.nValue;
3345 }
3346
3347 if (scripts_to_watch.contains(scriptPubKey)) {
3348 UniValue event(UniValue::VOBJ);
3349 activity.push_back(AddSpend(
3350 scriptPubKey, value, tx, vin_idx, txin, nullptr));
3351 }
3352 }
3353
3354 for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {
3355 const auto& vout = tx->vout.at(vout_idx);
3356 if (scripts_to_watch.contains(vout.scriptPubKey)) {
3357 activity.push_back(AddReceive(vout, nullptr, vout_idx, tx));
3358 }
3359 }
3360 }
3361 }
3362
3363 ret.pushKV("activity", activity);
3364 return ret;
3365 },
3366 };
3367 }
3368
3369 static RPCHelpMan getblockfilter()
3370 {
3371 return RPCHelpMan{"getblockfilter",
3372 "\nRetrieve a BIP 157 content filter for a particular block.\n",
3373 {
3374 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},
3375 {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter, values: " + ListBlockFilterTypes()},
3376 },
3377 RPCResult{
3378 RPCResult::Type::OBJ, "", "",
3379 {
3380 {RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"},
3381 {RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"},
3382 }},
3383 RPCExamples{
3384 HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") +
3385 HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\", \"basic\"")
3386 },
3387 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3388 {
3389 uint256 block_hash = ParseHashV(request.params[0], "blockhash");
3390 std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC);
3391 if (!request.params[1].isNull()) {
3392 filtertype_name = request.params[1].get_str();
3393 }
3394
3395 BlockFilterType filtertype;
3396 if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
3397 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
3398 }
3399
3400 BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
3401 if (!index) {
3402 throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
3403 }
3404
3405 const CBlockIndex* block_index;
3406 bool block_was_connected;
3407 {
3408 ChainstateManager& chainman = EnsureAnyChainman(request.context);
3409 LOCK(cs_main);
3410 block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
3411 if (!block_index) {
3412 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
3413 }
3414 block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
3415 }
3416
3417 bool index_ready = index->BlockUntilSyncedToCurrentChain();
3418
3419 BlockFilter filter;
3420 uint256 filter_header;
3421 if (!index->LookupFilter(block_index, filter) ||
3422 !index->LookupFilterHeader(block_index, filter_header)) {
3423 int err_code;
3424 std::string errmsg = "Filter not found.";
3425
3426 if (!block_was_connected) {
3427 err_code = RPC_INVALID_ADDRESS_OR_KEY;
3428 errmsg += " Block was not connected to active chain.";
3429 } else if (!index_ready) {
3430 err_code = RPC_MISC_ERROR;
3431 errmsg += " Block filters are still in the process of being indexed.";
3432 } else {
3433 err_code = RPC_INTERNAL_ERROR;
3434 errmsg += " This error is unexpected and indicates index corruption.";
3435 }
3436
3437 throw JSONRPCError(err_code, errmsg);
3438 }
3439
3440 UniValue ret(UniValue::VOBJ);
3441 ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
3442 ret.pushKV("header", filter_header.GetHex());
3443 return ret;
3444 },
3445 };
3446 }
3447
3448 /**
3449 * RAII class that disables the network in its constructor and enables it in its
3450 * destructor.
3451 */
3452 class NetworkDisable
3453 {
3454 CConnman& m_connman;
3455 public:
3456 NetworkDisable(CConnman& connman) : m_connman(connman) {
3457 m_connman.SetNetworkActive(false);
3458 if (m_connman.GetNetworkActive()) {
3459 throw JSONRPCError(RPC_MISC_ERROR, "Network activity could not be suspended.");
3460 }
3461 };
3462 ~NetworkDisable() {
3463 m_connman.SetNetworkActive(true);
3464 };
3465 };
3466
3467 /**
3468 * RAII class that temporarily rolls back the local chain in it's constructor
3469 * and rolls it forward again in it's destructor.
3470 */
3471 class TemporaryRollback
3472 {
3473 ChainstateManager& m_chainman;
3474 const CBlockIndex& m_invalidate_index;
3475 public:
3476 TemporaryRollback(ChainstateManager& chainman, const CBlockIndex& index) : m_chainman(chainman), m_invalidate_index(index) {
3477 InvalidateBlock(m_chainman, m_invalidate_index.GetBlockHash());
3478 };
3479 ~TemporaryRollback() {
3480 ReconsiderBlock(m_chainman, m_invalidate_index.GetBlockHash());
3481 };
3482 };
3483
3484 /**
3485 * Serialize the UTXO set to a file for loading elsewhere.
3486 *
3487 * @see SnapshotMetadata
3488 */
3489 static RPCHelpMan dumptxoutset()
3490 {
3491 static const std::vector<std::pair<std::string, coinascii_cb_t>> ascii_types{
3492 {"txid", [](const COutPoint& k, const Coin& c) { return k.hash.GetHex(); }},
3493 {"vout", [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<int32_t>(k.n)); }},
3494 {"value", [](const COutPoint& k, const Coin& c) { return util::ToString(c.out.nValue); }},
3495 {"coinbase", [](const COutPoint& k, const Coin& c) { return util::ToString(c.fCoinBase); }},
3496 {"height", [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<uint32_t>(c.nHeight)); }},
3497 {"scriptPubKey", [](const COutPoint& k, const Coin& c) { return HexStr(c.out.scriptPubKey); }},
3498 // add any other desired items here
3499 };
3500
3501 std::vector<RPCArg> ascii_args;
3502 std::transform(std::begin(ascii_types), std::end(ascii_types), std::back_inserter(ascii_args),
3503 [](const std::pair<std::string, coinascii_cb_t>& t) { return RPCArg{t.first, RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Info to write for a given UTXO"}; });
3504
3505 return RPCHelpMan{
3506 "dumptxoutset",
3507 "Write the UTXO set to a file. This can be used in loadtxoutset afterwards if this snapshot height is supported in the chainparams as well.\n\n"
3508 "Unless the \"latest\" type is requested, the node will roll back to the requested height and network activity will be suspended during this process. "
3509 "Because of this it is discouraged to interact with the node in any other way during the execution of this call to avoid inconsistent results and race conditions, particularly RPCs that interact with blockstorage.\n\n"
3510 "This call may take several minutes. Make sure to use no RPC timeout (limenka-cli -rpcclienttimeout=0)",
3511 {
3512 {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
3513 {"type|format", {RPCArg::Type::STR, RPCArg::Type::ARR}, RPCArg::Default(""), "The type of snapshot to create. Can be \"latest\" to create a snapshot of the current UTXO set or \"rollback\" to temporarily roll back the state of the node to a historical block before creating the snapshot of a historical UTXO set. This parameter can be omitted if a separate \"rollback\" named parameter is specified indicating the height or hash of a specific historical block. If \"rollback\" is specified and separate \"rollback\" named parameter is not specified, this will roll back to the latest valid snapshot block that can currently be loaded with loadtxoutset."},
3514 {"options|show_header", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::BOOL}, RPCArg::Optional::OMITTED, "",
3515 {
3516 {"format", RPCArg::Type::ARR, RPCArg::DefaultHint{"compact serialized format"},
3517 "If no argument is provided, a compact binary serialized format is used; otherwise only requested items "
3518 "available below are written in ASCII format (if an empty array is provided, all items are written in ASCII).",
3519 ascii_args,
3520 RPCArgOptions{.oneline_description="format", .also_positional = true}},
3521 {"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
3522 "Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case.",
3523 RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},
3524 {"show_header", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include the header line in non-serialized (ASCII) mode", RPCArgOptions{.also_positional = true}},
3525 {"separator", RPCArg::Type::STR, RPCArg::Default{","}, "Field separator to use in non-serialized (ASCII) mode", RPCArgOptions{.also_positional = true}},
3526 },
3527 },
3528 {"separator", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.hidden=true}},
3529 },
3530 RPCResult{
3531 RPCResult::Type::OBJ, "", "",
3532 {
3533 {RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"},
3534 {RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"},
3535 {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
3536 {RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"},
3537 {RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"},
3538 {RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"},
3539 }
3540 },
3541 RPCExamples{
3542 HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +
3543 HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +
3544 HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)")
3545 +
3546 HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", "utxo.dat type=latest format='[]'") +
3547 HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", "utxo.dat type=latest format='[\"txid\", \"vout\"]' show_header=false separator=':'")
3548 },
3549 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3550 {
3551 EnsureNotWalletRestricted(request);
3552
3553 NodeContext& node = EnsureAnyNodeContext(request.context);
3554 const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
3555 const CBlockIndex* target_index{nullptr};
3556 std::string snapshot_type;
3557 UniValue options{request.params[2].isObject() ? request.params[2] : UniValue::VOBJ};
3558
3559 const UniValue& hr_format = [&]() -> const UniValue& {
3560 if (options["format"].isNull() && request.params[1].isArray()) {
3561 // Knots 0.20.0-28.1 compatibility
3562 snapshot_type = "latest";
3563 return request.params[1];
3564 }
3565 snapshot_type = self.Arg<std::string>("type");
3566 return options["format"];
3567 }();
3568 const bool is_human_readable = !hr_format.isNull();
3569 const bool show_header = [&] {
3570 if (!options["show_header"].isNull()) { // only possible of options is an Object
3571 return options["show_header"].get_bool();
3572 }
3573 if (is_human_readable && request.params[2].isBool()) {
3574 // Knots 0.20.0-28.1 compatibility
3575 return request.params[2].get_bool();
3576 }
3577 if (!request.params[2].isNull()) request.params[2].get_obj(); // type check skipped earlier
3578 return true;
3579 }();
3580 const auto separator = [&] {
3581 const bool null_separator_in_options{options["separator"].isNull()};
3582 if (null_separator_in_options && is_human_readable && request.params[3].isStr()) {
3583 return MakeByteSpan(request.params[3].get_str());
3584 }
3585 if (!request.params[3].isNull()) {
3586 throw std::runtime_error(self.ToString());
3587 }
3588 if (null_separator_in_options) {
3589 return MakeByteSpan(",").first(1);
3590 }
3591 return MakeByteSpan(options["separator"].get_str());
3592 }();
3593
3594 if (options.exists("rollback")) {
3595 if (!snapshot_type.empty() && snapshot_type != "rollback") {
3596 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type));
3597 }
3598 target_index = ParseHashOrHeight(options["rollback"], *node.chainman);
3599 } else if (snapshot_type == "rollback") {
3600 auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();
3601 CHECK_NONFATAL(snapshot_heights.size() > 0);
3602 auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());
3603 target_index = ParseHashOrHeight(*max_height, *node.chainman);
3604 } else if (snapshot_type == "latest") {
3605 target_index = tip;
3606 } else {
3607 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type));
3608 }
3609
3610 // handle optional ASCII parameters
3611 std::vector<std::pair<std::string, coinascii_cb_t>> requested;
3612 if (is_human_readable) {
3613 const auto& arr = hr_format.get_array();
3614 const std::unordered_map<std::string, coinascii_cb_t> ascii_map(std::begin(ascii_types), std::end(ascii_types));
3615 for (size_t i = 0; i < arr.size(); ++i) {
3616 const auto it = ascii_map.find(arr[i].get_str());
3617 if (it == std::end(ascii_map))
3618 throw JSONRPCError(RPC_INVALID_PARAMETER, "unable to find item '"+arr[i].get_str()+"'");
3619
3620 requested.emplace_back(*it);
3621 }
3622
3623 // if nothing was found, shows everything by default
3624 if (requested.size() == 0)
3625 requested = ascii_types;
3626 }
3627
3628 const ArgsManager& args{EnsureAnyArgsman(request.context)};
3629 const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
3630 const auto path_info{fs::status(path)};
3631 // Write to a temporary path and then move into `path` on completion
3632 // to avoid confusion due to an interruption.
3633 const fs::path temppath = fs::is_fifo(path_info) ? path : // If a named pipe is passed, write directly to it
3634 fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete"));
3635
3636 if (fs::exists(path_info) && !fs::is_fifo(path_info)) {
3637 throw JSONRPCError(
3638 RPC_INVALID_PARAMETER,
3639 path.utf8string() + " already exists. If you are sure this is what you want, "
3640 "move it out of the way first");
3641 }
3642
3643 FILE* file{fsbridge::fopen(temppath, !is_human_readable ? "wb" : "w")};
3644 AutoFile afile{file};
3645 if (afile.IsNull()) {
3646 throw JSONRPCError(
3647 RPC_INVALID_PARAMETER,
3648 "Couldn't open file " + temppath.utf8string() + " for writing.");
3649 }
3650
3651 CConnman& connman = EnsureConnman(node);
3652 const CBlockIndex* invalidate_index{nullptr};
3653 std::optional<NetworkDisable> disable_network;
3654 std::optional<TemporaryRollback> temporary_rollback;
3655
3656 // If the user wants to dump the txoutset of the current tip, we don't have
3657 // to roll back at all
3658 if (target_index != tip) {
3659 // If the node is running in pruned mode we ensure all necessary block
3660 // data is available before starting to roll back.
3661 if (node.chainman->m_blockman.IsPruneMode()) {
3662 LOCK(node.chainman->GetMutex());
3663 const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()};
3664 const CBlockIndex* first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)};
3665 if (first_block->nHeight > target_index->nHeight) {
3666 throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned.");
3667 }
3668 }
3669
3670 // Suspend network activity for the duration of the process when we are
3671 // rolling back the chain to get a utxo set from a past height. We do
3672 // this so we don't punish peers that send us that send us data that
3673 // seems wrong in this temporary state. For example a normal new block
3674 // would be classified as a block connecting an invalid block.
3675 // Skip if the network is already disabled because this
3676 // automatically re-enables the network activity at the end of the
3677 // process which may not be what the user wants.
3678 if (connman.GetNetworkActive()) {
3679 disable_network.emplace(connman);
3680 }
3681
3682 invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(target_index));
3683 temporary_rollback.emplace(*node.chainman, *invalidate_index);
3684 }
3685
3686 Chainstate* chainstate;
3687 std::unique_ptr<CCoinsViewCursor> cursor;
3688 CCoinsStats stats;
3689 {
3690 // Lock the chainstate before calling PrepareUtxoSnapshot, to be able
3691 // to get a UTXO database cursor while the chain is pointing at the
3692 // target block. After that, release the lock while calling
3693 // WriteUTXOSnapshot. The cursor will remain valid and be used by
3694 // WriteUTXOSnapshot to write a consistent snapshot even if the
3695 // chainstate changes.
3696 LOCK(node.chainman->GetMutex());
3697 chainstate = &node.chainman->ActiveChainstate();
3698 // In case there is any issue with a block being read from disk we need
3699 // to stop here, otherwise the dump could still be created for the wrong
3700 // height.
3701 // The new tip could also not be the target block if we have a stale
3702 // sister block of invalidate_index. This block (or a descendant) would
3703 // be activated as the new tip and we would not get to new_tip_index.
3704 if (target_index != chainstate->m_chain.Tip()) {
3705 LogWarning("dumptxoutset failed to roll back to requested height, reverting to tip.\n");
3706 throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height.");
3707 } else {
3708 std::tie(cursor, stats, tip) = PrepareUTXOSnapshot(*chainstate, node.rpc_interruption_point);
3709 }
3710 }
3711
3712 UniValue result = WriteUTXOSnapshot(
3713 is_human_readable,
3714 show_header, separator, requested,
3715 *chainstate,
3716 cursor.get(),
3717 &stats,
3718 tip,
3719 std::move(afile),
3720 path,
3721 temppath,
3722 node.rpc_interruption_point);
3723 if (!fs::is_fifo(path_info)) fs::rename(temppath, path);
3724
3725 result.pushKV("path", path.utf8string());
3726 return result;
3727 },
3728 };
3729 }
3730
3731 std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
3732 PrepareUTXOSnapshot(
3733 Chainstate& chainstate,
3734 const std::function<void()>& interruption_point)
3735 {
3736 std::unique_ptr<CCoinsViewCursor> pcursor;
3737 std::optional<CCoinsStats> maybe_stats;
3738 const CBlockIndex* tip;
3739
3740 {
3741 // We need to lock cs_main to ensure that the coinsdb isn't written to
3742 // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
3743 // based upon the coinsdb, and (iii) constructing a cursor to the
3744 // coinsdb for use in WriteUTXOSnapshot.
3745 //
3746 // Cursors returned by leveldb iterate over snapshots, so the contents
3747 // of the pcursor will not be affected by simultaneous writes during
3748 // use below this block.
3749 //
3750 // See discussion here:
3751 // https://github.com/limenka/limenka/pull/15606#discussion_r274479369
3752 //
3753 AssertLockHeld(::cs_main);
3754
3755 chainstate.ForceFlushStateToDisk();
3756
3757 maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
3758 if (!maybe_stats) {
3759 throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
3760 }
3761
3762 pcursor = chainstate.CoinsDB().Cursor();
3763 tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
3764 }
3765
3766 return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip};
3767 }
3768
3769 UniValue WriteUTXOSnapshot(
3770 const bool is_human_readable,
3771 const bool show_header,
3772 const Span<const std::byte>& separator,
3773 const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
3774 Chainstate& chainstate,
3775 CCoinsViewCursor* pcursor,
3776 CCoinsStats* maybe_stats,
3777 const CBlockIndex* tip,
3778 AutoFile&& afile,
3779 const fs::path& path,
3780 const fs::path& temppath,
3781 const std::function<void()>& interruption_point)
3782 {
3783 LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
3784 tip->nHeight, tip->GetBlockHash().ToString(),
3785 fs::PathToString(path), fs::PathToString(temppath)));
3786
3787 // used when human readable format is requested
3788 const auto line_separator = MakeByteSpan("\n").first(1);
3789
3790 if (!is_human_readable) {
3791 SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
3792
3793 afile << metadata;
3794 } else if (show_header) {
3795 afile.write(MakeByteSpan("#(blockhash " + tip->GetBlockHash().ToString() + " ) "));
3796 for (auto it = std::begin(requested); it != std::end(requested); ++it) {
3797 if (it != std::begin(requested)) {
3798 afile.write(separator);
3799 }
3800 afile.write(MakeByteSpan(it->first));
3801 }
3802 afile.write(line_separator);
3803 }
3804
3805 COutPoint key;
3806 Txid last_hash;
3807 Coin coin;
3808 unsigned int iter{0};
3809 size_t written_coins_count{0};
3810 std::vector<std::pair<uint32_t, Coin>> coins;
3811
3812 // To reduce space the serialization format of the snapshot avoids
3813 // duplication of tx hashes. The code takes advantage of the guarantee by
3814 // leveldb that keys are lexicographically sorted.
3815 // In the coins vector we collect all coins that belong to a certain tx hash
3816 // (key.hash) and when we have them all (key.hash != last_hash) we write
3817 // them to file using the below lambda function.
3818 // See also https://github.com/limenka/limenka/issues/25675
3819 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) {
3820 afile << last_hash;
3821 WriteCompactSize(afile, coins.size());
3822 for (const auto& [n, coin] : coins) {
3823 WriteCompactSize(afile, n);
3824 afile << coin;
3825 ++written_coins_count;
3826 }
3827 };
3828
3829 pcursor->GetKey(key);
3830 last_hash = key.hash;
3831 while (pcursor->Valid()) {
3832 if (iter % 5000 == 0) interruption_point();
3833 ++iter;
3834 if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
3835 if (!is_human_readable) {
3836 if (key.hash != last_hash) {
3837 write_coins_to_file(afile, last_hash, coins, written_coins_count);
3838 last_hash = key.hash;
3839 coins.clear();
3840 }
3841 coins.emplace_back(key.n, coin);
3842 } else {
3843 for (auto it = std::begin(requested); it != std::end(requested); ++it) {
3844 if (it != std::begin(requested))
3845 afile.write(separator);
3846 afile.write(MakeByteSpan(it->second(key, coin)));
3847 }
3848 afile.write(line_separator);
3849 ++written_coins_count;
3850 }
3851 }
3852 pcursor->Next();
3853 }
3854
3855 if (!coins.empty()) {
3856 write_coins_to_file(afile, last_hash, coins, written_coins_count);
3857 }
3858
3859 CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
3860
3861 if (afile.fclose() != 0) {
3862 throw std::ios_base::failure(
3863 strprintf("Error closing %s: %s", fs::PathToString(temppath), SysErrorString(errno)));
3864 }
3865
3866 UniValue result(UniValue::VOBJ);
3867 result.pushKV("coins_written", written_coins_count);
3868 result.pushKV("base_hash", tip->GetBlockHash().ToString());
3869 result.pushKV("base_height", tip->nHeight);
3870 result.pushKV("path", path.utf8string());
3871 result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
3872 result.pushKV("nchaintx", tip->m_chain_tx_count);
3873 return result;
3874 }
3875
3876 UniValue CreateUTXOSnapshot(
3877 node::NodeContext& node,
3878 Chainstate& chainstate,
3879 AutoFile&& afile,
3880 const fs::path& path,
3881 const fs::path& tmppath)
3882 {
3883 auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
3884 return WriteUTXOSnapshot(
3885 false, false, Span<std::byte>(), {},
3886 chainstate,
3887 cursor.get(),
3888 &stats,
3889 tip,
3890 std::move(afile),
3891 path,
3892 tmppath,
3893 node.rpc_interruption_point);
3894 }
3895
3896 static RPCHelpMan loadtxoutset()
3897 {
3898 return RPCHelpMan{
3899 "loadtxoutset",
3900 "Load the serialized UTXO set from a file.\n"
3901 "Once this snapshot is loaded, its contents will be "
3902 "deserialized into a second chainstate data structure, which is then used to sync to "
3903 "the network's tip. "
3904 "Meanwhile, the original chainstate will complete the initial block download process in "
3905 "the background, eventually validating up to the block that the snapshot is based upon.\n\n"
3906
3907 "The result is a usable limenkad instance that is current with the network tip in a "
3908 "matter of minutes rather than hours. UTXO snapshot are typically obtained from "
3909 "third-party sources (HTTP, torrent, etc.) which is reasonable since their "
3910 "contents are always checked by hash.\n\n"
3911
3912 "You can find more information on this process in the `assumeutxo` design "
3913 "document (<https://github.com/limenka/limenka/blob/master/doc/design/assumeutxo.md>).",
3914 {
3915 {"path",
3916 RPCArg::Type::STR,
3917 RPCArg::Optional::NO,
3918 "path to the snapshot file. If relative, will be prefixed by datadir."},
3919 },
3920 RPCResult{
3921 RPCResult::Type::OBJ, "", "",
3922 {
3923 {RPCResult::Type::NUM, "coins_loaded", "the number of coins loaded from the snapshot"},
3924 {RPCResult::Type::STR_HEX, "tip_hash", "the hash of the base of the snapshot"},
3925 {RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
3926 {RPCResult::Type::STR, "path", "the absolute path that the snapshot was loaded from"},
3927 }
3928 },
3929 RPCExamples{
3930 HelpExampleCli("-rpcclienttimeout=0 loadtxoutset", "utxo.dat")
3931 },
3932 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3933 {
3934 EnsureNotWalletRestricted(request);
3935
3936 NodeContext& node = EnsureAnyNodeContext(request.context);
3937 ChainstateManager& chainman = EnsureChainman(node);
3938 const fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(self.Arg<std::string>("path")))};
3939
3940 FILE* file{fsbridge::fopen(path, "rb")};
3941 AutoFile afile{file};
3942 if (afile.IsNull()) {
3943 throw JSONRPCError(
3944 RPC_INVALID_PARAMETER,
3945 "Couldn't open file " + path.utf8string() + " for reading.");
3946 }
3947
3948 SnapshotMetadata metadata{chainman.GetParams().MessageStart()};
3949 try {
3950 afile >> metadata;
3951 } catch (const std::ios_base::failure& e) {
3952 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Unable to parse metadata: %s", e.what()));
3953 }
3954
3955 auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)};
3956 if (!activation_result) {
3957 throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
3958 }
3959
3960 // Because we can't provide historical blocks during tip or background sync.
3961 // Update local services to reflect we are a limited peer until we are fully sync.
3962 node.connman->RemoveLocalServices(NODE_NETWORK);
3963 // Setting the limited state is usually redundant because the node can always
3964 // provide the last 288 blocks, but it doesn't hurt to set it.
3965 node.connman->AddLocalServices(NODE_NETWORK_LIMITED);
3966
3967 CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)};
3968
3969 UniValue result(UniValue::VOBJ);
3970 result.pushKV("coins_loaded", metadata.m_coins_count);
3971 result.pushKV("tip_hash", snapshot_index.GetBlockHash().ToString());
3972 result.pushKV("base_height", snapshot_index.nHeight);
3973 result.pushKV("path", fs::PathToString(path));
3974 return result;
3975 },
3976 };
3977 }
3978
3979 const std::vector<RPCResult> RPCHelpForChainstate{
3980 {RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
3981 {RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
3982 {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
3983 {RPCResult::Type::STR_HEX, "target", "The difficulty target"},
3984 {RPCResult::Type::NUM, "difficulty", "difficulty of the tip"},
3985 {RPCResult::Type::NUM, "verificationprogress", "progress towards the network tip"},
3986 {RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true, "the base block of the snapshot this chainstate is based on, if any"},
3987 {RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
3988 {RPCResult::Type::NUM, "coins_tip_cache_bytes", "size of the coinstip cache"},
3989 {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."},
3990 };
3991
3992 static RPCHelpMan getchainstates()
3993 {
3994 return RPCHelpMan{
3995 "getchainstates",
3996 "\nReturn information about chainstates.\n",
3997 {},
3998 RPCResult{
3999 RPCResult::Type::OBJ, "", "", {
4000 {RPCResult::Type::NUM, "headers", "the number of headers seen so far"},
4001 {RPCResult::Type::ARR, "chainstates", "list of the chainstates ordered by work, with the most-work (active) chainstate last", {{RPCResult::Type::OBJ, "", "", RPCHelpForChainstate},}},
4002 }
4003 },
4004 RPCExamples{
4005 HelpExampleCli("getchainstates", "")
4006 + HelpExampleRpc("getchainstates", "")
4007 },
4008 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
4009 {
4010 LOCK(cs_main);
4011 UniValue obj(UniValue::VOBJ);
4012
4013 ChainstateManager& chainman = EnsureAnyChainman(request.context);
4014
4015 auto make_chain_data = [&](const Chainstate& cs, bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
4016 AssertLockHeld(::cs_main);
4017 UniValue data(UniValue::VOBJ);
4018 if (!cs.m_chain.Tip()) {
4019 return data;
4020 }
4021 const CChain& chain = cs.m_chain;
4022 const CBlockIndex* tip = chain.Tip();
4023
4024 data.pushKV("blocks", (int)chain.Height());
4025 data.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
4026 data.pushKV("bits", strprintf("%08x", tip->nBits));
4027 data.pushKV("target", GetTarget(*tip, chainman.GetConsensus().powLimit).GetHex());
4028 data.pushKV("difficulty", GetDifficulty(*tip));
4029 data.pushKV("verificationprogress", chainman.GuessVerificationProgress(tip));
4030 data.pushKV("coins_db_cache_bytes", cs.m_coinsdb_cache_size_bytes);
4031 data.pushKV("coins_tip_cache_bytes", cs.m_coinstip_cache_size_bytes);
4032 if (cs.m_from_snapshot_blockhash) {
4033 data.pushKV("snapshot_blockhash", cs.m_from_snapshot_blockhash->ToString());
4034 }
4035 data.pushKV("validated", validated);
4036 return data;
4037 };
4038
4039 obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
4040
4041 const auto& chainstates = chainman.GetAll();
4042 UniValue obj_chainstates{UniValue::VARR};
4043 for (Chainstate* cs : chainstates) {
4044 obj_chainstates.push_back(make_chain_data(*cs, !cs->m_from_snapshot_blockhash || chainstates.size() == 1));
4045 }
4046 obj.pushKV("chainstates", std::move(obj_chainstates));
4047 return obj;
4048 }
4049 };
4050 }
4051
4052 static RPCHelpMan getblockfileinfo()
4053 {
4054 return RPCHelpMan{
4055 "getblockfileinfo",
4056 "Retrieves information about a certain block file.",
4057 {
4058 {"file_number", RPCArg::Type::NUM, RPCArg::Optional::NO, "block file number"},
4059 },
4060 RPCResult{
4061 RPCResult::Type::OBJ, "", "",
4062 {
4063 {RPCResult::Type::NUM, "blocks_num", "the number of blocks stored in the file"},
4064 {RPCResult::Type::NUM, "lowest_block", "the height of the lowest block inside the file"},
4065 {RPCResult::Type::NUM, "highest_block", "the height of the highest block inside the file"},
4066 {RPCResult::Type::NUM, "data_size", "the number of used bytes in the block file"},
4067 {RPCResult::Type::NUM, "undo_size", "the number of used bytes in the undo file"},
4068 }
4069 },
4070 RPCExamples{ HelpExampleCli("getblockfileinfo", "0") },
4071 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
4072 NodeContext& node = EnsureAnyNodeContext(request.context);
4073
4074 int block_num = request.params[0].getInt<int>();
4075 if (block_num < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block number");
4076
4077 CBlockFileInfo* info = node.chainman->m_blockman.GetBlockFileInfo(block_num);
4078 if (!info) throw JSONRPCError(RPC_INVALID_PARAMETER, "block file not found");
4079
4080 UniValue result(UniValue::VOBJ);
4081 result.pushKV("blocks_num", info->nBlocks);
4082 result.pushKV("lowest_block", info->nHeightFirst);
4083 result.pushKV("highest_block", info->nHeightLast);
4084 result.pushKV("data_size", info->nSize);
4085 result.pushKV("undo_size", info->nUndoSize);
4086
4087 return result;
4088 }
4089 };
4090 }
4091
4092 static RPCHelpMan getblocklocations()
4093 {
4094 return RPCHelpMan{"getblocklocations",
4095 "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
4096 "\nReturns a JSON for the file system location of 'blockhash' block and undo data.\n"
4097 "\nIt is possible to return also the locations of previous blocks, by specifying 'nblocks' > 1.\n",
4098 {
4099 {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
4100 {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "Maximum number locations to return (up to genesis block)"},
4101 },
4102 {
4103 RPCResult{
4104 RPCResult::Type::ARR, "", "",
4105 {
4106 {RPCResult::Type::OBJ, "", "",
4107 {
4108 {RPCResult::Type::NUM, "file", "blk*.dat/rev*.dat file index"},
4109 {RPCResult::Type::NUM, "data", "block data file offset"},
4110 {RPCResult::Type::NUM, "undo", /*optional=*/true, "undo data file offset (if exists)"},
4111 {RPCResult::Type::STR_HEX, "prev", "previous block hash"},
4112 }},
4113 }
4114 },
4115 },
4116 RPCExamples{
4117 HelpExampleCli("getblocklocations", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 10")
4118 },
4119 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
4120
4121 ChainstateManager& chainman = EnsureAnyChainman(request.context);
4122 if (chainman.m_blockman.IsPruneMode()) {
4123 throw JSONRPCError(RPC_MISC_ERROR, "Block locations are not available in prune mode");
4124 }
4125
4126 uint256 hash(ParseHashV(request.params[0], "blockhash"));
4127 size_t nblocks = request.params[1].getInt<size_t>();
4128
4129 const CBlockIndex* pblockindex = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash));
4130 if (!pblockindex) {
4131 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
4132 }
4133
4134 UniValue result(UniValue::VARR);
4135 do {
4136 int64_t file_num;
4137 uint64_t data_pos, undo_pos;
4138 {
4139 LOCK(::cs_main);
4140 file_num = pblockindex->nFile;
4141 data_pos = pblockindex->nDataPos;
4142 undo_pos = pblockindex->nUndoPos;
4143 }
4144 UniValue location(UniValue::VOBJ);
4145 location.pushKV("file", file_num);
4146 location.pushKV("data", data_pos);
4147 if (undo_pos) {
4148 location.pushKV("undo", undo_pos);
4149 }
4150 if (pblockindex->pprev) {
4151 location.pushKV("prev", pblockindex->pprev->GetBlockHash().GetHex());
4152 } else {
4153 location.pushKV("prev", uint256().GetHex());
4154 }
4155 result.push_back(location);
4156 pblockindex = pblockindex->pprev;
4157 } while (result.size() < nblocks && pblockindex);
4158 return result;
4159 },
4160 };
4161 }
4162
4163
4164 void RegisterBlockchainRPCCommands(CRPCTable& t)
4165 {
4166 static const CRPCCommand commands[]{
4167 {"blockchain", &getblockchaininfo},
4168 {"blockchain", &getchaintxstats},
4169 {"blockchain", &getblockstats},
4170 {"blockchain", &getbestblockhash},
4171 {"blockchain", &getblockcount},
4172 {"blockchain", &getblock},
4173 {"blockchain", &getblockfrompeer},
4174 {"blockchain", &getblockhash},
4175 {"blockchain", &getblockheader},
4176 {"blockchain", &getchaintips},
4177 {"blockchain", &getdifficulty},
4178 {"blockchain", &getdeploymentinfo},
4179 {"blockchain", &gettxout},
4180 {"blockchain", &gettxoutsetinfo},
4181 {"blockchain", &listprunelocks},
4182 {"blockchain", &setprunelock},
4183 {"blockchain", &pruneblockchain},
4184 {"blockchain", &verifychain},
4185 {"blockchain", &scriptthreadsinfo},
4186 {"blockchain", &setscriptthreadsenabled},
4187 {"blockchain", &preciousblock},
4188 {"blockchain", &scantxoutset},
4189 {"blockchain", &scanblocks},
4190 {"blockchain", &getdescriptoractivity},
4191 {"blockchain", &getblockfilter},
4192 {"blockchain", &dumptxoutset},
4193 {"blockchain", &loadtxoutset},
4194 {"blockchain", &getchainstates},
4195 {"hidden", &getblockfileinfo},
4196 {"hidden", &invalidateblock},
4197 {"hidden", &reconsiderblock},
4198 {"blockchain", &waitfornewblock},
4199 {"blockchain", &waitforblock},
4200 {"blockchain", &waitforblockheight},
4201 {"hidden", &syncwithvalidationinterfacequeue},
4202 {"hidden", &getblocklocations},
4203
4204 #ifdef ENABLE_WALLET
4205 {"wallet", &sweepprivkeys},
4206 #endif
4207 };
4208 for (const auto& c : commands) {
4209 t.appendCommand(c.name, &c);
4210 }
4211 }
4212