aboutsummaryrefslogtreecommitdiff
path: root/src/test/util/chainstate.h
blob: e2a88eacddabf5c3e48c1a7f7aebc7109fe6e76a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (c) 2021-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#ifndef BITCOIN_TEST_UTIL_CHAINSTATE_H
#define BITCOIN_TEST_UTIL_CHAINSTATE_H

#include <clientversion.h>
#include <logging.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <rpc/blockchain.h>
#include <test/util/setup_common.h>
#include <util/fs.h>
#include <validation.h>

#include <univalue.h>

const auto NoMalleation = [](AutoFile& file, node::SnapshotMetadata& meta){};

/**
 * Create and activate a UTXO snapshot, optionally providing a function to
 * malleate the snapshot.
 *
 * If `reset_chainstate` is true, reset the original chainstate back to the genesis
 * block. This allows us to simulate more realistic conditions in which a snapshot is
 * loaded into an otherwise mostly-uninitialized datadir. It also allows us to test
 * conditions that would otherwise cause shutdowns based on the IBD chainstate going
 * past the snapshot it generated.
 */
template<typename F = decltype(NoMalleation)>
static bool
CreateAndActivateUTXOSnapshot(
    TestingSetup* fixture,
    F malleation = NoMalleation,
    bool reset_chainstate = false,
    bool in_memory_chainstate = false)
{
    node::NodeContext& node = fixture->m_node;
    fs::path root = fixture->m_path_root;

    // Write out a snapshot to the test's tempdir.
    //
    int height;
    WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
    fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height));
    FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
    AutoFile auto_outfile{outfile};

    UniValue result = CreateUTXOSnapshot(
        node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
    LogPrintf(
        "Wrote UTXO snapshot to %s: %s\n", fs::PathToString(snapshot_path.make_preferred()), result.write());

    // Read the written snapshot in and then activate it.
    //
    FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
    AutoFile auto_infile{infile};
    node::SnapshotMetadata metadata;
    auto_infile >> metadata;

    malleation(auto_infile, metadata);

    if (reset_chainstate) {
        {
            // What follows is code to selectively reset chainstate data without
            // disturbing the existing BlockManager instance, which is needed to
            // recognize the headers chain previously generated by the chainstate we're
            // removing. Without those headers, we can't activate the snapshot below.
            //
            // This is a stripped-down version of node::LoadChainstate which
            // preserves the block index.
            LOCK(::cs_main);
            CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
            uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
            node.chainman->ResetChainstates();
            node.chainman->InitializeChainstate(node.mempool.get());
            Chainstate& chain = node.chainman->ActiveChainstate();
            Assert(chain.LoadGenesisBlock());
            // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
            chain.InitCoinsDB(1 << 20, true, false, "");
            chain.InitCoinsCache(1 << 20);
            chain.CoinsTip().SetBestBlock(gen_hash);
            chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
            chain.LoadChainTip();
            node.chainman->MaybeRebalanceCaches();

            // Reset the HAVE_DATA flags below the snapshot height, simulating
            // never-having-downloaded them in the first place.
            // TODO: perhaps we could improve this by using pruning to delete
            // these blocks instead
            CBlockIndex *pindex = orig_tip;
            while (pindex && pindex != chain.m_chain.Tip()) {
                pindex->nStatus &= ~BLOCK_HAVE_DATA;
                pindex->nStatus &= ~BLOCK_HAVE_UNDO;
                // We have to set the ASSUMED_VALID flag, because otherwise it
                // would not be possible to have a block index entry without HAVE_DATA
                // and with nTx > 0 (since we aren't setting the pruned flag);
                // see CheckBlockIndex().
                pindex->nStatus |= BLOCK_ASSUMED_VALID;
                pindex = pindex->pprev;
            }
        }
        BlockValidationState state;
        if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
            throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
        }
        Assert(
            0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
    }

    auto& new_active = node.chainman->ActiveChainstate();
    auto* tip = new_active.m_chain.Tip();

    // Disconnect a block so that the snapshot chainstate will be ahead, otherwise
    // it will refuse to activate.
    //
    // TODO this is a unittest-specific hack, and we should probably rethink how to
    // better generate/activate snapshots in unittests.
    if (tip->pprev) {
        new_active.m_chain.SetTip(*(tip->pprev));
    }

    bool res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);

    // Restore the old tip.
    new_active.m_chain.SetTip(*tip);
    return res;
}


#endif // BITCOIN_TEST_UTIL_CHAINSTATE_H