From 52992ebe1c55c8f7219b824f05d22fbc18acb794 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Fri, 20 Feb 2026 14:23:32 -0800 Subject: [PATCH 1/2] interfaces: Add waitForNotifications() to call SyncWithValidationInterfaceQueue() Co-Authored-By: stickies-v --- src/interfaces/chain.h | 6 ++++++ src/node/interfaces.cpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index e6847b9b161..20369fd2e26 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -326,12 +326,18 @@ public: }; //! Register handler for notifications. + //! Some notifications are asynchronous and may still execute after the handler is disconnected. + //! Use waitForNotifications() after the handler is disconnected to ensure all pending notifications + //! have been processed. virtual std::unique_ptr handleNotifications(std::shared_ptr notifications) = 0; //! Wait for pending notifications to be processed unless block hash points to the current //! chain tip. virtual void waitForNotificationsIfTipChanged(const uint256& old_tip) = 0; + //! Wait for all pending notifications up to this point to be processed + virtual void waitForNotifications() = 0; + //! Register handler for RPC. Command is not copied, so reference //! needs to remain valid until Handler is disconnected. virtual std::unique_ptr handleRpc(const CRPCCommand& command) = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 8f5406ab3c0..16fb977035c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -785,6 +785,10 @@ public: if (!old_tip.IsNull() && old_tip == WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()->GetBlockHash())) return; validation_signals().SyncWithValidationInterfaceQueue(); } + void waitForNotifications() override + { + validation_signals().SyncWithValidationInterfaceQueue(); + } std::unique_ptr handleRpc(const CRPCCommand& command) override { return std::make_unique(command); From 98e8af4bb991fd8edeb15c0fb8afa66bff6b5cac Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Fri, 20 Feb 2026 14:23:51 -0800 Subject: [PATCH 2/2] wallet: Drain validation interface queue after notifications disconnect When unloading a wallet, there may be unexecuted callbacks in the validation interface queue that can still execute after we have completed all of the other wallet shutdown tasks. Instead of letting these run in the background, once the notifications are disconnected, wait for the queue to drain before continuing with wallet shutdown. --- src/wallet/wallet.cpp | 16 +++++++++++++--- src/wallet/wallet.h | 3 +++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 63dab29972d..1e7ad3fc6b8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -169,7 +169,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr& wallet WITH_LOCK(wallet->cs_wallet, wallet->WriteBestBlock()); // Unregister with the validation interface which also drops shared pointers. - wallet->m_chain_notifications_handler.reset(); + wallet->DisconnectChainNotifications(); { LOCK(context.wallets_mutex); std::vector>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet); @@ -3117,7 +3117,7 @@ std::shared_ptr CWallet::CreateNew(WalletContext& context, const std::s walletInstance->TopUpKeyPool(); if (chain && !AttachChain(walletInstance, *chain, /*rescan_required=*/false, error, warnings)) { - walletInstance->m_chain_notifications_handler.reset(); // Reset this pointer so that the wallet will actually be unloaded + walletInstance->DisconnectChainNotifications(); return nullptr; } @@ -3158,7 +3158,7 @@ std::shared_ptr CWallet::LoadExisting(WalletContext& context, const std walletInstance->TopUpKeyPool(); if (chain && !AttachChain(walletInstance, *chain, rescan_required, error, warnings)) { - walletInstance->m_chain_notifications_handler.reset(); // Reset this pointer so that the wallet will actually be unloaded + walletInstance->DisconnectChainNotifications(); return nullptr; } @@ -4577,4 +4577,14 @@ std::optional CWallet::GetTXO(const COutPoint& outpoint) const } return it->second; } + +void CWallet::DisconnectChainNotifications() +{ + if (m_chain_notifications_handler) { + m_chain_notifications_handler->disconnect(); + chain().waitForNotifications(); + m_chain_notifications_handler.reset(); + } +} + } // namespace wallet diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e3d362360c4..fd4b368d781 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1072,6 +1072,9 @@ public: //! Find the private key for the given key id from the wallet's descriptors, if available //! Returns nullopt when no descriptor has the key or if the wallet is locked. std::optional GetKey(const CKeyID& keyid) const; + + //! Disconnect chain notifications and wait for all notifications to be processed + void DisconnectChainNotifications(); }; /**