bitcoin/src/test/util/chainstate.h
Ryan Ofsky ef29c8b662 assumeutxo: Get rid of faked nTx and nChainTx values
The `PopulateAndValidateSnapshot` function introduced in
f6e2da5fb7c6406c37612c838c998078ea8d2252 from #19806 has been setting fake
`nTx` and `nChainTx` values that can show up in RPC results (see #29328) and
make `CBlockIndex` state hard to reason about, because it is difficult to know
whether the values are real or fake.

Revert to previous behavior of setting `nTx` and `nChainTx` to 0 when the
values are unknown, instead of faking them.

This commit fixes at least two assert failures in the (pindex->nChainTx ==
pindex->nTx + prev_chain_tx) check that would happen previously. Tests for
these failures are added separately in the next two commits.

Compatibility note: This change could result in -checkblockindex failures if a
snapshot was loaded by a previous version of Bitcoin Core and not fully
validated, because fake nTx values will have been saved to the block index. It
would be pretty easy to avoid these failures by adding some compatibility code
to `LoadBlockIndex` and changing `nTx` values from 1 to 0 when they are fake
(when `(pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TRANSACTIONS`), but a
little simpler not to worry about being compatible in this case.
2024-03-18 11:28:40 -05:00

136 lines
5.5 KiB
C++

// 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()) {
// Remove all data and validity flags by just setting
// BLOCK_VALID_TREE. Also reset transaction counts and sequence
// ids that are set when blocks are received, to make test setup
// more realistic and satisfy consistency checks in
// CheckBlockIndex().
assert(pindex->IsValid(BlockStatus::BLOCK_VALID_TREE));
pindex->nStatus = BlockStatus::BLOCK_VALID_TREE;
pindex->nTx = 0;
pindex->nChainTx = 0;
pindex->nSequenceId = 0;
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