From 963472a1ca10c6fe40117aeae085db6b87646bb6 Mon Sep 17 00:00:00 2001 From: josibake Date: Tue, 5 Nov 2024 13:07:51 +0100 Subject: [PATCH] assumeutxo benchmarking patch introduce two commandline options for assumeutxo, specifically for benchmarking. these commands are: - pausebackgroundsync - an option lets the user pause the verification of historical blocks in the background -loadutxosnapshot= - load an assumeutxo snapshot on startup, instead of needing to go through the rpc command. the node will shutdown immediately after the snapshot has been loaded this path is not meant for general use and is instead just for making it more ergonomic to use assumeutxo for benchmarking IBD changes. the benefits of using assumeutxo here are we can start from an arbitrary height and sync to chaintip to collect relevant data quickly. using assumeutxo means we can make whatever changes we need to the chainstatedb, since it will be created fresh from the snapshot. note, to use the loadutxosnapshot option, you must first run: ./build/src/bitcoind -stopatheight=1 this makes the node do a header sync and then shut down. this is because assumeutxo will not load a snapshot unless the base block is in the header chain. we could remove this requirement, but this patch is meant to be as minimal as possible, and this also allows us to perform heaeder sync as a preparation commit for a benchmark, which helps keep IBD benchmarks more focused on strictly measuring IBD. next, run: ./build/src/bitcoind -loadutxosnapshot= the node will shutdown after the snapshot is loaded. finally, run: ./build/src/bitcoind -pausebackgroundsync=1 for the actual benchmarking step. this ensures only the sync to chaintip is benchmarked and the load snapshot step is not included in the measurement. Co-authored-by: Sjors Provoost <10217+sjors@users.noreply.github.com> --- src/init.cpp | 59 ++++++++++++++++++++++++++++- src/kernel/chainstatemanager_opts.h | 2 + src/node/chainstatemanager_args.cpp | 2 + src/node/chainstatemanager_args.h | 2 + src/validation.cpp | 6 +++ src/validation.h | 4 +- 6 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 55e01e090b59d..78b3c7d77482d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include // IWYU pragma: keep +#include "node/utxo_snapshot.h" #include @@ -140,6 +141,7 @@ using node::VerifyLoadedChainstate; using util::Join; using util::ReplaceAll; using util::ToString; +using node::SnapshotMetadata; static constexpr bool DEFAULT_PROXYRANDOMIZE{true}; static constexpr bool DEFAULT_REST_ENABLE{false}; @@ -158,6 +160,44 @@ static constexpr bool DEFAULT_STOPAFTERBLOCKIMPORT{false}; static constexpr int MIN_CORE_FDS = MIN_LEVELDB_FDS + NUM_FDS_MESSAGE_CAPTURE; static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map"; +bool LoadUTXOSnapshot(NodeContext& node, const fs::path& snapshot_path) { + ChainstateManager& chainman = *node.chainman; + + FILE* file{fsbridge::fopen(snapshot_path, "rb")}; + AutoFile afile{file}; + if (afile.IsNull()) { + LogPrintf("Error: Couldn't open UTXO snapshot file %s for reading\n", snapshot_path.utf8string()); + return false; + } + + SnapshotMetadata metadata{chainman.GetParams().MessageStart()}; + try { + afile >> metadata; + } catch (const std::ios_base::failure& e) { + LogPrintf("Error: Unable to parse snapshot metadata: %s\n", e.what()); + return false; + } + + auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)}; + if (!activation_result) { + LogPrintf("Error: Unable to load UTXO snapshot: %s\n", + util::ErrorString(activation_result).original); + return false; + } + + // Update services to reflect limited peer capabilities during sync + node.connman->RemoveLocalServices(NODE_NETWORK); + node.connman->AddLocalServices(NODE_NETWORK_LIMITED); + + CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)}; + LogPrintf("Loaded UTXO snapshot: coins=%d, height=%d, hash=%s\n", + metadata.m_coins_count, + snapshot_index.nHeight, + snapshot_index.GetBlockHash().ToString()); + + return true; +} + /** * The PID file facilities. */ @@ -495,6 +535,12 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-minimumchainwork=", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnet4ChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-par=", strprintf("Set the number of script verification threads (0 = auto, up to %d, <0 = leave that many cores free, default: %d)", MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-pausebackgroundsync", strprintf("When a UTXO snapshot is loaded, pause the verification of historical blocks in the background (default: %u)", DEFAULT_PAUSE_BACKGROUND_SYNC), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-loadutxosnapshot=", + "Load UTXO set from snapshot file at startup. " + "This allows fast synchronization by loading a pre-built UTXO " + "snapshot while the full chain validation happens in background.", + ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempoolv1", strprintf("Whether a mempool.dat file created by -persistmempool or the savemempool RPC will be written in the legacy format " @@ -1661,6 +1707,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) ChainstateManager& chainman = *Assert(node.chainman); + if (args.IsArgSet("-loadutxosnapshot")) { + fs::path snapshot_path = fs::u8path(args.GetArg("-loadutxosnapshot", "")); + snapshot_path = AbsPathForConfigVal(args, snapshot_path); + + if (!LoadUTXOSnapshot(node, snapshot_path)) { + LogPrintf("Failed to load UTXO snapshot from %s", snapshot_path.utf8string()); + } + } + assert(!node.peerman); node.peerman = PeerManager::make(*node.connman, *node.addrman, node.banman.get(), chainman, @@ -1800,7 +1855,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) }); } - if (ShutdownRequested(node)) { + // if loadutxosnapshot is set, we want to load the snapshot then shut down so that only + // syncing to chaintip is benchmarked + if (ShutdownRequested(node) || args.IsArgSet("-loadutxosnapshot")) { return false; } diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 1b605f3d55dfc..dee7292b6310c 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -51,6 +51,8 @@ struct ChainstateManagerOpts { int worker_threads_num{0}; size_t script_execution_cache_bytes{DEFAULT_SCRIPT_EXECUTION_CACHE_BYTES}; size_t signature_cache_bytes{DEFAULT_SIGNATURE_CACHE_BYTES}; + //! Whether to defer syncing the background chainstate after an assumeutxo snapshot is loaded + bool pause_background_sync{false}; }; } // namespace kernel diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp index b86d0b2991325..683c9cef7a23c 100644 --- a/src/node/chainstatemanager_args.cpp +++ b/src/node/chainstatemanager_args.cpp @@ -73,6 +73,8 @@ util::Result ApplyArgsManOptions(const ArgsManager& args, ChainstateManage opts.signature_cache_bytes = clamped_size_each; } + opts.pause_background_sync = args.GetBoolArg("-pausebackgroundsync", DEFAULT_PAUSE_BACKGROUND_SYNC); + return {}; } } // namespace node diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h index b2cdba68b8fe9..6a7bb27478d66 100644 --- a/src/node/chainstatemanager_args.h +++ b/src/node/chainstatemanager_args.h @@ -14,6 +14,8 @@ class ArgsManager; static constexpr int MAX_SCRIPTCHECK_THREADS{15}; /** -par default (number of script-checking threads, 0 = auto) */ static constexpr int DEFAULT_SCRIPTCHECK_THREADS{0}; +/** -pausebackgroundsync default */ +static const bool DEFAULT_PAUSE_BACKGROUND_SYNC{false}; namespace node { [[nodiscard]] util::Result ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts); diff --git a/src/validation.cpp b/src/validation.cpp index f9288fcc0d7c0..8227a5b0a72cf 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -6394,6 +6394,12 @@ std::optional ChainstateManager::GetSnapshotBaseHeight() const return base ? std::make_optional(base->nHeight) : std::nullopt; } +bool ChainstateManager::BackgroundSyncInProgress() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { + if (!IsUsable(m_snapshot_chainstate.get())) return false; + if (!IsUsable(m_ibd_chainstate.get())) return false; + return !m_options.pause_background_sync; +} + bool ChainstateManager::ValidatedSnapshotCleanup() { AssertLockHeld(::cs_main); diff --git a/src/validation.h b/src/validation.h index f6aeea3faaf75..f462354e5f81a 100644 --- a/src/validation.h +++ b/src/validation.h @@ -1113,9 +1113,7 @@ class ChainstateManager CBlockIndex* ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Tip(); } //! The state of a background sync (for net processing) - bool BackgroundSyncInProgress() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { - return IsUsable(m_snapshot_chainstate.get()) && IsUsable(m_ibd_chainstate.get()); - } + bool BackgroundSyncInProgress() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()); //! The tip of the background sync chain const CBlockIndex* GetBackgroundSyncTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {