Coverage Report

Created: 2026-07-14 18:13

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