From 0067abe153298ce9f14262a15533033e6e907f2b Mon Sep 17 00:00:00 2001 From: stringintech Date: Thu, 18 Dec 2025 02:04:45 +0330 Subject: [PATCH 1/2] p2p: Allow block downloads from peers without snapshot block after assumeutxo validation After assumeutxo background validation completes, allow block downloads from peers that don't have the snapshot block in their best chain. Previously, these peers were skipped until restart because `m_chainman.CurrentChainstate().SnapshotBase()` continued returning non-null even after validation finished. Add `m_chainman.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED` check to only apply the restriction while background validation is ongoing. --- src/net_processing.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 51dcadad6f8..a3707d3b5f4 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1385,11 +1385,13 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co return; } - // When we sync with AssumeUtxo and discover the snapshot is not in the peer's best chain, abort: - // We can't reorg to this chain due to missing undo data until the background sync has finished, + // When syncing with AssumeUtxo and the snapshot has not yet been validated, + // abort downloading blocks from peers that don't have the snapshot block in their best chain. + // We can't reorg to this chain due to missing undo data until validation completes, // so downloading blocks from it would be futile. const CBlockIndex* snap_base{m_chainman.CurrentChainstate().SnapshotBase()}; - if (snap_base && state->pindexBestKnownBlock->GetAncestor(snap_base->nHeight) != snap_base) { + if (snap_base && m_chainman.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED && + state->pindexBestKnownBlock->GetAncestor(snap_base->nHeight) != snap_base) { LogDebug(BCLog::NET, "Not downloading blocks from peer=%d, which doesn't have the snapshot block in its best chain.\n", peer.m_id); return; } From 7d9e1a810239a65a153c35f0f94490560441db49 Mon Sep 17 00:00:00 2001 From: stringintech Date: Thu, 18 Dec 2025 02:06:57 +0330 Subject: [PATCH 2/2] test: Verify peer usage after assumeutxo validation completes Add test coverage to ensure peers without the snapshot block in their chain can be used for block downloads after background validation completes. The test fails without the fix in the previous commit. --- test/functional/feature_assumeutxo.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index 20ebd823d13..7420dfe02a0 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -347,6 +347,29 @@ class AssumeutxoTest(BitcoinTestFramework): assert 'NETWORK' in ibd_node.getpeerinfo()[0]['servicesnames'] self.sync_blocks(nodes=(ibd_node, snapshot_node)) + def test_sync_to_most_work_chain_after_background_validation(self): + """ + After background validation completes, node should be able + to download and process blocks from peers without the snapshot block in their chain. + """ + self.log.info("Testing sync to the most-work chain without the snapshot block after background validation") + + forking_node = self.nodes[0] + snapshot_node = self.nodes[2] # Has already completed background validation + + self.log.info("Forking node switches to an alternative chain that forks one block before the snapshot block") + fork_point = SNAPSHOT_BASE_HEIGHT - 1 + forking_node_old_height = forking_node.getblockcount() + forking_node_old_chainwork = int(forking_node.getblockchaininfo()['chainwork'], 16) + forking_node.invalidateblock(forking_node.getblockhash(fork_point + 1)) + + self.log.info("Mine one more block than original chain to make the new chain have most work") + self.generate(forking_node, nblocks=(forking_node_old_height - fork_point) + 1, sync_fun=self.no_op) + assert int(forking_node.getblockchaininfo()['chainwork'], 16) > forking_node_old_chainwork + + self.log.info("Snapshot node should reorg to the most-work chain without the snapshot block") + self.sync_blocks(nodes=(snapshot_node, forking_node)) + def assert_only_network_limited_service(self, node): node_services = node.getnetworkinfo()['localservicesnames'] assert 'NETWORK' not in node_services @@ -775,6 +798,8 @@ class AssumeutxoTest(BitcoinTestFramework): # The following test cleans node2 and node3 chain directories. self.test_sync_from_assumeutxo_node(snapshot=dump_output) + self.test_sync_to_most_work_chain_after_background_validation() + @dataclass class Block: hash: str