diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index b7fec4c98..f426ebf17 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -1,82 +1,83 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include static void WalletBalance(benchmark::State &state, const bool set_dirty, const bool add_watchonly, const bool add_mine) { const auto &ADDRESS_WATCHONLY = ADDRESS_BCHREG_UNSPENDABLE; const Config &config = GetConfig(); NodeContext node; std::unique_ptr chain = interfaces::MakeChain(node); CWallet wallet{config.GetChainParams(), chain.get(), WalletLocation(), WalletDatabase::CreateMock()}; { bool first_run; if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) { assert(false); } - wallet.handleNotifications(); } + auto handler = chain->handleNotifications({&wallet, [](CWallet *) {}}); + const Optional address_mine{ add_mine ? Optional{getnewaddress(config, wallet)} : nullopt}; if (add_watchonly) { importaddress(wallet, ADDRESS_WATCHONLY); } for (int i = 0; i < 100; ++i) { generatetoaddress(config, address_mine.get_value_or(ADDRESS_WATCHONLY)); generatetoaddress(config, ADDRESS_WATCHONLY); } SyncWithValidationInterfaceQueue(); // Cache auto bal = wallet.GetBalance(); while (state.KeepRunning()) { if (set_dirty) { wallet.MarkDirty(); } bal = wallet.GetBalance(); if (add_mine) { assert(bal.m_mine_trusted > Amount::zero()); } if (add_watchonly) { assert(bal.m_watchonly_trusted > Amount::zero()); } } } static void WalletBalanceDirty(benchmark::State &state) { WalletBalance(state, /* set_dirty */ true, /* add_watchonly */ true, /* add_mine */ true); } static void WalletBalanceClean(benchmark::State &state) { WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ true, /* add_mine */ true); } static void WalletBalanceMine(benchmark::State &state) { WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ false, /* add_mine */ true); } static void WalletBalanceWatch(benchmark::State &state) { WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ true, /* add_mine */ false); } BENCHMARK(WalletBalanceDirty, 2500); BENCHMARK(WalletBalanceClean, 8000); BENCHMARK(WalletBalanceMine, 16000); BENCHMARK(WalletBalanceWatch, 8000); diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index fc0b00718..984b13729 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -1,409 +1,418 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace interfaces { namespace { class LockImpl : public Chain::Lock, public UniqueLock { Optional getHeight() override { LockAssertion lock(::cs_main); int height = ::ChainActive().Height(); if (height >= 0) { return height; } return nullopt; } Optional getBlockHeight(const BlockHash &hash) override { LockAssertion lock(::cs_main); CBlockIndex *block = LookupBlockIndex(hash); if (block && ::ChainActive().Contains(block)) { return block->nHeight; } return nullopt; } int getBlockDepth(const BlockHash &hash) override { const Optional tip_height = getHeight(); const Optional height = getBlockHeight(hash); return tip_height && height ? *tip_height - *height + 1 : 0; } BlockHash getBlockHash(int height) override { LockAssertion lock(::cs_main); CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetBlockHash(); } int64_t getBlockTime(int height) override { LockAssertion lock(::cs_main); CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetBlockTime(); } int64_t getBlockMedianTimePast(int height) override { LockAssertion lock(::cs_main); CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetMedianTimePast(); } bool haveBlockOnDisk(int height) override { LockAssertion lock(::cs_main); CBlockIndex *block = ::ChainActive()[height]; return block && (block->nStatus.hasData() != 0) && block->nTx > 0; } Optional findFirstBlockWithTimeAndHeight(int64_t time, int height, BlockHash *hash) override { LockAssertion lock(::cs_main); CBlockIndex *block = ::ChainActive().FindEarliestAtLeast(time, height); if (block) { if (hash) { *hash = block->GetBlockHash(); } return block->nHeight; } return nullopt; } Optional findPruned(int start_height, Optional stop_height) override { LockAssertion lock(::cs_main); if (::fPruneMode) { CBlockIndex *block = stop_height ? ::ChainActive()[*stop_height] : ::ChainActive().Tip(); while (block && block->nHeight >= start_height) { if (block->nStatus.hasData() == 0) { return block->nHeight; } block = block->pprev; } } return nullopt; } Optional findFork(const BlockHash &hash, Optional *height) override { LockAssertion lock(::cs_main); const CBlockIndex *block = LookupBlockIndex(hash); const CBlockIndex *fork = block ? ::ChainActive().FindFork(block) : nullptr; if (height) { if (block) { *height = block->nHeight; } else { height->reset(); } } if (fork) { return fork->nHeight; } return nullopt; } CBlockLocator getTipLocator() override { LockAssertion lock(::cs_main); return ::ChainActive().GetLocator(); } Optional findLocatorFork(const CBlockLocator &locator) override { LockAssertion lock(::cs_main); if (CBlockIndex *fork = FindForkInGlobalIndex(::ChainActive(), locator)) { return fork->nHeight; } return nullopt; } bool contextualCheckTransactionForCurrentBlock( const Consensus::Params ¶ms, const CTransaction &tx, CValidationState &state) override { LockAssertion lock(::cs_main); return ContextualCheckTransactionForCurrentBlock(params, tx, state); } using UniqueLock::UniqueLock; }; // namespace interfaces - class NotificationsHandlerImpl : public Handler, CValidationInterface { + class NotificationsProxy : public CValidationInterface { public: - explicit NotificationsHandlerImpl(Chain &chain, - Chain::Notifications ¬ifications) - : m_chain(chain), m_notifications(¬ifications) { - RegisterValidationInterface(this); - } - ~NotificationsHandlerImpl() override { disconnect(); } - void disconnect() override { - if (m_notifications) { - m_notifications = nullptr; - UnregisterValidationInterface(this); - } - } + explicit NotificationsProxy( + std::shared_ptr notifications) + : m_notifications(std::move(notifications)) {} + virtual ~NotificationsProxy() = default; void TransactionAddedToMempool(const CTransactionRef &tx) override { m_notifications->TransactionAddedToMempool(tx); } void TransactionRemovedFromMempool(const CTransactionRef &tx) override { m_notifications->TransactionRemovedFromMempool(tx); } void BlockConnected( const std::shared_ptr &block, const CBlockIndex *index, const std::vector &tx_conflicted) override { m_notifications->BlockConnected(*block, tx_conflicted); } void BlockDisconnected(const std::shared_ptr &block) override { m_notifications->BlockDisconnected(*block); } void UpdatedBlockTip(const CBlockIndex *index, const CBlockIndex *fork_index, bool is_ibd) override { m_notifications->UpdatedBlockTip(); } void ChainStateFlushed(const CBlockLocator &locator) override { m_notifications->ChainStateFlushed(locator); } - Chain &m_chain; - Chain::Notifications *m_notifications; + std::shared_ptr m_notifications; + }; + + class NotificationsHandlerImpl : public Handler { + public: + explicit NotificationsHandlerImpl( + std::shared_ptr notifications) + : m_proxy(std::make_shared( + std::move(notifications))) { + RegisterSharedValidationInterface(m_proxy); + } + ~NotificationsHandlerImpl() override { disconnect(); } + void disconnect() override { + if (m_proxy) { + UnregisterSharedValidationInterface(m_proxy); + m_proxy.reset(); + } + } + std::shared_ptr m_proxy; }; class RpcHandlerImpl : public Handler { public: explicit RpcHandlerImpl(const CRPCCommand &command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](Config &config, const JSONRPCRequest &request, UniValue &result, bool last_handler) { if (!m_wrapped_command) { return false; } try { return m_wrapped_command->actor(config, request, result, last_handler); } catch (const UniValue &e) { // If this is not the last handler and a wallet not found // exception was thrown, return false so the next handler // can try to handle the request. Otherwise, reraise the // exception. if (!last_handler) { const UniValue &code = e["code"]; if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { return false; } } throw; } }; ::tableRPC.appendCommand(m_command.name, &m_command); } void disconnect() override final { if (m_wrapped_command) { m_wrapped_command = nullptr; ::tableRPC.removeCommand(m_command.name, &m_command); } } ~RpcHandlerImpl() override { disconnect(); } CRPCCommand m_command; const CRPCCommand *m_wrapped_command; }; class ChainImpl : public Chain { public: explicit ChainImpl(NodeContext &node) : m_node(node) {} std::unique_ptr lock(bool try_lock) override { auto lock = std::make_unique( ::cs_main, "cs_main", __FILE__, __LINE__, try_lock); if (try_lock && lock && !*lock) { return {}; } // Temporary to avoid CWG 1579 std::unique_ptr result = std::move(lock); return result; } bool findBlock(const BlockHash &hash, CBlock *block, int64_t *time, int64_t *time_max) override { CBlockIndex *index; { LOCK(cs_main); index = LookupBlockIndex(hash); if (!index) { return false; } if (time) { *time = index->GetBlockTime(); } if (time_max) { *time_max = index->GetBlockTimeMax(); } } if (block && !ReadBlockFromDisk(*block, index, Params().GetConsensus())) { block->SetNull(); } return true; } void findCoins(std::map &coins) override { return FindCoins(coins); } double guessVerificationProgress(const BlockHash &block_hash) override { LOCK(cs_main); return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash)); } bool hasDescendantsInMempool(const TxId &txid) override { LOCK(::g_mempool.cs); auto it = ::g_mempool.GetIter(txid); return it && (*it)->GetCountWithDescendants() > 1; } bool broadcastTransaction(const Config &config, const CTransactionRef &tx, std::string &err_string, const Amount &max_tx_fee, bool relay) override { const TransactionError err = BroadcastTransaction( m_node, config, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); // Chain clients only care about failures to accept the tx to the // mempool. Disregard non-mempool related failures. Note: this will // need to be updated if BroadcastTransactions() is updated to // return other non-mempool failures that Chain clients do not need // to know about. return err == TransactionError::OK; } void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) override { ::g_mempool.GetTransactionAncestry(txid, ancestors, descendants); } bool checkChainLimits(const CTransactionRef &tx) override { LockPoints lp; CTxMemPoolEntry entry(tx, Amount(), 0, 0, false, 0, lp); CTxMemPool::setEntries ancestors; auto limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); auto limit_ancestor_size = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; auto limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); auto limit_descendant_size = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; std::string unused_error_string; LOCK(::g_mempool.cs); return ::g_mempool.CalculateMemPoolAncestors( entry, ancestors, limit_ancestor_count, limit_ancestor_size, limit_descendant_count, limit_descendant_size, unused_error_string); } CFeeRate estimateFee() const override { return ::g_mempool.estimateFee(); } CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; } bool getPruneMode() override { return ::fPruneMode; } bool p2pEnabled() override { return m_node.connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string &message) override { ::uiInterface.InitMessage(message); } void initWarning(const std::string &message) override { InitWarning(message); } void initError(const std::string &message) override { InitError(message); } void loadWallet(std::unique_ptr wallet) override { ::uiInterface.LoadWallet(wallet); } void showProgress(const std::string &title, int progress, bool resume_possible) override { ::uiInterface.ShowProgress(title, progress, resume_possible); } - std::unique_ptr - handleNotifications(Notifications ¬ifications) override { - return std::make_unique(*this, - notifications); + std::unique_ptr handleNotifications( + std::shared_ptr notifications) override { + return std::make_unique( + std::move(notifications)); } void waitForNotificationsIfNewBlocksConnected( const BlockHash &old_tip) override { if (!old_tip.IsNull()) { LOCK(::cs_main); if (old_tip == ::ChainActive().Tip()->GetBlockHash()) { return; } CBlockIndex *block = LookupBlockIndex(old_tip); if (block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip()) { return; } } SyncWithValidationInterfaceQueue(); } std::unique_ptr handleRpc(const CRPCCommand &command) override { return std::make_unique(command); } bool rpcEnableDeprecated(const std::string &method) override { return IsDeprecatedRPCEnabled(gArgs, method); } void rpcRunLater(const std::string &name, std::function fn, int64_t seconds) override { RPCRunLater(name, std::move(fn), seconds); } int rpcSerializationFlags() override { return RPCSerializationFlags(); } void requestMempoolTransactions(Notifications ¬ifications) override { LOCK2(::cs_main, ::g_mempool.cs); for (const CTxMemPoolEntry &entry : ::g_mempool.mapTx) { notifications.TransactionAddedToMempool(entry.GetSharedTx()); } } NodeContext &m_node; }; } // namespace std::unique_ptr MakeChain(NodeContext &node) { return std::make_unique(node); } } // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index f81bbb87c..138f8fc46 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -1,316 +1,316 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H #include #include #include #include #include #include #include #include struct BlockHash; class CBlock; struct CBlockLocator; class CChainParams; class Coin; class Config; class CRPCCommand; class CScheduler; class CValidationState; struct NodeContext; namespace Consensus { struct Params; } namespace interfaces { class Handler; class Wallet; //! Interface giving clients (wallet processes, maybe other analysis tools in //! the future) ability to access to the chain state, receive notifications, //! estimate fees, and submit transactions. //! //! TODO: Current chain methods are too low level, exposing too much of the //! internal workings of the bitcoin node, and not being very convenient to use. //! Chain methods should be cleaned up and simplified over time. Examples: //! //! * The Chain::lock() method, which lets clients delay chain tip updates //! should be removed when clients are able to respond to updates //! asynchronously //! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! //! * The initMessages() and loadWallet() methods which the wallet uses to send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node //! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096). //! //! * The handleRpc, registerRpcs, rpcEnableDeprecated methods and other RPC //! methods can go away if wallets listen for HTTP requests on their own //! ports instead of registering to handle requests on the node HTTP port. class Chain { public: virtual ~Chain() {} //! Interface for querying locked chain state, used by legacy code that //! assumes state won't change between calls. New code should avoid using //! the Lock interface and instead call higher-level Chain methods //! that return more information so the chain doesn't need to stay locked //! between calls. class Lock { public: virtual ~Lock() {} //! Get current chain height, not including genesis block (returns 0 if //! chain only contains genesis block, nullopt if chain does not contain //! any blocks). virtual Optional getHeight() = 0; //! Get block height above genesis block. Returns 0 for genesis block, //! 1 for following block, and so on. Returns nullopt for a block not //! included in the current chain. virtual Optional getBlockHeight(const BlockHash &hash) = 0; //! Get block depth. Returns 1 for chain tip, 2 for preceding block, and //! so on. Returns 0 for a block not included in the current chain. virtual int getBlockDepth(const BlockHash &hash) = 0; //! Get block hash. Height must be valid or this function will abort. virtual BlockHash getBlockHash(int height) = 0; //! Get block time. Height must be valid or this function will abort. virtual int64_t getBlockTime(int height) = 0; //! Get block median time past. Height must be valid or this function //! will abort. virtual int64_t getBlockMedianTimePast(int height) = 0; //! Check that the block is available on disk (i.e. has not been //! pruned), and contains transactions. virtual bool haveBlockOnDisk(int height) = 0; //! Return height of the first block in the chain with timestamp equal //! or greater than the given time and height equal or greater than the //! given height, or nullopt if there is no block with a high enough //! timestamp and height. Also return the block hash as an optional //! output parameter (to avoid the cost of a second lookup in case this //! information is needed.) virtual Optional findFirstBlockWithTimeAndHeight(int64_t time, int height, BlockHash *hash) = 0; //! Return height of last block in the specified range which is pruned, //! or nullopt if no block in the range is pruned. Range is inclusive. virtual Optional findPruned(int start_height = 0, Optional stop_height = nullopt) = 0; //! Return height of the highest block on the chain that is an ancestor //! of the specified block, or nullopt if no common ancestor is found. //! Also return the height of the specified block as an optional output //! parameter (to avoid the cost of a second hash lookup in case this //! information is desired). virtual Optional findFork(const BlockHash &hash, Optional *height) = 0; //! Get locator for the current chain tip. virtual CBlockLocator getTipLocator() = 0; //! Return height of the latest block common to locator and chain, which //! is guaranteed to be an ancestor of the block used to create the //! locator. virtual Optional findLocatorFork(const CBlockLocator &locator) = 0; //! Check if transaction will be final given chain height current time. virtual bool contextualCheckTransactionForCurrentBlock( const Consensus::Params ¶ms, const CTransaction &tx, CValidationState &state) = 0; }; //! Return Lock interface. Chain is locked when this is called, and //! unlocked when the returned interface is freed. virtual std::unique_ptr lock(bool try_lock = false) = 0; //! Return whether node has the block and optionally return block metadata //! or contents. //! //! If a block pointer is provided to retrieve the block contents, and the //! block exists but doesn't have data (for example due to pruning), the //! block will be empty and all fields set to null. virtual bool findBlock(const BlockHash &hash, CBlock *block = nullptr, int64_t *time = nullptr, int64_t *max_time = nullptr) = 0; //! Look up unspent output information. Returns coins in the mempool and in //! the current chain UTXO set. Iterates through all the keys in the map and //! populates the values. virtual void findCoins(std::map &coins) = 0; //! Estimate fraction of total transactions verified if blocks up to //! the specified block hash are verified. virtual double guessVerificationProgress(const BlockHash &block_hash) = 0; //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const TxId &txid) = 0; //! Transaction is added to memory pool, if the transaction fee is below the //! amount specified by max_tx_fee, and broadcast to all peers if relay is //! set to true. Return false if the transaction could not be added due to //! the fee or for another reason. virtual bool broadcastTransaction(const Config &config, const CTransactionRef &tx, std::string &err_string, const Amount &max_tx_fee, bool relay) = 0; //! Calculate mempool ancestor and descendant counts for the given //! transaction. virtual void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) = 0; //! Check if transaction will pass the mempool's chain limits. virtual bool checkChainLimits(const CTransactionRef &tx) = 0; //! Estimate fee virtual CFeeRate estimateFee() const = 0; //! Relay current minimum fee (from -minrelaytxfee settings). virtual CFeeRate relayMinFee() = 0; //! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's //! economical to spend. virtual CFeeRate relayDustFee() = 0; //! Check if pruning is enabled. virtual bool getPruneMode() = 0; //! Check if p2p enabled. virtual bool p2pEnabled() = 0; //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; //! Check if in IBD. virtual bool isInitialBlockDownload() = 0; //! Check if shutdown requested. virtual bool shutdownRequested() = 0; //! Get adjusted time. virtual int64_t getAdjustedTime() = 0; //! Send init message. virtual void initMessage(const std::string &message) = 0; //! Send init warning. virtual void initWarning(const std::string &message) = 0; //! Send init error. virtual void initError(const std::string &message) = 0; //! Send wallet load notification to the GUI. virtual void loadWallet(std::unique_ptr wallet) = 0; //! Send progress indicator. virtual void showProgress(const std::string &title, int progress, bool resume_possible) = 0; //! Chain notifications. class Notifications { public: virtual ~Notifications() {} virtual void TransactionAddedToMempool(const CTransactionRef &tx) {} virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) { } virtual void BlockConnected(const CBlock &block, const std::vector &tx_conflicted) {} virtual void BlockDisconnected(const CBlock &block) {} virtual void UpdatedBlockTip() {} virtual void ChainStateFlushed(const CBlockLocator &locator) {} }; //! Register handler for notifications. virtual std::unique_ptr - handleNotifications(Notifications ¬ifications) = 0; + handleNotifications(std::shared_ptr notifications) = 0; //! Wait for pending notifications to be processed unless block hash points //! to the current chain tip, or to a possible descendant of the current //! chain tip that isn't currently connected. virtual void waitForNotificationsIfNewBlocksConnected(const BlockHash &old_tip) = 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; //! Check if deprecated RPC is enabled. virtual bool rpcEnableDeprecated(const std::string &method) = 0; //! Run function after given number of seconds. Cancel any previous calls //! with same name. virtual void rpcRunLater(const std::string &name, std::function fn, int64_t seconds) = 0; //! Current RPC serialization flags. virtual int rpcSerializationFlags() = 0; //! Synchronously send TransactionAddedToMempool notifications about all //! current mempool transactions to the specified handler and return after //! the last one is sent. These notifications aren't coordinated with async //! notifications sent by handleNotifications, so out of date async //! notifications from handleNotifications can arrive during and after //! synchronous notifications from requestMempoolTransactions. Clients need //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications ¬ifications) = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for //! monitoring and analysis in the future). class ChainClient { public: virtual ~ChainClient() {} //! Register rpcs. virtual void registerRpcs() = 0; //! Check for errors before loading. virtual bool verify(const CChainParams &chainParams) = 0; //! Load saved state. virtual bool load(const CChainParams &chainParams) = 0; //! Start client execution and provide a scheduler. virtual void start(CScheduler &scheduler) = 0; //! Save state to disk. virtual void flush() = 0; //! Shut down client. virtual void stop() = 0; }; //! Return implementation of Chain interface. std::unique_ptr MakeChain(NodeContext &node); //! Return implementation of ChainClient interface for a wallet client. This //! function will be undefined in builds where ENABLE_WALLET is false. //! //! Currently, wallets are the only chain clients. But in the future, other //! types of chain clients could be added, such as tools for monitoring, //! analysis, or fee estimation. These clients need to expose their own //! MakeXXXClient functions returning their implementations of the ChainClient //! interface. std::unique_ptr MakeWalletClient(Chain &chain, std::vector wallet_filenames); } // namespace interfaces #endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 761287125..b610a4870 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -1,173 +1,176 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include WalletController::WalletController(interfaces::Node &node, const PlatformStyle *platform_style, OptionsModel *options_model, QObject *parent) : QObject(parent), m_node(node), m_platform_style(platform_style), m_options_model(options_model) { m_handler_load_wallet = m_node.handleLoadWallet( [this](std::unique_ptr wallet) { getOrCreateWallet(std::move(wallet)); }); for (std::unique_ptr &wallet : m_node.getWallets()) { getOrCreateWallet(std::move(wallet)); } m_activity_thread.start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. WalletController::~WalletController() { m_activity_thread.quit(); m_activity_thread.wait(); } std::vector WalletController::getOpenWallets() const { QMutexLocker locker(&m_mutex); return m_wallets; } std::map WalletController::listWalletDir() const { QMutexLocker locker(&m_mutex); std::map wallets; for (const std::string &name : m_node.listWalletDir()) { wallets[name] = false; } for (WalletModel *wallet_model : m_wallets) { auto it = wallets.find(wallet_model->wallet().getWalletName()); if (it != wallets.end()) { it->second = true; } } return wallets; } OpenWalletActivity *WalletController::openWallet(const CChainParams ¶ms, const std::string &name, QWidget *parent) { OpenWalletActivity *activity = new OpenWalletActivity(this, name, params); activity->moveToThread(&m_activity_thread); return activity; } void WalletController::closeWallet(WalletModel *wallet_model, QWidget *parent) { QMessageBox box(parent); box.setWindowTitle(tr("Close wallet")); box.setText(tr("Are you sure you wish to close wallet %1?") .arg(wallet_model->getDisplayName())); box.setInformativeText( tr("Closing the wallet for too long can result in having to resync the " "entire chain if pruning is enabled.")); box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); box.setDefaultButton(QMessageBox::Yes); if (box.exec() != QMessageBox::Yes) { return; } // First remove wallet from node. wallet_model->wallet().remove(); // Now release the model. removeAndDeleteWallet(wallet_model); } WalletModel *WalletController::getOrCreateWallet( std::unique_ptr wallet) { QMutexLocker locker(&m_mutex); // Return model instance if exists. if (!m_wallets.empty()) { std::string name = wallet->getWalletName(); for (WalletModel *wallet_model : m_wallets) { if (wallet_model->wallet().getWalletName() == name) { return wallet_model; } } } // Instantiate model and register it. WalletModel *wallet_model = new WalletModel( std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); // Handler callback runs in a different thread so fix wallet model thread // affinity. wallet_model->moveToThread(thread()); wallet_model->setParent(this); m_wallets.push_back(wallet_model); - connect(wallet_model, &WalletModel::unload, [this, wallet_model] { - // Defer removeAndDeleteWallet when no modal widget is active. - // TODO: remove this workaround by removing usage of QDiallog::exec. - if (QApplication::activeModalWidget()) { - connect( - qApp, &QApplication::focusWindowChanged, wallet_model, - [this, wallet_model]() { - if (!QApplication::activeModalWidget()) { - removeAndDeleteWallet(wallet_model); - } - }, - Qt::QueuedConnection); - } else { - removeAndDeleteWallet(wallet_model); - } - }); + connect( + wallet_model, &WalletModel::unload, this, + [this, wallet_model] { + // Defer removeAndDeleteWallet when no modal widget is active. + // TODO: remove this workaround by removing usage of QDiallog::exec. + if (QApplication::activeModalWidget()) { + connect( + qApp, &QApplication::focusWindowChanged, wallet_model, + [this, wallet_model]() { + if (!QApplication::activeModalWidget()) { + removeAndDeleteWallet(wallet_model); + } + }, + Qt::QueuedConnection); + } else { + removeAndDeleteWallet(wallet_model); + } + }, + Qt::QueuedConnection); // Re-emit coinsSent signal from wallet model. connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); // Notify walletAdded signal on the GUI thread. Q_EMIT walletAdded(wallet_model); return wallet_model; } void WalletController::removeAndDeleteWallet(WalletModel *wallet_model) { // Unregister wallet model. { QMutexLocker locker(&m_mutex); m_wallets.erase( std::remove(m_wallets.begin(), m_wallets.end(), wallet_model)); } Q_EMIT walletRemoved(wallet_model); // Currently this can trigger the unload since the model can hold the last // CWallet shared pointer. delete wallet_model; } OpenWalletActivity::OpenWalletActivity(WalletController *wallet_controller, const std::string &name, const CChainParams ¶ms) : m_wallet_controller(wallet_controller), m_name(name), m_chain_params(params) {} void OpenWalletActivity::open() { std::string error, warning; std::unique_ptr wallet = m_wallet_controller->m_node.loadWallet(m_chain_params, m_name, error, warning); if (!warning.empty()) { Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning)); } if (wallet) { Q_EMIT opened( m_wallet_controller->getOrCreateWallet(std::move(wallet))); } else { Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error)); } Q_EMIT finished(); } diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 9f9990953..d7b4cf4c1 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -1,199 +1,214 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include struct ValidationInterfaceConnections { boost::signals2::scoped_connection UpdatedBlockTip; boost::signals2::scoped_connection TransactionAddedToMempool; boost::signals2::scoped_connection BlockConnected; boost::signals2::scoped_connection BlockDisconnected; boost::signals2::scoped_connection TransactionRemovedFromMempool; boost::signals2::scoped_connection ChainStateFlushed; boost::signals2::scoped_connection BlockChecked; boost::signals2::scoped_connection NewPoWValidBlock; }; struct MainSignalsInstance { boost::signals2::signal UpdatedBlockTip; boost::signals2::signal TransactionAddedToMempool; boost::signals2::signal &, const CBlockIndex *pindex, const std::vector &)> BlockConnected; boost::signals2::signal &)> BlockDisconnected; boost::signals2::signal TransactionRemovedFromMempool; boost::signals2::signal ChainStateFlushed; boost::signals2::signal BlockChecked; boost::signals2::signal &)> NewPoWValidBlock; // We are not allowed to assume the scheduler only runs in one thread, // but must ensure all callbacks happen in-order, so we end up creating // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; std::unordered_map m_connMainSignals; explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} }; static CMainSignals g_signals; void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler &scheduler) { assert(!m_internals); m_internals.reset(new MainSignalsInstance(&scheduler)); } void CMainSignals::UnregisterBackgroundSignalScheduler() { m_internals.reset(nullptr); } void CMainSignals::FlushBackgroundCallbacks() { if (m_internals) { m_internals->m_schedulerClient.EmptyQueue(); } } size_t CMainSignals::CallbacksPending() { if (!m_internals) { return 0; } return m_internals->m_schedulerClient.CallbacksPending(); } CMainSignals &GetMainSignals() { return g_signals; } -void RegisterValidationInterface(CValidationInterface *pwalletIn) { +void RegisterSharedValidationInterface( + std::shared_ptr pwalletIn) { + // Each connection captures pwalletIn to ensure that each callback is + // executed before pwalletIn is destroyed. For more details see #18338. ValidationInterfaceConnections &conns = - g_signals.m_internals->m_connMainSignals[pwalletIn]; + g_signals.m_internals->m_connMainSignals[pwalletIn.get()]; conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect( std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect( std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); conns.BlockConnected = g_signals.m_internals->BlockConnected.connect( std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect( std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect( std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect( std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); conns.BlockChecked = g_signals.m_internals->BlockChecked.connect( std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect( std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2)); } +void RegisterValidationInterface(CValidationInterface *callbacks) { + // Create a shared_ptr with a no-op deleter - CValidationInterface lifecycle + // is managed by the caller. + RegisterSharedValidationInterface( + {callbacks, [](CValidationInterface *) {}}); +} + +void UnregisterSharedValidationInterface( + std::shared_ptr callbacks) { + UnregisterValidationInterface(callbacks.get()); +} + void UnregisterValidationInterface(CValidationInterface *pwalletIn) { if (g_signals.m_internals) { g_signals.m_internals->m_connMainSignals.erase(pwalletIn); } } void UnregisterAllValidationInterfaces() { if (!g_signals.m_internals) { return; } g_signals.m_internals->m_connMainSignals.clear(); } void CallFunctionInValidationInterfaceQueue(std::function func) { g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func)); } void SyncWithValidationInterfaceQueue() { AssertLockNotHeld(cs_main); // Block until the validation queue drains std::promise promise; CallFunctionInValidationInterfaceQueue([&promise] { promise.set_value(); }); promise.get_future().wait(); } void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { // Dependencies exist that require UpdatedBlockTip events to be delivered in // the order in which the chain actually updates. One way to ensure this is // for the caller to invoke this signal in the same critical section where // the chain is updated m_internals->m_schedulerClient.AddToProcessQueue([pindexNew, pindexFork, fInitialDownload, this] { m_internals->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); }); } void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) { m_internals->m_schedulerClient.AddToProcessQueue( [ptx, this] { m_internals->TransactionAddedToMempool(ptx); }); } void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) { m_internals->m_schedulerClient.AddToProcessQueue( [ptx, this] { m_internals->TransactionRemovedFromMempool(ptx); }); } void CMainSignals::BlockConnected( const std::shared_ptr &pblock, const CBlockIndex *pindex, const std::shared_ptr> &pvtxConflicted) { m_internals->m_schedulerClient.AddToProcessQueue( [pblock, pindex, pvtxConflicted, this] { m_internals->BlockConnected(pblock, pindex, *pvtxConflicted); }); } void CMainSignals::BlockDisconnected( const std::shared_ptr &pblock) { m_internals->m_schedulerClient.AddToProcessQueue( [pblock, this] { m_internals->BlockDisconnected(pblock); }); } void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) { m_internals->m_schedulerClient.AddToProcessQueue( [locator, this] { m_internals->ChainStateFlushed(locator); }); } void CMainSignals::BlockChecked(const CBlock &block, const CValidationState &state) { m_internals->BlockChecked(block, state); } void CMainSignals::NewPoWValidBlock( const CBlockIndex *pindex, const std::shared_ptr &block) { m_internals->NewPoWValidBlock(pindex, block); } diff --git a/src/validationinterface.h b/src/validationinterface.h index 73b58eaa4..f667edf79 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -1,205 +1,217 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_VALIDATIONINTERFACE_H #define BITCOIN_VALIDATIONINTERFACE_H #include // CTransaction(Ref) #include #include #include extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; struct CBlockLocator; class CBlockIndex; class CConnman; class CReserveScript; class CValidationInterface; class CValidationState; class uint256; class CScheduler; // These functions dispatch to one or all registered wallets /** Register a wallet to receive updates from core */ void RegisterValidationInterface(CValidationInterface *pwalletIn); /** Unregister a wallet from core */ void UnregisterValidationInterface(CValidationInterface *pwalletIn); /** Unregister all wallets from core */ void UnregisterAllValidationInterfaces(); + +// Alternate registration functions that release a shared_ptr after the last +// notification is sent. These are useful for race-free cleanup, since +// unregistration is nonblocking and can return before the last notification is +// processed. +void RegisterSharedValidationInterface( + std::shared_ptr callbacks); +void UnregisterSharedValidationInterface( + std::shared_ptr callbacks); + /** * Pushes a function to callback onto the notification queue, guaranteeing any * callbacks generated prior to now are finished when the function is called. * * Be very careful blocking on func to be called if any locks are held - * validation interface clients may not be able to make progress as they often * wait for things like cs_main, so blocking until func is called with cs_main * will result in a deadlock (that DEBUG_LOCKORDER will miss). */ void CallFunctionInValidationInterfaceQueue(std::function func); /** * This is a synonym for the following, which asserts certain locks are not * held: * std::promise promise; * CallFunctionInValidationInterfaceQueue([&promise] { * promise.set_value(); * }); * promise.get_future().wait(); */ void SyncWithValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main); /** * Implement this to subscribe to events generated in validation * * Each CValidationInterface() subscriber will receive event callbacks * in the order in which the events were generated by validation. * Furthermore, each ValidationInterface() subscriber may assume that * callbacks effectively run in a single thread with single-threaded * memory consistency. That is, for a given ValidationInterface() * instantiation, each callback will complete before the next one is * invoked. This means, for example when a block is connected that the * UpdatedBlockTip() callback may depend on an operation performed in * the BlockConnected() callback without worrying about explicit * synchronization. No ordering should be assumed across * ValidationInterface() subscribers. */ class CValidationInterface { protected: /** * Protected destructor so that instances can only be deleted by derived * classes. If that restriction is no longer desired, this should be made * public and virtual. */ ~CValidationInterface() = default; /** * Notifies listeners when the block chain tip advances. * * When multiple blocks are connected at once, UpdatedBlockTip will be * called on the final tip but may not be called on every intermediate tip. * If the latter behavior is desired, subscribe to BlockConnected() instead. * * Called on a background thread. */ virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {} /** * Notifies listeners of a transaction having been added to mempool. * * Called on a background thread. */ virtual void TransactionAddedToMempool(const CTransactionRef &ptxn) {} /** * Notifies listeners of a transaction leaving mempool. * * This only fires for transactions which leave mempool because of expiry, * size limiting, reorg (changes in lock times/coinbase maturity), or * replacement. This does not include any transactions which are included * in BlockConnectedDisconnected either in block->vtx or in txnConflicted. * * Called on a background thread. */ virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) {} /** * Notifies listeners of a block being connected. * Provides a vector of transactions evicted from the mempool as a result. * * Called on a background thread. */ virtual void BlockConnected(const std::shared_ptr &block, const CBlockIndex *pindex, const std::vector &txnConflicted) {} /** * Notifies listeners of a block being disconnected * * Called on a background thread. */ virtual void BlockDisconnected(const std::shared_ptr &block) { } /** * Notifies listeners of the new active block chain on-disk. * * Prior to this callback, any updates are not guaranteed to persist on disk * (ie clients need to handle shutdown/restart safety by being able to * understand when some updates were lost due to unclean shutdown). * * When this callback is invoked, the validation changes done by any prior * callback are guaranteed to exist on disk and survive a restart, including * an unclean shutdown. * * Provides a locator describing the best chain, which is likely useful for * storing current state on disk in client DBs. * * Called on a background thread. */ virtual void ChainStateFlushed(const CBlockLocator &locator) {} /** * Notifies listeners of a block validation result. * If the provided CValidationState IsValid, the provided block * is guaranteed to be the current best block at the time the * callback was generated (not necessarily now) */ virtual void BlockChecked(const CBlock &, const CValidationState &) {} /** * Notifies listeners that a block which builds directly on our current tip * has been received and connected to the headers tree, though not validated * yet. */ virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr &block){}; - friend void ::RegisterValidationInterface(CValidationInterface *); + friend void ::RegisterSharedValidationInterface( + std::shared_ptr); friend void ::UnregisterValidationInterface(CValidationInterface *); friend void ::UnregisterAllValidationInterfaces(); }; struct MainSignalsInstance; class CMainSignals { private: std::unique_ptr m_internals; - friend void ::RegisterValidationInterface(CValidationInterface *); + friend void ::RegisterSharedValidationInterface( + std::shared_ptr); friend void ::UnregisterValidationInterface(CValidationInterface *); friend void ::UnregisterAllValidationInterfaces(); friend void ::CallFunctionInValidationInterfaceQueue( std::function func); public: /** * Register a CScheduler to give callbacks which should run in the * background (may only be called once) */ void RegisterBackgroundSignalScheduler(CScheduler &scheduler); /** * Unregister a CScheduler to give callbacks which should run in the * background - these callbacks will now be dropped! */ void UnregisterBackgroundSignalScheduler(); /** Call any remaining callbacks on the calling thread */ void FlushBackgroundCallbacks(); size_t CallbacksPending(); void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void TransactionAddedToMempool(const CTransactionRef &); void TransactionRemovedFromMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr &, const CBlockIndex *pindex, const std::shared_ptr> &); void BlockDisconnected(const std::shared_ptr &); void ChainStateFlushed(const CBlockLocator &); void BlockChecked(const CBlock &, const CValidationState &); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr &); }; CMainSignals &GetMainSignals(); #endif // BITCOIN_VALIDATIONINTERFACE_H diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 87f16ca4a..94fbdf7f6 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -1,21 +1,21 @@ // Copyright (c) 2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include WalletTestingSetup::WalletTestingSetup(const std::string &chainName) : TestingSetup(chainName), m_wallet(Params(), m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()) { 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 94273ce68..da606c516 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -1,31 +1,32 @@ // Copyright (c) 2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #include #include #include #include #include #include /** * Testing setup and teardown for wallet. */ struct WalletTestingSetup : public TestingSetup { explicit WalletTestingSetup( const std::string &chainName = CBaseChainParams::MAIN); NodeContext m_node; 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 fa51509b8..66c54507e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,5469 +1,5464 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include