From ab31b9d6fe7b39713682e3f52d11238dbe042c16 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 10 Mar 2020 15:46:20 -0400 Subject: Fix wallet unload race condition Currently it's possible for ReleaseWallet to delete the CWallet pointer while it is processing BlockConnected, etc chain notifications. To fix this, unregister from notifications earlier in UnloadWallet instead of ReleaseWallet, and use a new RegisterSharedValidationInterface function to prevent the CValidationInterface shared_ptr from being deleted until the last notification is actually finished. --- src/wallet/test/wallet_test_fixture.cpp | 3 +-- src/wallet/test/wallet_test_fixture.h | 1 + src/wallet/wallet.cpp | 16 +++++----------- src/wallet/wallet.h | 5 +---- 4 files changed, 8 insertions(+), 17 deletions(-) (limited to 'src/wallet') diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index ba0843f352..b9e714946d 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -10,7 +10,6 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); - m_wallet.handleNotifications(); - + m_chain_notifications_handler = m_chain->handleNotifications({ &m_wallet, [](CWallet*) {} }); m_chain_client->registerRpcs(); } diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index 4e4129fb2c..81d8a60b8a 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -23,6 +23,7 @@ struct WalletTestingSetup: public TestingSetup { std::unique_ptr m_chain = interfaces::MakeChain(m_node); std::unique_ptr m_chain_client = interfaces::MakeWalletClient(*m_chain, {}); CWallet m_wallet; + std::unique_ptr m_chain_notifications_handler; }; #endif // BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9a972febab..98f308f927 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -62,8 +62,10 @@ bool AddWallet(const std::shared_ptr& wallet) bool RemoveWallet(const std::shared_ptr& wallet) { - LOCK(cs_wallets); assert(wallet); + // Unregister with the validation interface which also drops shared ponters. + wallet->m_chain_notifications_handler.reset(); + LOCK(cs_wallets); std::vector>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i == vpwallets.end()) return false; vpwallets.erase(i); @@ -105,13 +107,9 @@ static std::set g_unloading_wallet_set; // Custom deleter for shared_ptr. static void ReleaseWallet(CWallet* wallet) { - // Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain - // so that it's in sync with the current chainstate. const std::string name = wallet->GetName(); wallet->WalletLogPrintf("Releasing wallet\n"); - wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); - wallet->m_chain_notifications_handler.reset(); delete wallet; // Wallet is now released, notify UnloadWallet, if any. { @@ -137,6 +135,7 @@ void UnloadWallet(std::shared_ptr&& wallet) // Notify the unload intent so that all remaining shared pointers are // released. wallet->NotifyUnload(); + // Time to ditch our shared_ptr and wait for ReleaseWallet call. wallet.reset(); { @@ -4092,7 +4091,7 @@ std::shared_ptr CWallet::CreateWalletFromFile(interfaces::Chain& chain, } // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. - walletInstance->handleNotifications(); + walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); @@ -4105,11 +4104,6 @@ std::shared_ptr CWallet::CreateWalletFromFile(interfaces::Chain& chain, return walletInstance; } -void CWallet::handleNotifications() -{ - m_chain_notifications_handler = m_chain->handleNotifications(*this); -} - void CWallet::postInitProcess() { auto locked_chain = chain().lock(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 75fd14a80e..e3903bfcf4 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -605,7 +605,7 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions /** * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions. */ -class CWallet final : public WalletStorage, private interfaces::Chain::Notifications +class CWallet final : public WalletStorage, public interfaces::Chain::Notifications { private: CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet); @@ -781,9 +781,6 @@ public: /** Registered interfaces::Chain::Notifications handler. */ std::unique_ptr m_chain_notifications_handler; - /** Register the wallet for chain notifications */ - void handleNotifications(); - /** Interface for accessing chain state. */ interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } -- cgit v1.2.3