Skip to content

Commit 4a1dc35

Browse files
committed
Merge #9371: Notify on removal
094e4b3 Better document usage of SyncTransaction (Alex Morcos) 4afbde6 Introduce MemPoolConflictRemovalTracker (Alex Morcos) ff25c32 mempool: add notification for added/removed entries (Wladimir J. van der Laan)
2 parents 5086452 + 094e4b3 commit 4a1dc35

File tree

5 files changed

+103
-21
lines changed

5 files changed

+103
-21
lines changed

src/txmempool.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n)
393393

394394
bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate)
395395
{
396+
NotifyEntryAdded(entry.GetSharedTx());
396397
// Add to memory pool without checking anything.
397398
// Used by main.cpp AcceptToMemoryPool(), which DOES do
398399
// all the appropriate checks.
@@ -449,8 +450,9 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
449450
return true;
450451
}
451452

452-
void CTxMemPool::removeUnchecked(txiter it)
453+
void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
453454
{
455+
NotifyEntryRemoved(it->GetSharedTx(), reason);
454456
const uint256 hash = it->GetTx().GetHash();
455457
BOOST_FOREACH(const CTxIn& txin, it->GetTx().vin)
456458
mapNextTx.erase(txin.prevout);
@@ -502,7 +504,7 @@ void CTxMemPool::CalculateDescendants(txiter entryit, setEntries &setDescendants
502504
}
503505
}
504506

505-
void CTxMemPool::removeRecursive(const CTransaction &origTx)
507+
void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReason reason)
506508
{
507509
// Remove transaction from memory pool
508510
{
@@ -529,7 +531,8 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx)
529531
BOOST_FOREACH(txiter it, txToRemove) {
530532
CalculateDescendants(it, setAllRemoves);
531533
}
532-
RemoveStaged(setAllRemoves, false);
534+
535+
RemoveStaged(setAllRemoves, false, reason);
533536
}
534537
}
535538

@@ -567,7 +570,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
567570
for (txiter it : txToRemove) {
568571
CalculateDescendants(it, setAllRemoves);
569572
}
570-
RemoveStaged(setAllRemoves, false);
573+
RemoveStaged(setAllRemoves, false, MemPoolRemovalReason::REORG);
571574
}
572575

573576
void CTxMemPool::removeConflicts(const CTransaction &tx)
@@ -581,7 +584,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
581584
if (txConflict != tx)
582585
{
583586
ClearPrioritisation(txConflict.GetHash());
584-
removeRecursive(txConflict);
587+
removeRecursive(txConflict, MemPoolRemovalReason::CONFLICT);
585588
}
586589
}
587590
}
@@ -610,7 +613,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
610613
if (it != mapTx.end()) {
611614
setEntries stage;
612615
stage.insert(it);
613-
RemoveStaged(stage, true);
616+
RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK);
614617
}
615618
removeConflicts(*tx);
616619
ClearPrioritisation(tx->GetHash());
@@ -989,11 +992,11 @@ size_t CTxMemPool::DynamicMemoryUsage() const {
989992
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
990993
}
991994

992-
void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants) {
995+
void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) {
993996
AssertLockHeld(cs);
994997
UpdateForRemoveFromMempool(stage, updateDescendants);
995998
BOOST_FOREACH(const txiter& it, stage) {
996-
removeUnchecked(it);
999+
removeUnchecked(it, reason);
9971000
}
9981001
}
9991002

@@ -1009,7 +1012,7 @@ int CTxMemPool::Expire(int64_t time) {
10091012
BOOST_FOREACH(txiter removeit, toremove) {
10101013
CalculateDescendants(removeit, stage);
10111014
}
1012-
RemoveStaged(stage, false);
1015+
RemoveStaged(stage, false, MemPoolRemovalReason::EXPIRY);
10131016
return stage.size();
10141017
}
10151018

