mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-18 22:35:39 +01:00
Merge bitcoin/bitcoin#29370: assumeutxo: Get rid of faked nTx and nChainTx values
9d9a7458a2assumeutxo: Remove BLOCK_ASSUMED_VALID flag (Ryan Ofsky)ef174e9ed2test: assumeutxo snapshot block CheckBlockIndex crash test (Ryan Ofsky)0391458d76test: assumeutxo stale block CheckBlockIndex crash test (Ryan Ofsky)ef29c8b662assumeutxo: Get rid of faked nTx and nChainTx values (Ryan Ofsky)9b97d5bbf9doc: Improve comments describing setBlockIndexCandidates checks (Ryan Ofsky)0fd915ee6bvalidation: Check GuessVerificationProgress is not called with disconnected block (Ryan Ofsky)63e8fc912cci: add getchaintxstats ubsan suppressions (Ryan Ofsky)f252e687ecassumeutxo test: Add RPC test for fake nTx and nChainTx values (Ryan Ofsky) Pull request description: The `PopulateAndValidateSnapshot` function introduced inf6e2da5fb7from #19806 has been setting fake `nTx` and `nChainTx` values that can show up in RPC results (https://github.com/bitcoin/bitcoin/issues/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-ACK9d9a7458a2achow101: ACK9d9a7458a2mzumsande: Tested ACK9d9a7458a2maflcko: ACK9d9a7458a2🎯 Tree-SHA512: b1e1e2731ec36be30d5f5914042517219378fc31486674030c29d9c7488ed83fb60ba7095600f469dc32f0d8ba79c49ff7706303006507654e1762f26ee416e0
This commit is contained in:
@@ -34,6 +34,7 @@ Interesting starting states could be loading a snapshot when the current chain t
|
||||
"""
|
||||
from shutil import rmtree
|
||||
|
||||
from dataclasses import dataclass
|
||||
from test_framework.messages import tx_from_hex
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
@@ -174,10 +175,26 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
|
||||
# Generate a series of blocks that `n0` will have in the snapshot,
|
||||
# but that n1 and n2 don't yet see.
|
||||
assert n0.getblockcount() == START_HEIGHT
|
||||
blocks = {START_HEIGHT: Block(n0.getbestblockhash(), 1, START_HEIGHT + 1)}
|
||||
for i in range(100):
|
||||
block_tx = 1
|
||||
if i % 3 == 0:
|
||||
self.mini_wallet.send_self_transfer(from_node=n0)
|
||||
block_tx += 1
|
||||
self.generate(n0, nblocks=1, sync_fun=self.no_op)
|
||||
height = n0.getblockcount()
|
||||
hash = n0.getbestblockhash()
|
||||
blocks[height] = Block(hash, block_tx, blocks[height-1].chain_tx + block_tx)
|
||||
if i == 4:
|
||||
# Create a stale block that forks off the main chain before the snapshot.
|
||||
temp_invalid = n0.getbestblockhash()
|
||||
n0.invalidateblock(temp_invalid)
|
||||
stale_hash = self.generateblock(n0, output="raw(aaaa)", transactions=[], sync_fun=self.no_op)["hash"]
|
||||
n0.invalidateblock(stale_hash)
|
||||
n0.reconsiderblock(temp_invalid)
|
||||
stale_block = n0.getblock(stale_hash, 0)
|
||||
|
||||
|
||||
self.log.info("-- Testing assumeutxo + some indexes + pruning")
|
||||
|
||||
@@ -207,7 +224,7 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
assert_equal(
|
||||
dump_output['txoutset_hash'],
|
||||
"a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
|
||||
assert_equal(dump_output["nchaintx"], 334)
|
||||
assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
|
||||
assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
|
||||
|
||||
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
|
||||
@@ -228,6 +245,29 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
|
||||
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
|
||||
|
||||
def check_tx_counts(final: bool) -> None:
|
||||
"""Check nTx and nChainTx intermediate values right after loading
|
||||
the snapshot, and final values after the snapshot is validated."""
|
||||
for height, block in blocks.items():
|
||||
tx = n1.getblockheader(block.hash)["nTx"]
|
||||
chain_tx = n1.getchaintxstats(nblocks=1, blockhash=block.hash)["txcount"]
|
||||
|
||||
# Intermediate nTx of the starting block should be set, but nTx of
|
||||
# later blocks should be 0 before they are downloaded.
|
||||
if final or height == START_HEIGHT:
|
||||
assert_equal(tx, block.tx)
|
||||
else:
|
||||
assert_equal(tx, 0)
|
||||
|
||||
# Intermediate nChainTx of the starting block and snapshot block
|
||||
# should be set, but others should be 0 until they are downloaded.
|
||||
if final or height in (START_HEIGHT, SNAPSHOT_BASE_HEIGHT):
|
||||
assert_equal(chain_tx, block.chain_tx)
|
||||
else:
|
||||
assert_equal(chain_tx, 0)
|
||||
|
||||
check_tx_counts(final=False)
|
||||
|
||||
normal, snapshot = n1.getchainstates()["chainstates"]
|
||||
assert_equal(normal['blocks'], START_HEIGHT)
|
||||
assert_equal(normal.get('snapshot_blockhash'), None)
|
||||
@@ -238,6 +278,15 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
|
||||
assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
|
||||
|
||||
self.log.info("Submit a stale block that forked off the chain before the snapshot")
|
||||
# Normally a block like this would not be downloaded, but if it is
|
||||
# submitted early before the background chain catches up to the fork
|
||||
# point, it winds up in m_blocks_unlinked and triggers a corner case
|
||||
# that previously crashed CheckBlockIndex.
|
||||
n1.submitblock(stale_block)
|
||||
n1.getchaintips()
|
||||
n1.getblock(stale_hash)
|
||||
|
||||
self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool")
|
||||
# spend the coinbase output of the first block that is not available on node1
|
||||
spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)
|
||||
@@ -275,6 +324,16 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Restarted node before snapshot validation completed, reloading...")
|
||||
self.restart_node(1, extra_args=self.extra_args[1])
|
||||
|
||||
# Send snapshot block to n1 out of order. This makes the test less
|
||||
# realistic because normally the snapshot block is one of the last
|
||||
# blocks downloaded, but its useful to test because it triggers more
|
||||
# corner cases in ReceivedBlockTransactions() and CheckBlockIndex()
|
||||
# setting and testing nChainTx values, and it exposed previous bugs.
|
||||
snapshot_hash = n0.getblockhash(SNAPSHOT_BASE_HEIGHT)
|
||||
snapshot_block = n0.getblock(snapshot_hash, 0)
|
||||
n1.submitblock(snapshot_block)
|
||||
|
||||
self.connect_nodes(0, 1)
|
||||
|
||||
self.log.info(f"Ensuring snapshot chain syncs to tip. ({FINAL_HEIGHT})")
|
||||
@@ -291,6 +350,8 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
}
|
||||
self.wait_until(lambda: n1.getindexinfo() == completed_idx_state)
|
||||
|
||||
self.log.info("Re-check nTx and nChainTx values")
|
||||
check_tx_counts(final=True)
|
||||
|
||||
for i in (0, 1):
|
||||
n = self.nodes[i]
|
||||
@@ -365,6 +426,11 @@ class AssumeutxoTest(BitcoinTestFramework):
|
||||
self.connect_nodes(0, 2)
|
||||
self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT)
|
||||
|
||||
@dataclass
|
||||
class Block:
|
||||
hash: str
|
||||
tx: int
|
||||
chain_tx: int
|
||||
|
||||
if __name__ == '__main__':
|
||||
AssumeutxoTest().main()
|
||||
|
||||
@@ -51,6 +51,7 @@ unsigned-integer-overflow:CCoinsViewCache::Uncache
|
||||
unsigned-integer-overflow:CompressAmount
|
||||
unsigned-integer-overflow:DecompressAmount
|
||||
unsigned-integer-overflow:crypto/
|
||||
unsigned-integer-overflow:getchaintxstats*
|
||||
unsigned-integer-overflow:MurmurHash3
|
||||
unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx
|
||||
unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal
|
||||
@@ -61,6 +62,7 @@ implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx
|
||||
implicit-integer-sign-change:SetStdinEcho
|
||||
implicit-integer-sign-change:compressor.h
|
||||
implicit-integer-sign-change:crypto/
|
||||
implicit-integer-sign-change:getchaintxstats*
|
||||
implicit-integer-sign-change:TxConfirmStats::removeTx
|
||||
implicit-integer-sign-change:prevector.h
|
||||
implicit-integer-sign-change:verify_flags
|
||||
|
||||
Reference in New Issue
Block a user