Skip to content

Commit 17220d6

Browse files
committed
Use callbacks to cache whether wallet transactions are in mempool
This avoid calling out to mempool state during coin selection, balance calculation, etc. In the next commit we ensure all wallet callbacks from CValidationInterface happen in the same queue, serialized with each other. This helps to avoid re-introducing one of the issues described in #9584 [1] by further disconnecting wallet from current chain/mempool state. Thanks to @morcos for the suggestion to do this. Note that there are several race conditions introduced here: * If a user calls sendrawtransaction from RPC, adding a transaction which is "trusted" (ie from them) and pays them change, it may not be immediately used by coin selection until the notification callbacks finish running. No such race is introduced in normal transaction-sending RPCs as this case is explicitly handled. * Until Block{Connected,Disconnected} and TransactionAddedToMempool calls also run in the CSceduler background thread, there is a race where TransactionAddedToMempool might be called after a Block{Connected,Disconnected} call happens. * Wallet will write a new best chain from the SetBestChain callback prior to having processed the transaction from that block. [1] "you could go to select coins, need to use 0-conf change, but such 0-conf change may have been included in a block who's callbacks have not yet been processed - resulting in thinking they are not in mempool and, thus, not selectable."
1 parent 5d67a78 commit 17220d6

File tree

2 files changed

+37
-8
lines changed

2 files changed

+37
-8
lines changed

src/wallet/wallet.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,19 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
12151215
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
12161216
LOCK2(cs_main, cs_wallet);
12171217
SyncTransaction(ptx);
1218+
1219+
auto it = mapWallet.find(ptx->GetHash());
1220+
if (it != mapWallet.end()) {
1221+
it->second.fInMempool = true;
1222+
}
1223+
}
1224+
1225+
void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
1226+
LOCK(cs_wallet);
1227+
auto it = mapWallet.find(ptx->GetHash());
1228+
if (it != mapWallet.end()) {
1229+
it->second.fInMempool = false;
1230+
}
12181231
}
12191232

12201233
void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) {
@@ -1229,9 +1242,11 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const
12291242

12301243
for (const CTransactionRef& ptx : vtxConflicted) {
12311244
SyncTransaction(ptx);
1245+
TransactionRemovedFromMempool(ptx);
12321246
}
12331247
for (size_t i = 0; i < pblock->vtx.size(); i++) {
12341248
SyncTransaction(pblock->vtx[i], pindex, i);
1249+
TransactionRemovedFromMempool(pblock->vtx[i]);
12351250
}
12361251

12371252
m_last_block_processed = pindex;
@@ -1901,8 +1916,7 @@ CAmount CWalletTx::GetChange() const
19011916

19021917
bool CWalletTx::InMempool() const
19031918
{
1904-
LOCK(mempool.cs);
1905-
return mempool.exists(GetHash());
1919+
return fInMempool;
19061920
}
19071921

19081922
bool CWalletTx::IsTrusted() const
@@ -3010,14 +3024,18 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
30103024
// Track how many getdata requests our transaction gets
30113025
mapRequestCount[wtxNew.GetHash()] = 0;
30123026

3027+
// Get the inserted-CWalletTx from mapWallet so that the
3028+
// fInMempool flag is cached properly
3029+
CWalletTx& wtx = mapWallet[wtxNew.GetHash()];
3030+
30133031
if (fBroadcastTransactions)
30143032
{
30153033
// Broadcast
3016-
if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
3034+
if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
30173035
LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
30183036
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
30193037
} else {
3020-
wtxNew.RelayWalletTransaction(connman);
3038+
wtx.RelayWalletTransaction(connman);
30213039
}
30223040
}
30233041
}
@@ -4091,8 +4109,15 @@ int CMerkleTx::GetBlocksToMaturity() const
40914109
}
40924110

40934111

4094-
bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
4112+
bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
40954113
{
4096-
return ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
4114+
// We must set fInMempool here - while it will be re-set to true by the
4115+
// entered-mempool callback, if we did not there would be a race where a
4116+
// user could call sendmoney in a loop and hit spurious out of funds errors
4117+
// because we think that the transaction they just generated's change is
4118+
// unavailable as we're not yet aware its in mempool.
4119+
bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
40974120
nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee);
4121+
fInMempool = ret;
4122+
return ret;
40984123
}

src/wallet/wallet.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,6 @@ class CMerkleTx
252252
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
253253
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
254254
int GetBlocksToMaturity() const;
255-
/** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
256-
bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
257255
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
258256
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
259257
void setAbandoned() { hashBlock = ABANDON_HASH; }
@@ -330,6 +328,7 @@ class CWalletTx : public CMerkleTx
330328
mutable bool fImmatureWatchCreditCached;
331329
mutable bool fAvailableWatchCreditCached;
332330
mutable bool fChangeCached;
331+
mutable bool fInMempool;
333332
mutable CAmount nDebitCached;
334333
mutable CAmount nCreditCached;
335334
mutable CAmount nImmatureCreditCached;
@@ -369,6 +368,7 @@ class CWalletTx : public CMerkleTx
369368
fImmatureWatchCreditCached = false;
370369
fAvailableWatchCreditCached = false;
371370
fChangeCached = false;
371+
fInMempool = false;
372372
nDebitCached = 0;
373373
nCreditCached = 0;
374374
nImmatureCreditCached = 0;
@@ -473,6 +473,9 @@ class CWalletTx : public CMerkleTx
473473
// RelayWalletTransaction may only be called if fBroadcastTransactions!
474474
bool RelayWalletTransaction(CConnman* connman);
475475

476+
/** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
477+
bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
478+
476479
std::set<uint256> GetConflicts() const;
477480
};
478481

@@ -932,6 +935,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
932935
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
933936
int64_t RescanFromTime(int64_t startTime, bool update);
934937
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
938+
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
935939
void ReacceptWalletTransactions();
936940
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
937941
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!

0 commit comments

Comments
 (0)