@@ -1118,7 +1121,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
11181121
BOOST_FOREACH(txiter iter, stage)
11191122
txn.push_back(iter->GetTx());
11201123
}
1121-
RemoveStaged(stage, false);
1124+
RemoveStaged(stage, false, MemPoolRemovalReason::SIZELIMIT);
11221125
if (pvNoSpendsRemaining) {
11231126
BOOST_FOREACH(const CTransaction& tx, txn) {
11241127
BOOST_FOREACH(const CTxIn& txin, tx.vin) {

src/txmempool.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "boost/multi_index/ordered_index.hpp"
2626
#include "boost/multi_index/hashed_index.hpp"
2727

28+
#include <boost/signals2/signal.hpp>
29+
2830
class CAutoFile;
2931
class CBlockIndex;
3032

@@ -333,6 +335,19 @@ struct TxMempoolInfo
333335
int64_t nFeeDelta;
334336
};
335337

338+
/** Reason why a transaction was removed from the mempool,
339+
* this is passed to the notification signal.
340+
*/
341+
enum class MemPoolRemovalReason {
342+
UNKNOWN = 0, //! Manually removed or unknown reason
343+
EXPIRY, //! Expired from mempool
344+
SIZELIMIT, //! Removed in size limiting
345+
REORG, //! Removed for reorganization
346+
BLOCK, //! Removed for block
347+
CONFLICT, //! Removed for conflict with in-block transaction
348+
REPLACED //! Removed for replacement
349+
};
350+
336351
/**
337352
* CTxMemPool stores valid-according-to-the-current-best-chain transactions
338353
* that may be included in the next block.
@@ -521,10 +536,11 @@ class CTxMemPool
521536
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool validFeeEstimate = true);
522537
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate = true);
523538

524-
void removeRecursive(const CTransaction &tx);
539+
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
525540
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
526541
void removeConflicts(const CTransaction &tx);
527542
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight);
543+
528544
void clear();
529545
void _clear(); //lock free
530546
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
@@ -551,7 +567,7 @@ class CTxMemPool
551567
* Set updateDescendants to true when removing a tx that was in a block, so
552568
* that any in-mempool descendants have their ancestor state updated.
553569
*/
554-
void RemoveStaged(setEntries &stage, bool updateDescendants);
570+
void RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
555571

556572
/** When adding transactions from a disconnected block back to the mempool,
557573
* new mempool entries may have children in the mempool (which is generally
@@ -647,6 +663,9 @@ class CTxMemPool
647663

648664
size_t DynamicMemoryUsage() const;
649665

666+
boost::signals2::signal<void (CTransactionRef)> NotifyEntryAdded;
667+
boost::signals2::signal<void (CTransactionRef, MemPoolRemovalReason)> NotifyEntryRemoved;
668+
650669
private:
651670
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
652671
* the descendants for a single transaction that has been added to the
@@ -683,7 +702,7 @@ class CTxMemPool
683702
* transactions in a chain before we've updated all the state for the
684703
* removal.
685704
*/
686-
void removeUnchecked(txiter entry);
705+
void removeUnchecked(txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
687706
};
688707

689708
/**

src/validation.cpp

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,39 @@ namespace {
157157
set<int> setDirtyFileInfo;
158158
} // anon namespace
159159

160+
/* Use this class to start tracking transactions that are removed from the
161+
* mempool and pass all those transactions through SyncTransaction when the
162+
* object goes out of scope. This is currently only used to call SyncTransaction
163+
* on conflicts removed from the mempool during block connection. Applied in
164+
* ActivateBestChain around ActivateBestStep which in turn calls:
165+
* ConnectTip->removeForBlock->removeConflicts
166+
*/
167+
class MemPoolConflictRemovalTracker
168+
{
169+
private:
170+
std::vector<CTransactionRef> conflictedTxs;
171+
CTxMemPool &pool;
172+
173+
public:
174+
MemPoolConflictRemovalTracker(CTxMemPool &_pool) : pool(_pool) {
175+
pool.NotifyEntryRemoved.connect(boost::bind(&MemPoolConflictRemovalTracker::NotifyEntryRemoved, this, _1, _2));
176+
}
177+
178+
void NotifyEntryRemoved(CTransactionRef txRemoved, MemPoolRemovalReason reason) {
179+
if (reason == MemPoolRemovalReason::CONFLICT) {
180+
conflictedTxs.push_back(txRemoved);
181+
}
182+
}
183+
184+
~MemPoolConflictRemovalTracker() {
185+
pool.NotifyEntryRemoved.disconnect(boost::bind(&MemPoolConflictRemovalTracker::NotifyEntryRemoved, this, _1, _2));
186+
for (const auto& tx : conflictedTxs) {
187+
GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK);
188+
}
189+
conflictedTxs.clear();
190+
}
191+
};
192+
160193
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator)
161194
{
162195
// Find the first block the caller has in the main chain
@@ -956,7 +989,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
956989
if (plTxnReplaced)
957990
plTxnReplaced->push_back(it->GetSharedTx());
958991
}
959-
pool.RemoveStaged(allConflicting, false);
992+
pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
960993

