diff --git a/src/init.cpp b/src/init.cpp index 42331d37e8..191d5e2fbb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -140,6 +140,9 @@ static constexpr bool DEFAULT_REST_ENABLE{false}; static constexpr bool DEFAULT_I2P_ACCEPT_INCOMING{true}; static constexpr bool DEFAULT_STOPAFTERBLOCKIMPORT{false}; +//! Check if initial sync is done with no change in block height or queued downloads every 30s +static constexpr auto SYNC_CHECK_INTERVAL{30s}; + #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for // accessing block files don't count towards the fd_set size limit @@ -1064,6 +1067,44 @@ bool AppInitLockDataDirectory() return true; } +/** + * Once initial block sync is finished and no change in block height or queued downloads, + * sync utxo state to protect against data loss + */ +static void SyncCoinsTipAfterChainSync(const NodeContext& node) +{ + LOCK(node.chainman->GetMutex()); + if (node.chainman->IsInitialBlockDownload()) { + LogPrintfCategory(BCLog::COINDB, "Node is still in IBD, rescheduling chainstate disk sync...\n"); + node.scheduler->scheduleFromNow([&node] { + SyncCoinsTipAfterChainSync(node); + }, SYNC_CHECK_INTERVAL); + return; + } + + static auto last_chain_height{-1}; + const auto current_height{node.chainman->ActiveHeight()}; + if (last_chain_height != current_height) { + LogPrintfCategory(BCLog::COINDB, "Chain height updated since last check, rescheduling chainstate disk sync...\n"); + last_chain_height = current_height; + node.scheduler->scheduleFromNow([&node] { + SyncCoinsTipAfterChainSync(node); + }, SYNC_CHECK_INTERVAL); + return; + } + + if (node.peerman->GetNumberOfPeersWithValidatedDownloads() > 0) { + LogPrintfCategory(BCLog::COINDB, "Still downloading blocks from peers, rescheduling chainstate disk sync...\n"); + node.scheduler->scheduleFromNow([&node] { + SyncCoinsTipAfterChainSync(node); + }, SYNC_CHECK_INTERVAL); + return; + } + + LogPrintfCategory(BCLog::COINDB, "Finished syncing to tip, syncing chainstate to disk\n"); + node.chainman->ActiveChainstate().CoinsTip().Sync(); +} + bool AppInitInterfaces(NodeContext& node) { node.chain = node.init->makeChain(); @@ -1923,6 +1964,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) StartupNotify(args); #endif + node.scheduler->scheduleFromNow([&node] { + SyncCoinsTipAfterChainSync(node); + }, SYNC_CHECK_INTERVAL); + return true; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 3bfb606037..0254673da9 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -518,6 +518,7 @@ public: const std::chrono::microseconds time_received, const std::atomic& interruptMsgProc) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; + int GetNumberOfPeersWithValidatedDownloads() const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main); private: /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ @@ -1713,6 +1714,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c return true; } +int PeerManagerImpl::GetNumberOfPeersWithValidatedDownloads() const +{ + AssertLockHeld(m_chainman.GetMutex()); + return m_peers_downloading_from; +} + void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) { if (m_opts.max_extra_txs <= 0) diff --git a/src/net_processing.h b/src/net_processing.h index 80d07648a4..9464554519 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -110,6 +110,9 @@ public: /** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */ virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0; + + /** Get number of peers from which we're downloading blocks */ + virtual int GetNumberOfPeersWithValidatedDownloads() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) = 0; }; #endif // BITCOIN_NET_PROCESSING_H