mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 15:04:44 +01:00
Merge bitcoin/bitcoin#30320: assumeutxo: Don't load a snapshot if it's not in the best header chain
55b6d7be68
validation: Don't load a snapshot if it's not in the best header chain. (Martin Zumsande) Pull request description: This was suggested by me in the discussion of #30288, which has more context. If the snapshot is not an ancestor of the most-work header (`m_best_header`), syncing from that alternative chain leading to `m_best_header` should be prioritised. Therefore it's not useful loading the snapshot in this situation. If the other chain turns out to be invalid or the chain with the snapshot retrieves additional headers so that it's the most-work one again (see functional test), `m_best_header` will change and loading the snapshot will be possible again. Because of the work required to generate a conflicting headers chain, a situation with two conflicting chains should only be possible under extreme circumstances, such as major forks. ACKs for top commit: fjahr: re-ACK55b6d7be68
achow101: ACK55b6d7be68
alfonsoromanz: Re ACK55b6d7be68
Tree-SHA512: 4fbea5ab1038ae353fc949a186041cf9b397e7ce4ac59ff36f881c9437b4f22ada922490ead5b2661389eb1ca0f3d1e7e7e6a4261057678643e71594a691ac36
This commit is contained in:
commit
0cac45755e
2 changed files with 33 additions and 3 deletions
|
@ -5668,6 +5668,10 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
|
|||
return util::Error{strprintf(Untranslated("The base block header (%s) is part of an invalid chain"), base_blockhash.ToString())};
|
||||
}
|
||||
|
||||
if (!m_best_header || m_best_header->GetAncestor(base_blockheight) != snapshot_start_block) {
|
||||
return util::Error{_("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};
|
||||
}
|
||||
|
||||
auto mempool{m_active_chainstate->GetMempool()};
|
||||
if (mempool && mempool->size() > 0) {
|
||||
return util::Error{Untranslated("Can't activate a snapshot when mempool not empty")};
|
||||
|
|
|
@ -15,20 +15,21 @@ Interesting test cases could be loading an assumeutxo snapshot file with:
|
|||
|
||||
- TODO: Valid snapshot file, but referencing a snapshot block that turns out to be
|
||||
invalid, or has an invalid parent
|
||||
- TODO: Valid snapshot file and snapshot block, but the block is not on the
|
||||
most-work chain
|
||||
|
||||
Interesting starting states could be loading a snapshot when the current chain tip is:
|
||||
|
||||
- TODO: An ancestor of snapshot block
|
||||
- TODO: The snapshot block
|
||||
- TODO: A descendant of the snapshot block
|
||||
- TODO: Not an ancestor or a descendant of the snapshot block and has more work
|
||||
|
||||
"""
|
||||
from shutil import rmtree
|
||||
|
||||
from dataclasses import dataclass
|
||||
from test_framework.blocktools import (
|
||||
create_block,
|
||||
create_coinbase
|
||||
)
|
||||
from test_framework.messages import tx_from_hex
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
|
@ -241,6 +242,30 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
self.sync_blocks(nodes=(n0, n3))
|
||||
self.wait_until(lambda: len(n3.getchainstates()['chainstates']) == 1)
|
||||
|
||||
def test_snapshot_not_on_most_work_chain(self, dump_output_path):
|
||||
self.log.info("Test snapshot is not loaded when the node knows the headers of another chain with more work.")
|
||||
node0 = self.nodes[0]
|
||||
node1 = self.nodes[1]
|
||||
# Create an alternative chain of 2 new blocks, forking off the main chain at the block before the snapshot block.
|
||||
# This simulates a longer chain than the main chain when submitting these two block headers to node 1 because it is only aware of
|
||||
# the main chain headers up to the snapshot height.
|
||||
parent_block_hash = node0.getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
|
||||
block_time = node0.getblock(node0.getbestblockhash())['time'] + 1
|
||||
fork_block1 = create_block(int(parent_block_hash, 16), create_coinbase(SNAPSHOT_BASE_HEIGHT), block_time)
|
||||
fork_block1.solve()
|
||||
fork_block2 = create_block(fork_block1.sha256, create_coinbase(SNAPSHOT_BASE_HEIGHT + 1), block_time + 1)
|
||||
fork_block2.solve()
|
||||
node1.submitheader(fork_block1.serialize().hex())
|
||||
node1.submitheader(fork_block2.serialize().hex())
|
||||
msg = "A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo."
|
||||
assert_raises_rpc_error(-32603, msg, node1.loadtxoutset, dump_output_path)
|
||||
# Cleanup: submit two more headers of the snapshot chain to node 1, so that it is the most-work chain again and loading
|
||||
# the snapshot in future subtests succeeds
|
||||
main_block1 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 1), 0)
|
||||
main_block2 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 2), 0)
|
||||
node1.submitheader(main_block1)
|
||||
node1.submitheader(main_block2)
|
||||
|
||||
def run_test(self):
|
||||
"""
|
||||
Bring up two (disconnected) nodes, mine some new blocks on the first,
|
||||
|
@ -330,6 +355,7 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
self.test_invalid_chainstate_scenarios()
|
||||
self.test_invalid_file_path()
|
||||
self.test_snapshot_block_invalidated(dump_output['path'])
|
||||
self.test_snapshot_not_on_most_work_chain(dump_output['path'])
|
||||
|
||||
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
|
||||
loaded = n1.loadtxoutset(dump_output['path'])
|
||||
|
|
Loading…
Add table
Reference in a new issue