Skip to content

Commit 3b7140e

Browse files
MarcoFalkePastaPastaPasta
authored andcommitted
Merge bitcoin#18982: wallet: Minimal fix to restore conflicted transaction notifications
7eaf86d trivial: Suggested cleanups to surrounding code (Russell Yanofsky) b604c5c wallet: Minimal fix to restore conflicted transaction notifications (Russell Yanofsky) Pull request description: This fix is a based on the fix by Antoine Riard (ariard) in bitcoin#18600. Unlike that PR, which implements some new behavior, this just restores previous wallet notification and status behavior for transactions removed from the mempool because they conflict with transactions in a block. The behavior was accidentally changed in two `CWallet::BlockConnected` updates: a31be09 and 7e89994 from bitcoin#16624, causing issue bitcoin#18325. The change here could be improved and replaced with a more comprehensive cleanup, so it includes a detailed comment explaining future considerations. Fixes bitcoin#18325 Co-authored-by: Antoine Riard (ariard) ACKs for top commit: jonatack: Re-ACK 7eaf86d ariard: ACK 7eaf86d, reviewed, built and ran tests. MarcoFalke: ACK 7eaf86d 🍡 Tree-SHA512: 9a1efe975969bb522a9dd73c41064a9348887cb67883cd92c6571fd2df4321b9f4568363891abdaae14a3b9b168ef8142e95c373fc04677e46289b251fb84689
1 parent 118794d commit 3b7140e

File tree

6 files changed

+63
-26
lines changed

6 files changed

+63
-26
lines changed

doc/release-notes-18982.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Notification changes
2+
--------------------
3+
4+
`-walletnotify` notifications are now sent for wallet transactions that are
5+
removed from the mempool because they conflict with a new block. These
6+
notifications were sent previously before the v18.1 release, but had been
7+
broken since that release.

src/interfaces/chain.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class CFeeRate;
2727
class CBlockIndex;
2828
class Coin;
2929
class uint256;
30+
enum class MemPoolRemovalReason;
3031
struct bilingual_str;
3132
struct CBlockLocator;
3233
struct FeeCalculation;
@@ -258,7 +259,7 @@ class Chain
258259
public:
259260
virtual ~Notifications() {}
260261
virtual void transactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) {}
261-
virtual void transactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason) {}
262+
virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}
262263
virtual void blockConnected(const CBlock& block, int height) {}
263264
virtual void blockDisconnected(const CBlock& block, int height) {}
264265
virtual void updatedBlockTip() {}

src/validationinterface.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,20 +208,20 @@ void CMainSignals::SynchronousUpdatedBlockTip(const CBlockIndex *pindexNew, cons
208208
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.SynchronousUpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); });
209209
}
210210

211-
void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx, int64_t nAcceptTime) {
212-
auto event = [ptx, nAcceptTime, this] {
213-
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(ptx, nAcceptTime); });
211+
void CMainSignals::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) {
212+
auto event = [tx, nAcceptTime, this] {
213+
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(tx, nAcceptTime); });
214214
};
215215
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s", __func__,
216-
ptx->GetHash().ToString());
216+
tx->GetHash().ToString());
217217
}
218218

219-
void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {
220-
auto event = [ptx, reason, this] {
221-
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(ptx, reason); });
219+
void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
220+
auto event = [tx, reason, this] {
221+
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason); });
222222
};
223223
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s", __func__,
224-
ptx->GetHash().ToString());
224+
tx->GetHash().ToString());
225225
}
226226

