Skip to content

Commit

Permalink
Merge bitcoin#29370: assumeutxo: Get rid of faked nTx and nChainTx va…
Browse files Browse the repository at this point in the history
…lues

9d9a745 assumeutxo: Remove BLOCK_ASSUMED_VALID flag (Ryan Ofsky)
ef174e9 test: assumeutxo snapshot block CheckBlockIndex crash test (Ryan Ofsky)
0391458 test: assumeutxo stale block CheckBlockIndex crash test (Ryan Ofsky)
ef29c8b assumeutxo: Get rid of faked nTx and nChainTx values (Ryan Ofsky)
9b97d5b doc: Improve comments describing setBlockIndexCandidates checks (Ryan Ofsky)
0fd915e validation: Check GuessVerificationProgress is not called with disconnected block (Ryan Ofsky)
63e8fc9 ci: add getchaintxstats ubsan suppressions (Ryan Ofsky)
f252e68 assumeutxo test: Add RPC test for fake nTx and nChainTx values (Ryan Ofsky)

Pull request description:

  The `PopulateAndValidateSnapshot` function introduced in f6e2da5 from bitcoin#19806 has been setting fake `nTx` and `nChainTx` values that can show up in RPC results (bitcoin#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. Also drop no-longer needed `BLOCK_ASSUMED_VALID` flag.

  Dropping the faked values also fixes assert failures in the `CheckBlockIndex` `(pindex->nChainTx == pindex->nTx + prev_chain_tx)` check that could happen previously if forked or out-of-order blocks before the snapshot got submitted while the snapshot was being validated. The PR includes two commits adding tests for these failures and describing them in detail.

  Compatibility note: This change could cause new `-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.

ACKs for top commit:
  Sjors:
    re-ACK 9d9a745
  achow101:
    ACK 9d9a745
  mzumsande:
    Tested ACK 9d9a745
  maflcko:
    ACK 9d9a745 🎯

Tree-SHA512: b1e1e2731ec36be30d5f5914042517219378fc31486674030c29d9c7488ed83fb60ba7095600f469dc32f0d8ba79c49ff7706303006507654e1762f26ee416e0
  • Loading branch information
achow101 committed Mar 20, 2024
2 parents 69ddee6 + 9d9a745 commit b50554b
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 156 deletions.
12 changes: 3 additions & 9 deletions doc/design/assumeutxo.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,12 @@ The utility script

## Design notes

- A new block index `nStatus` flag is introduced, `BLOCK_ASSUMED_VALID`, to mark block
index entries that are required to be assumed-valid by a chainstate created
from a UTXO snapshot. This flag is used as a way to modify certain
CheckBlockIndex() logic to account for index entries that are pending validation by a
chainstate running asynchronously in the background.

- The concept of UTXO snapshots is treated as an implementation detail that lives
behind the ChainstateManager interface. The external presentation of the changes
required to facilitate the use of UTXO snapshots is the understanding that there are
now certain regions of the chain that can be temporarily assumed to be valid (using
the nStatus flag mentioned above). In certain cases, e.g. wallet rescanning, this is
very similar to dealing with a pruned chain.
now certain regions of the chain that can be temporarily assumed to be valid.
In certain cases, e.g. wallet rescanning, this is very similar to dealing with
a pruned chain.

Logic outside ChainstateManager should try not to know about snapshots, instead
preferring to work in terms of more general states like assumed-valid.
Expand Down
67 changes: 19 additions & 48 deletions src/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,20 @@ enum BlockStatus : uint32_t {

/**
* Only first tx is coinbase, 2 <= coinbase input script length <= 100, transactions valid, no duplicate txids,
* sigops, size, merkle root. Implies all parents are at least TREE but not necessarily TRANSACTIONS. When all
* parent blocks also have TRANSACTIONS, CBlockIndex::nChainTx will be set.
* sigops, size, merkle root. Implies all parents are at least TREE but not necessarily TRANSACTIONS.
*
* If a block's validity is at least VALID_TRANSACTIONS, CBlockIndex::nTx will be set. If a block and all previous
* blocks back to the genesis block or an assumeutxo snapshot block are at least VALID_TRANSACTIONS,
* CBlockIndex::nChainTx will be set.
*/
BLOCK_VALID_TRANSACTIONS = 3,

//! Outputs do not overspend inputs, no double spends, coinbase output ok, no immature coinbase spends, BIP30.
//! Implies all parents are either at least VALID_CHAIN, or are ASSUMED_VALID
//! Implies all previous blocks back to the genesis block or an assumeutxo snapshot block are at least VALID_CHAIN.
BLOCK_VALID_CHAIN = 4,

//! Scripts & signatures ok. Implies all parents are either at least VALID_SCRIPTS, or are ASSUMED_VALID.
//! Scripts & signatures ok. Implies all previous blocks back to the genesis block or an assumeutxo snapshot block
//! are at least VALID_SCRIPTS.
BLOCK_VALID_SCRIPTS = 5,

//! All validity bits.
Expand All @@ -124,21 +128,8 @@ enum BlockStatus : uint32_t {

BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client

/**
* If ASSUMED_VALID is set, it means that this block has not been validated
* and has validity status less than VALID_SCRIPTS. Also that it may have
* descendant blocks with VALID_SCRIPTS set, because they can be validated
* based on an assumeutxo snapshot.
*
* When an assumeutxo snapshot is loaded, the ASSUMED_VALID flag is added to
* unvalidated blocks at the snapshot height and below. Then, as the background
* validation progresses, and these blocks are validated, the ASSUMED_VALID
* flags are removed. See `doc/design/assumeutxo.md` for details.
*
* This flag is only used to implement checks in CheckBlockIndex() and
* should not be used elsewhere.
*/
BLOCK_ASSUMED_VALID = 256,
BLOCK_STATUS_RESERVED = 256, //!< Unused flag that was previously set on assumeutxo snapshot blocks and their
//!< ancestors before they were validated, and unset when they were validated.
};

/** The block chain is a tree shaped structure starting with the
Expand Down Expand Up @@ -173,21 +164,16 @@ class CBlockIndex
//! (memory only) Total amount of work (expected number of hashes) in the chain up to and including this block
arith_uint256 nChainWork{};

//! Number of transactions in this block.
//! Number of transactions in this block. This will be nonzero if the block
//! reached the VALID_TRANSACTIONS level, and zero otherwise.
//! Note: in a potential headers-first mode, this number cannot be relied upon
//! Note: this value is faked during UTXO snapshot load to ensure that
//! LoadBlockIndex() will load index entries for blocks that we lack data for.
//! @sa ActivateSnapshot
unsigned int nTx{0};

//! (memory only) Number of transactions in the chain up to and including this block.
//! This value will be non-zero only if and only if transactions for this block and all its parents are available.
//! This value will be non-zero if this block and all previous blocks back
//! to the genesis block or an assumeutxo snapshot block have reached the
//! VALID_TRANSACTIONS level.
//! Change to 64-bit type before 2024 (assuming worst case of 60 byte transactions).
//!
//! Note: this value is faked during use of a UTXO snapshot because we don't
//! have the underlying block data available during snapshot load.
//! @sa AssumeutxoData
//! @sa ActivateSnapshot
unsigned int nChainTx{0};

//! Verification status of this block. See enum BlockStatus
Expand Down Expand Up @@ -262,15 +248,14 @@ class CBlockIndex
}

/**
* Check whether this block's and all previous blocks' transactions have been
* downloaded (and stored to disk) at some point.
* Check whether this block and all previous blocks back to the genesis block or an assumeutxo snapshot block have
* reached VALID_TRANSACTIONS and had transactions downloaded (and stored to disk) at some point.
*
* Does not imply the transactions are consensus-valid (ConnectTip might fail)
* Does not imply the transactions are still stored on disk. (IsBlockPruned might return true)
*
* Note that this will be true for the snapshot base block, if one is loaded (and
* all subsequent assumed-valid blocks) since its nChainTx value will have been set
* manually based on the related AssumeutxoData entry.
* Note that this will be true for the snapshot base block, if one is loaded, since its nChainTx value will have
* been set manually based on the related AssumeutxoData entry.
*/
bool HaveNumChainTxs() const { return nChainTx != 0; }

Expand Down Expand Up @@ -318,14 +303,6 @@ class CBlockIndex
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
}

//! @returns true if the block is assumed-valid; this means it is queued to be
//! validated by a background chainstate.
bool IsAssumedValid() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
return nStatus & BLOCK_ASSUMED_VALID;
}

//! Raise the validity level of this block index entry.
//! Returns true if the validity was changed.
bool RaiseValidity(enum BlockStatus nUpTo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
Expand All @@ -335,12 +312,6 @@ class CBlockIndex
if (nStatus & BLOCK_FAILED_MASK) return false;

if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
// If this block had been marked assumed-valid and we're raising
// its validity to a certain point, there is no longer an assumption.
if (nStatus & BLOCK_ASSUMED_VALID && nUpTo >= BLOCK_VALID_SCRIPTS) {
nStatus &= ~BLOCK_ASSUMED_VALID;
}

nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
return true;
}
Expand Down
17 changes: 10 additions & 7 deletions src/test/util/chainstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,16 @@ CreateAndActivateUTXOSnapshot(
// 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;
// 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;
}
}
Expand Down
21 changes: 6 additions & 15 deletions src/test/validation_chainstatemanager_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,6 @@ struct SnapshotTestSetup : TestChain100Setup {
BOOST_CHECK_EQUAL(
*node::ReadSnapshotBaseBlockhash(found),
*chainman.SnapshotBlockhash());

// Ensure that the genesis block was not marked assumed-valid.
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
}

const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height);
Expand Down Expand Up @@ -410,7 +407,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
//! - First, verify that setBlockIndexCandidates is as expected when using a single,
//! fully-validating chainstate.
//!
//! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
//! - Then mark a region of the chain as missing data and introduce a second chainstate
//! that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
//! chainstate only contains fully validated blocks and the other chainstate contains all blocks,
//! except those marked assume-valid, because those entries don't HAVE_DATA.
Expand All @@ -421,7 +418,6 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
Chainstate& cs1 = chainman.ActiveChainstate();

int num_indexes{0};
int num_assumed_valid{0};
// Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
// marked as assumed-valid and not having data.
const int expected_assumed_valid{20};
Expand Down Expand Up @@ -456,35 +452,30 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
reload_all_block_indexes();
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);

// Mark some region of the chain assumed-valid, and remove the HAVE_DATA flag.
// Reset some region of the chain's nStatus, removing the HAVE_DATA flag.
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
LOCK(::cs_main);
auto index = cs1.m_chain[i];

// Blocks with heights in range [91, 110] are marked ASSUMED_VALID
// Blocks with heights in range [91, 110] are marked as missing data.
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
index->nStatus = BlockStatus::BLOCK_VALID_TREE;
index->nTx = 0;
index->nChainTx = 0;
}

++num_indexes;
if (index->IsAssumedValid()) ++num_assumed_valid;

// Note the last fully-validated block as the expected validated tip.
if (i == (assumed_valid_start_idx - 1)) {
validated_tip = index;
BOOST_CHECK(!index->IsAssumedValid());
}
// Note the last assumed valid block as the snapshot base
if (i == last_assumed_valid_idx - 1) {
assumed_base = index;
BOOST_CHECK(index->IsAssumedValid());
} else if (i == last_assumed_valid_idx) {
BOOST_CHECK(!index->IsAssumedValid());
}
}

BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);

// Note: cs2's tip is not set when ActivateExistingSnapshot is called.
Chainstate& cs2 = WITH_LOCK(::cs_main,
return chainman.ActivateExistingSnapshot(*assumed_base->phashBlock));
Expand Down
Loading

0 comments on commit b50554b

Please sign in to comment.