diff --git a/doc/design/assumeutxo.md b/doc/design/assumeutxo.md index 8068a93f27a7d..75a7b6c8666ec 100644 --- a/doc/design/assumeutxo.md +++ b/doc/design/assumeutxo.md @@ -1,7 +1,7 @@ # assumeutxo Assumeutxo is a feature that allows fast bootstrapping of a validating bitcoind -instance with a very similar security model to assumevalid. +instance. The RPC commands `dumptxoutset` and `loadtxoutset` are used to respectively generate and load UTXO snapshots. The utility script diff --git a/doc/release-notes-27596.md b/doc/release-notes-27596.md index 799b82643fec6..cbaf4b3a9e3ee 100644 --- a/doc/release-notes-27596.md +++ b/doc/release-notes-27596.md @@ -12,7 +12,7 @@ RPC `loadtxoutset` has been added, which allows loading a UTXO snapshot of the format generated by `dumptxoutset`. Once this snapshot is loaded, its contents will be deserialized into a second chainstate data structure, which is then used to sync to -the network's tip under a security model very much like `assumevalid`. +the network's tip. Meanwhile, the original chainstate will complete the initial block download process in the background, eventually validating up to the block that the snapshot is based upon. diff --git a/src/chain.h b/src/chain.h index 78b06719f43a4..4bf2001f74b1e 100644 --- a/src/chain.h +++ b/src/chain.h @@ -280,10 +280,8 @@ class CBlockIndex * 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. - * - * TODO: potentially change the name of this based on the fact above. */ - bool HaveTxsDownloaded() const { return nChainTx != 0; } + bool HaveNumChainTxs() const { return nChainTx != 0; } NodeSeconds Time() const { diff --git a/src/init.cpp b/src/init.cpp index a0b4425898177..98f233d9dfc90 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -462,8 +462,8 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-prune=", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk. This will also rebuild active optional indexes.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex", "If enabled, wipe chain state and block index, and rebuild them from blk*.dat files on disk. Also wipe and rebuild other optional indexes that are active. If an assumeutxo snapshot was loaded, its chainstate will be wiped as well. The snapshot can then be reloaded via RPC.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex-chainstate", "If enabled, wipe chain state, and rebuild it from blk*.dat files on disk. If an assumeutxo snapshot was loaded, its chainstate will be wiped as well. The snapshot can then be reloaded via RPC.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-settings=", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-startupnotify=", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 5e893a3f58c4d..644e2a2c67123 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -495,7 +495,7 @@ class CRegTestParams : public CChainParams { .height = 110, .hash_serialized = AssumeutxoHash{uint256S("0x1ebbf5850204c0bdb15bf030f47c7fe91d45c44c712697e4509ba67adb01c618")}, - .nChainTx = 110, + .nChainTx = 111, .blockhash = uint256S("0x696e92821f65549c7ee134edceeeeaaa4105647a3c4fd9f298c0aec0ab50425c") }, { diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a8d1553eed688..84ccc54f03319 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1448,7 +1448,7 @@ void PeerManagerImpl::FindNextBlocks(std::vector& vBlocks, c return; } if (pindex->nStatus & BLOCK_HAVE_DATA || (activeChain && activeChain->Contains(pindex))) { - if (activeChain && pindex->HaveTxsDownloaded()) + if (activeChain && pindex->HaveNumChainTxs()) state->pindexLastCommonBlock = pindex; } else if (!IsBlockRequested(pindex->GetBlockHash())) { // The block is not already downloaded, and not yet in flight. @@ -1937,6 +1937,8 @@ void PeerManagerImpl::BlockConnected( } } + // The following task can be skipped since we don't maintain a mempool for + // the ibd/background chainstate. if (role == ChainstateRole::BACKGROUND) { return; } @@ -2231,7 +2233,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& LOCK(cs_main); const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); if (pindex) { - if (pindex->HaveTxsDownloaded() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && + if (pindex->HaveNumChainTxs() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && pindex->IsValid(BLOCK_VALID_TREE)) { // If we have the block and all of its parents, but have not yet validated it, // we might be in the middle of connecting it (ie in the unlock of cs_main diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 706b62ea9bc76..931db80274001 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -761,12 +761,14 @@ bool BlockManager::FlushChainstateBlockFile(int tip_height) { LOCK(cs_LastBlockFile); auto& cursor = m_blockfile_cursors[BlockfileTypeForHeight(tip_height)]; + // If the cursor does not exist, it means an assumeutxo snapshot is loaded, + // but no blocks past the snapshot height have been written yet, so there + // is no data associated with the chainstate, and it is safe not to flush. if (cursor) { - // The cursor may not exist after a snapshot has been loaded but before any - // blocks have been downloaded. return FlushBlockFile(cursor->file_num, /*fFinalize=*/false, /*finalize_undo=*/false); } - return false; + // No need to log warnings in this case. + return true; } uint64_t BlockManager::CalculateCurrentUsage() diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 16ca1d9156ba8..eb1994177a109 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -185,7 +185,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize chainman.InitializeChainstate(options.mempool); // Load a chain created from a UTXO snapshot, if any exist. - bool has_snapshot = chainman.DetectSnapshotChainstate(options.mempool); + bool has_snapshot = chainman.DetectSnapshotChainstate(); if (has_snapshot && (options.reindex || options.reindex_chainstate)) { LogPrintf("[snapshot] deleting snapshot chainstate due to reindexing\n"); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index abd723ee56223..31dd8164fe643 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1455,7 +1455,7 @@ static RPCHelpMan getchaintips() } else if (block->nStatus & BLOCK_FAILED_MASK) { // This block or one of its ancestors is invalid. status = "invalid"; - } else if (!block->HaveTxsDownloaded()) { + } else if (!block->HaveNumChainTxs()) { // This block cannot be connected because full block data for it or one of its parents is missing. status = "headers-only"; } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) { @@ -2707,7 +2707,7 @@ static RPCHelpMan loadtxoutset() "Load the serialized UTXO set from disk.\n" "Once this snapshot is loaded, its contents will be " "deserialized into a second chainstate data structure, which is then used to sync to " - "the network's tip under a security model very much like `assumevalid`. " + "the network's tip. " "Meanwhile, the original chainstate will complete the initial block download process in " "the background, eventually validating up to the block that the snapshot is based upon.\n\n" @@ -2759,7 +2759,7 @@ static RPCHelpMan loadtxoutset() LogPrintf("[snapshot] waiting to see blockheader %s in headers chain before snapshot activation\n", base_blockhash.ToString()); - ChainstateManager& chainman = *node.chainman; + ChainstateManager& chainman = EnsureChainman(node); while (max_secs_to_wait_for_headers > 0) { snapshot_start_block = WITH_LOCK(::cs_main, @@ -2831,8 +2831,7 @@ return RPCHelpMan{ LOCK(cs_main); UniValue obj(UniValue::VOBJ); - NodeContext& node = EnsureAnyNodeContext(request.context); - ChainstateManager& chainman = *node.chainman; + ChainstateManager& chainman = EnsureAnyChainman(request.context); auto make_chain_data = [&](const Chainstate& cs, bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); diff --git a/src/test/fuzz/chain.cpp b/src/test/fuzz/chain.cpp index 49b9898228626..0363f317b63f9 100644 --- a/src/test/fuzz/chain.cpp +++ b/src/test/fuzz/chain.cpp @@ -29,7 +29,7 @@ FUZZ_TARGET(chain) (void)disk_block_index->GetBlockTimeMax(); (void)disk_block_index->GetMedianTimePast(); (void)disk_block_index->GetUndoPos(); - (void)disk_block_index->HaveTxsDownloaded(); + (void)disk_block_index->HaveNumChainTxs(); (void)disk_block_index->IsValid(); } diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index d34d98c219a83..2692037273250 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -138,11 +138,11 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo) const auto out110 = *params->AssumeutxoForHeight(110); BOOST_CHECK_EQUAL(out110.hash_serialized.ToString(), "1ebbf5850204c0bdb15bf030f47c7fe91d45c44c712697e4509ba67adb01c618"); - BOOST_CHECK_EQUAL(out110.nChainTx, 110U); + BOOST_CHECK_EQUAL(out110.nChainTx, 111U); const auto out110_2 = *params->AssumeutxoForBlockhash(uint256S("0x696e92821f65549c7ee134edceeeeaaa4105647a3c4fd9f298c0aec0ab50425c")); BOOST_CHECK_EQUAL(out110_2.hash_serialized.ToString(), "1ebbf5850204c0bdb15bf030f47c7fe91d45c44c712697e4509ba67adb01c618"); - BOOST_CHECK_EQUAL(out110_2.nChainTx, 110U); + BOOST_CHECK_EQUAL(out110_2.nChainTx, 111U); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index e24c187591a8e..9108f911f04ee 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -68,8 +68,8 @@ #include #include #include -#include #include +#include using kernel::CCoinsStats; using kernel::CoinStatsHashType; @@ -2996,7 +2996,7 @@ CBlockIndex* Chainstate::FindMostWorkChain() CBlockIndex *pindexTest = pindexNew; bool fInvalidAncestor = false; while (pindexTest && !m_chain.Contains(pindexTest)) { - assert(pindexTest->HaveTxsDownloaded() || pindexTest->nHeight == 0); + assert(pindexTest->HaveNumChainTxs() || pindexTest->nHeight == 0); // Pruned nodes may have entries in setBlockIndexCandidates for // which block files have been deleted. Remove those as candidates @@ -3351,7 +3351,7 @@ bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) // call preciousblock 2**31-1 times on the same set of tips... m_chainman.nBlockReverseSequenceId--; } - if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->HaveTxsDownloaded()) { + if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->HaveNumChainTxs()) { setBlockIndexCandidates.insert(pindex); PruneBlockIndexCandidates(); } @@ -3399,7 +3399,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde if (!m_chain.Contains(candidate) && !CBlockIndexWorkComparator()(candidate, pindex->pprev) && candidate->IsValid(BLOCK_VALID_TRANSACTIONS) && - candidate->HaveTxsDownloaded()) { + candidate->HaveNumChainTxs()) { candidate_blocks_by_work.insert(std::make_pair(candidate->nChainWork, candidate)); } } @@ -3488,7 +3488,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde // Loop back over all block index entries and add any missing entries // to setBlockIndexCandidates. for (auto& [_, block_index] : m_blockman.m_block_index) { - if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(&block_index, m_chain.Tip())) { + if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveNumChainTxs() && !setBlockIndexCandidates.value_comp()(&block_index, m_chain.Tip())) { setBlockIndexCandidates.insert(&block_index); } } @@ -3520,7 +3520,7 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) { if (!block_index.IsValid() && block_index.GetAncestor(nHeight) == pindex) { block_index.nStatus &= ~BLOCK_FAILED_MASK; m_blockman.m_dirty_blockindex.insert(&block_index); - if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveTxsDownloaded() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) { + if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveNumChainTxs() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) { setBlockIndexCandidates.insert(&block_index); } if (&block_index == m_chainman.m_best_invalid) { @@ -3583,7 +3583,7 @@ void ChainstateManager::ReceivedBlockTransactions(const CBlock& block, CBlockInd pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); m_blockman.m_dirty_blockindex.insert(pindexNew); - if (pindexNew->pprev == nullptr || pindexNew->pprev->HaveTxsDownloaded()) { + if (pindexNew->pprev == nullptr || pindexNew->pprev->HaveNumChainTxs()) { // If pindexNew is the genesis block or all parents are BLOCK_VALID_TRANSACTIONS. std::deque queue; queue.push_back(pindexNew); @@ -4566,7 +4566,7 @@ bool ChainstateManager::LoadBlockIndex() // here. if (pindex == GetSnapshotBaseBlock() || (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && - (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) { + (pindex->HaveNumChainTxs() || pindex->pprev == nullptr))) { for (Chainstate* chainstate : GetAll()) { chainstate->TryAddBlockIndexCandidate(pindex); @@ -4844,10 +4844,14 @@ void ChainstateManager::CheckBlockIndex() CBlockIndex* pindexFirstAssumeValid = nullptr; // Oldest ancestor of pindex which has BLOCK_ASSUMED_VALID while (pindex != nullptr) { nNodes++; - if (pindex->pprev && pindex->nTx > 0) { - // nChainTx should increase monotonically - assert(pindex->pprev->nChainTx <= pindex->nChainTx); - } + // Make sure nChainTx sum is correctly computed. + unsigned int prev_chain_tx = pindex->pprev ? pindex->pprev->nChainTx : 0; + assert((pindex->nChainTx == pindex->nTx + prev_chain_tx) + // For testing, allow transaction counts to be completely unset. + || (pindex->nChainTx == 0 && pindex->nTx == 0) + // For testing, allow this nChainTx to be unset if previous is also unset. + || (pindex->nChainTx == 0 && prev_chain_tx == 0 && pindex->pprev)); + if (pindexFirstAssumeValid == nullptr && pindex->nStatus & BLOCK_ASSUMED_VALID) pindexFirstAssumeValid = pindex; if (pindexFirstInvalid == nullptr && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex; if (pindexFirstMissing == nullptr && !(pindex->nStatus & BLOCK_HAVE_DATA)) { @@ -4886,7 +4890,7 @@ void ChainstateManager::CheckBlockIndex() } } } - if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) + if (!pindex->HaveNumChainTxs()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) // VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred). // HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred. // Unless these indexes are assumed valid and pending block download on a @@ -4916,9 +4920,9 @@ void ChainstateManager::CheckBlockIndex() // actually seen a block's transactions. assert(((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TRANSACTIONS) == (pindex->nTx > 0)); // This is pruning-independent. } - // All parents having had data (at some point) is equivalent to all parents being VALID_TRANSACTIONS, which is equivalent to HaveTxsDownloaded(). - assert((pindexFirstNeverProcessed == nullptr) == pindex->HaveTxsDownloaded()); - assert((pindexFirstNotTransactionsValid == nullptr) == pindex->HaveTxsDownloaded()); + // All parents having had data (at some point) is equivalent to all parents being VALID_TRANSACTIONS, which is equivalent to HaveNumChainTxs(). + assert((pindexFirstNeverProcessed == nullptr) == pindex->HaveNumChainTxs()); + assert((pindexFirstNotTransactionsValid == nullptr) == pindex->HaveNumChainTxs()); assert(pindex->nHeight == nHeight); // nHeight must be consistent. assert(pindex->pprev == nullptr || pindex->nChainWork >= pindex->pprev->nChainWork); // For every block except the genesis block, the chainwork must be larger than the parent's. assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks. @@ -5367,7 +5371,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // ActivateSnapshot(), but is done so that we avoid doing the long work of staging // a snapshot that isn't actually usable. if (WITH_LOCK(::cs_main, return !CBlockIndexWorkComparator()(ActiveTip(), snapshot_start_block))) { - LogPrintf("[snapshot] activation failed - height does not exceed active chainstate\n"); + LogPrintf("[snapshot] activation failed - work does not exceed active chainstate\n"); return false; } @@ -5768,7 +5772,7 @@ ChainstateManager::~ChainstateManager() m_versionbitscache.Clear(); } -bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool) +bool ChainstateManager::DetectSnapshotChainstate() { assert(!m_snapshot_chainstate); std::optional path = node::FindSnapshotChainstateDir(m_options.datadir); diff --git a/src/validation.h b/src/validation.h index 94a00e44a4eed..7ce60da6340b1 100644 --- a/src/validation.h +++ b/src/validation.h @@ -836,9 +836,10 @@ class ChainstateManager //! Once this pointer is set to a corresponding chainstate, it will not //! be reset until init.cpp:Shutdown(). //! - //! This is especially important when, e.g., calling ActivateBestChain() - //! on all chainstates because we are not able to hold ::cs_main going into - //! that call. + //! It is important for the pointer to not be deleted until shutdown, + //! because cs_main is not always held when the pointer is accessed, for + //! example when calling ActivateBestChain, so there's no way you could + //! prevent code from using the pointer while deleting it. std::unique_ptr m_ibd_chainstate GUARDED_BY(::cs_main); //! A chainstate initialized on the basis of a UTXO snapshot. If this is @@ -847,17 +848,14 @@ class ChainstateManager //! Once this pointer is set to a corresponding chainstate, it will not //! be reset until init.cpp:Shutdown(). //! - //! This is especially important when, e.g., calling ActivateBestChain() - //! on all chainstates because we are not able to hold ::cs_main going into - //! that call. + //! It is important for the pointer to not be deleted until shutdown, + //! because cs_main is not always held when the pointer is accessed, for + //! example when calling ActivateBestChain, so there's no way you could + //! prevent code from using the pointer while deleting it. std::unique_ptr m_snapshot_chainstate GUARDED_BY(::cs_main); //! Points to either the ibd or snapshot chainstate; indicates our //! most-work chain. - //! - //! This is especially important when, e.g., calling ActivateBestChain() - //! on all chainstates because we are not able to hold ::cs_main going into - //! that call. Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; @@ -1203,7 +1201,7 @@ class ChainstateManager //! When starting up, search the datadir for a chainstate based on a UTXO //! snapshot that is in the process of being validated. - bool DetectSnapshotChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool DetectSnapshotChainstate() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index 15cacc204c10e..6c09a6f5e751d 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -36,7 +36,7 @@ """ from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, wait_until_helper +from test_framework.util import assert_equal START_HEIGHT = 199 SNAPSHOT_BASE_HEIGHT = 299 @@ -80,16 +80,13 @@ def run_test(self): self.sync_blocks() - def no_sync(): - pass - # Generate a series of blocks that `n0` will have in the snapshot, # but that n1 doesn't yet see. In order for the snapshot to activate, # though, we have to ferry over the new headers to n1 so that it # isn't waiting forever to see the header of the snapshot's base block # while disconnected from n0. for i in range(100): - self.generate(n0, nblocks=1, sync_fun=no_sync) + self.generate(n0, nblocks=1, sync_fun=self.no_op) newblock = n0.getblock(n0.getbestblockhash(), 0) # make n1 aware of the new header, but don't give it the block. @@ -116,7 +113,7 @@ def no_sync(): # Mine more blocks on top of the snapshot that n1 hasn't yet seen. This # will allow us to test n1's sync-to-tip on top of a snapshot. - self.generate(n0, nblocks=100, sync_fun=no_sync) + self.generate(n0, nblocks=100, sync_fun=self.no_op) assert_equal(n0.getblockcount(), FINAL_HEIGHT) assert_equal(n1.getblockcount(), START_HEIGHT) @@ -162,11 +159,11 @@ def no_sync(): self.connect_nodes(0, 1) self.log.info(f"Ensuring snapshot chain syncs to tip. ({FINAL_HEIGHT})") - wait_until_helper(lambda: n1.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) + self.wait_until(lambda: n1.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) self.sync_blocks(nodes=(n0, n1)) self.log.info("Ensuring background validation completes") - wait_until_helper(lambda: len(n1.getchainstates()['chainstates']) == 1) + self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1) # Ensure indexes have synced. completed_idx_state = { @@ -211,11 +208,11 @@ def no_sync(): assert_equal(snapshot['validated'], False) self.connect_nodes(0, 2) - wait_until_helper(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) + self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) self.sync_blocks() self.log.info("Ensuring background validation completes") - wait_until_helper(lambda: len(n2.getchainstates()['chainstates']) == 1) + self.wait_until(lambda: len(n2.getchainstates()['chainstates']) == 1) completed_idx_state = { 'basic block filter index': COMPLETE_IDX, @@ -242,12 +239,12 @@ def no_sync(): self.restart_node(2, extra_args=[ '-reindex-chainstate=1', *self.extra_args[2]]) assert_equal(n2.getblockchaininfo()["blocks"], FINAL_HEIGHT) - wait_until_helper(lambda: n2.getblockcount() == FINAL_HEIGHT) + self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT) self.log.info("Test -reindex of an assumeutxo-synced node") self.restart_node(2, extra_args=['-reindex=1', *self.extra_args[2]]) self.connect_nodes(0, 2) - wait_until_helper(lambda: n2.getblockcount() == FINAL_HEIGHT) + self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT) if __name__ == '__main__': diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index ceb4bbd7dea95..be4ed624fce9d 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -77,7 +77,7 @@ from test_framework.util import ( MAX_NODES, p2p_port, - wait_until_helper, + wait_until_helper_internal, ) logger = logging.getLogger("TestFramework.p2p") @@ -466,7 +466,7 @@ def test_function(): assert self.is_connected return test_function_in() - wait_until_helper(test_function, timeout=timeout, lock=p2p_lock, timeout_factor=self.timeout_factor) + wait_until_helper_internal(test_function, timeout=timeout, lock=p2p_lock, timeout_factor=self.timeout_factor) def wait_for_connect(self, timeout=60): test_function = lambda: self.is_connected @@ -602,7 +602,7 @@ def run(self): def close(self, timeout=10): """Close the connections and network event loop.""" self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop) - wait_until_helper(lambda: not self.network_event_loop.is_running(), timeout=timeout) + wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout) self.network_event_loop.close() self.join(timeout) # Safe to remove event loop. diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index a34c34713e922..c46c04c0ec6ed 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -33,7 +33,7 @@ get_datadir_path, initialize_datadir, p2p_port, - wait_until_helper, + wait_until_helper_internal, ) @@ -747,7 +747,7 @@ def sync_all(self, nodes=None): self.sync_mempools(nodes) def wait_until(self, test_function, timeout=60): - return wait_until_helper(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor) + return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor) # Private helper methods. These should not be accessed by the subclass test scripts. diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 7e85935807d9e..33a753964123f 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -36,7 +36,7 @@ get_auth_cookie, get_rpc_proxy, rpc_url, - wait_until_helper, + wait_until_helper_internal, p2p_port, ) @@ -253,7 +253,7 @@ def wait_for_rpc_connection(self): if self.version_is_at_least(190000): # getmempoolinfo.loaded is available since commit # bb8ae2c (version 0.19.0) - wait_until_helper(lambda: rpc.getmempoolinfo()['loaded'], timeout_factor=self.timeout_factor) + wait_until_helper_internal(lambda: rpc.getmempoolinfo()['loaded'], timeout_factor=self.timeout_factor) # Wait for the node to finish reindex, block import, and # loading the mempool. Usually importing happens fast or # even "immediate" when the node is started. However, there @@ -407,7 +407,7 @@ def is_node_stopped(self, *, expected_stderr="", expected_ret_code=0): def wait_until_stopped(self, *, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False, **kwargs): expected_ret_code = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS - wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor) + wait_until_helper_internal(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor) def replace_in_config(self, replacements): """ @@ -718,7 +718,7 @@ def disconnect_p2ps(self): p.peer_disconnect() del self.p2ps[:] - wait_until_helper(lambda: self.num_test_p2p_connections() == 0, timeout_factor=self.timeout_factor) + wait_until_helper_internal(lambda: self.num_test_p2p_connections() == 0, timeout_factor=self.timeout_factor) def bumpmocktime(self, seconds): """Fast forward using setmocktime to self.mocktime + seconds. Requires setmocktime to have diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 3bd18c26d856e..0c10d500afcb9 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -241,7 +241,7 @@ def satoshi_round(amount): return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) -def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): +def wait_until_helper_internal(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): """Sleep until the predicate resolves to be True. Warning: Note that this method is not recommended to be used in tests as it is