227227
void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex) {

src/validationinterface.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class CValidationInterface {
117117
*
118118
* Called on a background thread.
119119
*/
120-
virtual void TransactionAddedToMempool(const CTransactionRef &ptxn, int64_t nAcceptTime) {}
120+
virtual void TransactionAddedToMempool(const CTransactionRef &xn, int64_t nAcceptTime) {}
121121
/**
122122
* Notifies listeners of a transaction leaving mempool.
123123
*
@@ -149,7 +149,7 @@ class CValidationInterface {
149149
*
150150
* Called on a background thread.
151151
*/
152-
virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {}
152+
virtual void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}
153153
/**
154154
* Notifies listeners of a block being connected.
155155
* Provides a vector of transactions evicted from the mempool as a result.
@@ -226,8 +226,8 @@ class CMainSignals {
226226
void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload);
227227
void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
228228
void SynchronousUpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
229-
void TransactionAddedToMempool(const CTransactionRef &, int64_t);
230-
void TransactionRemovedFromMempool(const CTransactionRef &, MemPoolRemovalReason);
229+
void TransactionAddedToMempool(const CTransactionRef&, int64_t);
230+
void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason);
231231
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex);
232232
void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex);
233233
void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr<const llmq::CInstantSendLock>& islock);

src/wallet/wallet.cpp

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,26 +1210,58 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio
12101210
fAnonymizableTallyCachedNonDenom = false;
12111211
}
12121212

1213-
void CWallet::transactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime) {
1213+
void CWallet::transactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) {
12141214
LOCK(cs_wallet);
12151215
CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
12161216
WalletBatch batch(GetDatabase());
1217-
SyncTransaction(ptx, confirm, batch);
1217+
SyncTransaction(tx, confirm, batch);
12181218

1219-
auto it = mapWallet.find(ptx->GetHash());
1219+
auto it = mapWallet.find(tx->GetHash());
12201220
if (it != mapWallet.end()) {
12211221
it->second.fInMempool = true;
12221222
}
12231223
}
12241224

1225-
void CWallet::transactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {
1225+
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
12261226
if (reason != MemPoolRemovalReason::CONFLICT) {
12271227
LOCK(cs_wallet);
1228-
auto it = mapWallet.find(ptx->GetHash());
1228+
auto it = mapWallet.find(tx->GetHash());
12291229
if (it != mapWallet.end()) {
12301230
it->second.fInMempool = false;
12311231
}
12321232
}
1233+
// Handle transactions that were removed from the mempool because they
1234+
// conflict with transactions in a newly connected block.
1235+
if (reason == MemPoolRemovalReason::CONFLICT) {
1236+
// Call SyncNotifications, so external -walletnotify notifications will
1237+
// be triggered for these transactions. Set Status::UNCONFIRMED instead
1238+
// of Status::CONFLICTED for a few reasons:
1239+
//
1240+
// 1. The transactionRemovedFromMempool callback does not currently
1241+
// provide the conflicting block's hash and height, and for backwards
1242+
// compatibility reasons it may not be not safe to store conflicted
1243+
// wallet transactions with a null block hash. See
1244+
// https://github.com/bitcoin/bitcoin/pull/18600#discussion_r420195993.
1245+
// 2. For most of these transactions, the wallet's internal conflict
1246+
// detection in the blockConnected handler will subsequently call
1247+
// MarkConflicted and update them with CONFLICTED status anyway. This
1248+
// applies to any wallet transaction that has inputs spent in the
1249+
// block, or that has ancestors in the wallet with inputs spent by
1250+
// the block.
1251+
// 3. Longstanding behavior since the sync implementation in
1252+
// https://github.com/bitcoin/bitcoin/pull/9371 and the prior sync
1253+
// implementation before that was to mark these transactions
1254+
// unconfirmed rather than conflicted.
1255+
//
1256+
// Nothing described above should be seen as an unchangeable requirement
1257+
// when improving this code in the future. The wallet's heuristics for
1258+
// distinguishing between conflicted and unconfirmed transactions are
1259+
// imperfect, and could be improved in general, see
1260+
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
1261+
LOCK(cs_wallet);
1262+
WalletBatch batch(GetDatabase());
1263+
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0}, batch);
1264+
}
12331265
}
12341266

12351267
void CWallet::blockConnected(const CBlock& block, int height)
@@ -1241,9 +1273,8 @@ void CWallet::blockConnected(const CBlock& block, int height)
12411273
m_last_block_processed = block_hash;
12421274
WalletBatch batch(GetDatabase());
12431275
for (size_t index = 0; index < block.vtx.size(); index++) {
1244-
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index);
1245-
SyncTransaction(block.vtx[index], confirm, batch);
1246-
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::MANUAL);
1276+
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index}, batch);
1277+
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK);
12471278
}
12481279

12491280
// reset cache to make sure no longer immature coins are included
@@ -1263,8 +1294,7 @@ void CWallet::blockDisconnected(const CBlock& block, int height)
12631294
m_last_block_processed = block.hashPrevBlock;
12641295
WalletBatch batch(GetDatabase());
12651296
for (const CTransactionRef& ptx : block.vtx) {
1266-
CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
1267-
SyncTransaction(ptx, confirm, batch);
1297+
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0}, batch);
12681298
}
12691299

12701300
// reset cache to make sure no longer mature coins are excluded
@@ -1957,8 +1987,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
19571987
break;
19581988
}
19591989
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
1960-
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block_height, block_hash, posInBlock);
1961-
SyncTransaction(block.vtx[posInBlock], confirm, batch, fUpdate);
1990+
SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, batch, fUpdate);
19621991
}
19631992
// scan succeeded, record block as most recent successfully scanned
19641993
result.last_scanned_block = block_hash;

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
10261026
uint256 last_failed_block;
10271027
};
10281028
ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate);
1029-
void transactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
1029+
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override;
10301030
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
10311031
void ResendWalletTransactions();
10321032
struct Balance {

0 commit comments

Comments
 (0)