961994
// This transaction should only count for fee estimation if
962995
// the node is not behind and it is not dependent on any other
@@ -2166,7 +2199,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
21662199
// ignore validation errors in resurrected transactions
21672200
CValidationState stateDummy;
21682201
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, it, false, NULL, NULL, true)) {
2169-
mempool.removeRecursive(tx);
2202+
mempool.removeRecursive(tx, MemPoolRemovalReason::REORG);
21702203
} else if (mempool.exists(tx.GetHash())) {
21712204
vHashUpdate.push_back(tx.GetHash());
21722205
}
@@ -2453,6 +2486,14 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
24532486
bool fInitialDownload;
24542487
{
24552488
LOCK(cs_main);
2489+
{ // TODO: Tempoarily ensure that mempool removals are notified before
2490+
// connected transactions. This shouldn't matter, but the abandoned
2491+
// state of transactions in our wallet is currently cleared when we
2492+
// receive another notification and there is a race condition where
2493+
// notification of a connected conflict might cause an outside process
2494+
// to abandon a transaction and then have it inadvertantly cleared by
2495+
// the notification that the conflicted transaction was evicted.
2496+
MemPoolConflictRemovalTracker mrt(mempool);
24562497
CBlockIndex *pindexOldTip = chainActive.Tip();
24572498
if (pindexMostWork == NULL) {
24582499
pindexMostWork = FindMostWorkChain();
@@ -2476,6 +2517,10 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
24762517
fInitialDownload = IsInitialBlockDownload();
24772518

24782519
// throw all transactions though the signal-interface
2520+
2521+
} // MemPoolConflictRemovalTracker destroyed and conflict evictions are notified
2522+
2523+
// Transactions in the connnected block are notified
24792524
for (const auto& pair : connectTrace.blocksConnected) {
24802525
assert(pair.second);
24812526
const CBlock& block = *(pair.second);
@@ -3597,7 +3642,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
35973642
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
35983643
// check level 1: verify block validity
35993644
if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus()))
3600-
return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__,
3645+
return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__,
36013646
pindex->nHeight, pindex->GetBlockHash().ToString(), FormatStateMessage(state));
36023647
// check level 2: verify undo validity
36033648
if (nCheckLevel >= 2 && pindex) {
@@ -3768,7 +3813,7 @@ bool LoadBlockIndex(const CChainParams& chainparams)
37683813
return true;
37693814
}
37703815

3771-
bool InitBlockIndex(const CChainParams& chainparams)
3816+
bool InitBlockIndex(const CChainParams& chainparams)
37723817
{
37733818
LOCK(cs_main);
37743819

src/validationinterface.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,16 @@ class CValidationInterface {
5050
struct CMainSignals {
5151
/** Notifies listeners of updated block chain tip */
5252
boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip;
53-
/** A posInBlock value for SyncTransaction which indicates the transaction was conflicted, disconnected, or not in a block */
53+
/** A posInBlock value for SyncTransaction calls for tranactions not
54+
* included in connected blocks such as transactions removed from mempool,
55+
* accepted to mempool or appearing in disconnected blocks.*/
5456
static const int SYNC_TRANSACTION_NOT_IN_BLOCK = -1;
55-
/** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */
57+
/** Notifies listeners of updated transaction data (transaction, and
58+
* optionally the block it is found in). Called with block data when
59+
* transaction is included in a connected block, and without block data when
60+
* transaction was accepted to mempool, removed from mempool (only when
61+
* removal was due to conflict from connected block), or appeared in a
62+
* disconnected block.*/
5663
boost::signals2::signal<void (const CTransaction &, const CBlockIndex *pindex, int posInBlock)> SyncTransaction;
5764
/** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */
5865
boost::signals2::signal<void (const uint256 &)> UpdatedTransaction;

src/wallet/wallet.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,9 +1003,17 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
10031003
}
10041004

10051005
/**
1006-
* Add a transaction to the wallet, or update it.
1007-
* pblock is optional, but should be provided if the transaction is known to be in a block.
1006+
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
1007+
* be set when the transaction was known to be included in a block. When
1008+
* posInBlock = SYNC_TRANSACTION_NOT_IN_BLOCK (-1) , then wallet state is not
1009+
* updated in AddToWallet, but notifications happen and cached balances are
1010+
* marked dirty.
10081011
* If fUpdate is true, existing transactions will be updated.
1012+
* TODO: One exception to this is that the abandoned state is cleared under the
1013+
* assumption that any further notification of a transaction that was considered
1014+
* abandoned is an indication that it is not safe to be considered abandoned.
1015+
* Abandoned state should probably be more carefuly tracked via different
1016+
* posInBlock signals or by checking mempool presence when necessary.
10091017
*/
10101018
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
10111019
{

0 commit comments

Comments
 (0)