Coverage Report

Created: 2026-07-30 14:31

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