diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -265,6 +265,7 @@ #endif UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); + GetMainSignals().UnregisterWithMempoolSignals(g_mempool); g_wallet_init_interface->Close(); g_wallet_init_interface.reset(); globalVerifyHandle.reset(); @@ -1785,6 +1786,7 @@ "scheduler", serviceLoop)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + GetMainSignals().RegisterWithMempoolSignals(g_mempool); /** * Start the RPC server. It will be started in "warmup" mode and not diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -596,6 +596,9 @@ // to track size/count of descendant transactions. First version of // addUnchecked can be used to have it call CalculateMemPoolAncestors(), and // then invoke the second version. + // Note that addUnchecked is ONLY called from ATMP outside of tests + // and any other callers may break wallet's in-mempool tracking (due to + // lack of CValidationInterface::TransactionAddedToMempool callbacks). bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry, bool validFeeEstimate = true); bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry, diff --git a/src/validationinterface.h b/src/validationinterface.h --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -20,6 +20,8 @@ class CValidationState; class uint256; class CScheduler; +class CTxMemPool; +enum class MemPoolRemovalReason; // These functions dispatch to one or all registered wallets @@ -52,6 +54,15 @@ * 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. + */ + 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. @@ -98,6 +109,8 @@ friend void ::UnregisterValidationInterface(CValidationInterface *); friend void ::UnregisterAllValidationInterfaces(); + void MempoolEntryRemoved(CTransactionRef tx, MemPoolRemovalReason reason); + public: /** * Register a CScheduler to give callbacks which should run in the @@ -112,6 +125,11 @@ /** Call any remaining callbacks on the calling thread */ void FlushBackgroundCallbacks(); + /** Register with mempool to call TransactionRemovedFromMempool callbacks */ + void RegisterWithMempoolSignals(CTxMemPool &pool); + /** Unregister with mempool */ + void UnregisterWithMempoolSignals(CTxMemPool &pool); + void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void TransactionAddedToMempool(const CTransactionRef &); diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -7,6 +7,7 @@ #include "init.h" #include "scheduler.h" #include "sync.h" +#include "txmempool.h" #include "util.h" #include <atomic> @@ -26,6 +27,8 @@ BlockConnected; boost::signals2::signal<void(const std::shared_ptr<const CBlock> &)> BlockDisconnected; + boost::signals2::signal<void(const CTransactionRef &)> + TransactionRemovedFromMempool; boost::signals2::signal<void(const CBlockLocator &)> SetBestChain; boost::signals2::signal<void(const uint256 &)> Inventory; boost::signals2::signal<void(int64_t nBestBlockTime, CConnman *connman)> @@ -60,6 +63,16 @@ m_internals->m_schedulerClient.EmptyQueue(); } +void CMainSignals::RegisterWithMempoolSignals(CTxMemPool &pool) { + pool.NotifyEntryRemoved.connect( + boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); +} + +void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool &pool) { + pool.NotifyEntryRemoved.disconnect( + boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); +} + CMainSignals &GetMainSignals() { return g_signals; } @@ -73,6 +86,8 @@ &CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); g_signals.m_internals->BlockDisconnected.connect( boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); + g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind( + &CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); g_signals.m_internals->SetBestChain.connect( boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); g_signals.m_internals->Inventory.connect( @@ -100,6 +115,8 @@ &CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); g_signals.m_internals->BlockDisconnected.disconnect( boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); + g_signals.m_internals->TransactionRemovedFromMempool.disconnect(boost::bind( + &CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); g_signals.m_internals->UpdatedBlockTip.disconnect(boost::bind( &CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); g_signals.m_internals->NewPoWValidBlock.disconnect(boost::bind( @@ -114,10 +131,19 @@ g_signals.m_internals->TransactionAddedToMempool.disconnect_all_slots(); g_signals.m_internals->BlockConnected.disconnect_all_slots(); g_signals.m_internals->BlockDisconnected.disconnect_all_slots(); + g_signals.m_internals->TransactionRemovedFromMempool.disconnect_all_slots(); g_signals.m_internals->UpdatedBlockTip.disconnect_all_slots(); g_signals.m_internals->NewPoWValidBlock.disconnect_all_slots(); } +void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, + MemPoolRemovalReason reason) { + if (reason != MemPoolRemovalReason::BLOCK && + reason != MemPoolRemovalReason::CONFLICT) { + m_internals->TransactionRemovedFromMempool(ptx); + } +} + void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {