Skip to content

Commit 3657aee

Browse files
author
MarcoFalke
committed
Merge #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/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/bitcoin#16624, causing issue bitcoin/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 #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
2 parents 5879bfa + 7eaf86d commit 3657aee

File tree

9 files changed

+71
-39
lines changed

9 files changed

+71
-39
lines changed

doc/release-notes-18982.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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 v0.19 release, but had been
7+
broken since that release (bug
8+
[#18325](https://github.com/bitcoin/bitcoin/issues/18325)).

src/interfaces/chain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ class NotificationsProxy : public CValidationInterface
6363
{
6464
m_notifications->transactionAddedToMempool(tx);
6565
}
66-
void TransactionRemovedFromMempool(const CTransactionRef& tx) override
66+
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override
6767
{
68-
m_notifications->transactionRemovedFromMempool(tx);
68+
m_notifications->transactionRemovedFromMempool(tx, reason);
6969
}
7070
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
7171
{

src/interfaces/chain.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class CRPCCommand;
2020
class CScheduler;
2121
class Coin;
2222
class uint256;
23+
enum class MemPoolRemovalReason;
2324
enum class RBFTransactionState;
2425
struct bilingual_str;
2526
struct CBlockLocator;
@@ -239,7 +240,7 @@ class Chain
239240
public:
240241
virtual ~Notifications() {}
241242
virtual void transactionAddedToMempool(const CTransactionRef& tx) {}
242-
virtual void transactionRemovedFromMempool(const CTransactionRef& ptx) {}
243+
virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}
243244
virtual void blockConnected(const CBlock& block, int height) {}
244245
virtual void blockDisconnected(const CBlock& block, int height) {}
245246
virtual void updatedBlockTip() {}

src/txmempool.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
410410
// for any reason except being included in a block. Clients interested
411411
// in transactions included in blocks can subscribe to the BlockConnected
412412
// notification.
413-
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx());
413+
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason);
414414
}
415415

416416
const uint256 hash = it->GetTx().GetHash();

src/validationinterface.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,22 +199,22 @@ void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInd
199199
fInitialDownload);
200200
}
201201

202-
void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) {
203-
auto event = [ptx, this] {
204-
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(ptx); });
202+
void CMainSignals::TransactionAddedToMempool(const CTransactionRef& tx) {
203+
auto event = [tx, this] {
204+
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(tx); });
205205
};
206206
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
207-
ptx->GetHash().ToString(),
208-
ptx->GetWitnessHash().ToString());
207+
tx->GetHash().ToString(),
208+
tx->GetWitnessHash().ToString());
209209
}
210210

211-
void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
212-
auto event = [ptx, this] {
213-
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(ptx); });
211+
void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
212+
auto event = [tx, reason, this] {
213+
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason); });
214214
};
215215
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
216-
ptx->GetHash().ToString(),
217-
ptx->GetWitnessHash().ToString());
216+
tx->GetHash().ToString(),
217+
tx->GetWitnessHash().ToString());
218218
}
219219

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

src/validationinterface.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class CConnman;
2121
class CValidationInterface;
2222
class uint256;
2323
class CScheduler;
24+
enum class MemPoolRemovalReason;
2425

2526
/** Register subscriber */
2627
void RegisterValidationInterface(CValidationInterface* callbacks);
@@ -96,7 +97,7 @@ class CValidationInterface {
9697
*
9798
* Called on a background thread.
9899
*/
99-
virtual void TransactionAddedToMempool(const CTransactionRef &ptxn) {}
100+
virtual void TransactionAddedToMempool(const CTransactionRef& tx) {}
100101
/**
101102
* Notifies listeners of a transaction leaving mempool.
102103
*
@@ -129,7 +130,7 @@ class CValidationInterface {
129130
*
130131
* Called on a background thread.
131132
*/
132-
virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) {}
133+
virtual void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}
133134
/**
134135
* Notifies listeners of a block being connected.
135136
* Provides a vector of transactions evicted from the mempool as a result.
@@ -196,8 +197,8 @@ class CMainSignals {
196197

197198

198199
void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
199-
void TransactionAddedToMempool(const CTransactionRef &);
200-
void TransactionRemovedFromMempool(const CTransactionRef &);
200+
void TransactionAddedToMempool(const CTransactionRef&);
201+
void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason);
201202
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex);
202203
void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex);
203204
void ChainStateFlushed(const CBlockLocator &);

src/wallet/wallet.cpp

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <script/descriptor.h>
2222
#include <script/script.h>
2323
#include <script/signingprovider.h>
24+
#include <txmempool.h>
2425
#include <util/bip32.h>
2526
#include <util/check.h>
2627
#include <util/error.h>
@@ -1100,23 +1101,52 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio
11001101
MarkInputsDirty(ptx);
11011102
}
11021103

1103-
void CWallet::transactionAddedToMempool(const CTransactionRef& ptx) {
1104+
void CWallet::transactionAddedToMempool(const CTransactionRef& tx) {
11041105
LOCK(cs_wallet);
1105-
CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
1106-
SyncTransaction(ptx, confirm);
1106+
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
11071107

1108-
auto it = mapWallet.find(ptx->GetHash());
1108+
auto it = mapWallet.find(tx->GetHash());
11091109
if (it != mapWallet.end()) {
11101110
it->second.fInMempool = true;
11111111
}
11121112
}
11131113

1114-
void CWallet::transactionRemovedFromMempool(const CTransactionRef &ptx) {
1114+
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
11151115
LOCK(cs_wallet);
1116-
auto it = mapWallet.find(ptx->GetHash());
1116+
auto it = mapWallet.find(tx->GetHash());
11171117
if (it != mapWallet.end()) {
11181118
it->second.fInMempool = false;
11191119
}
1120+
// Handle transactions that were removed from the mempool because they
1121+
// conflict with transactions in a newly connected block.
1122+
if (reason == MemPoolRemovalReason::CONFLICT) {
1123+
// Call SyncNotifications, so external -walletnotify notifications will
1124+
// be triggered for these transactions. Set Status::UNCONFIRMED instead
1125+
// of Status::CONFLICTED for a few reasons:
1126+
//
1127+
// 1. The transactionRemovedFromMempool callback does not currently
1128+
// provide the conflicting block's hash and height, and for backwards
1129+
// compatibility reasons it may not be not safe to store conflicted
1130+
// wallet transactions with a null block hash. See
1131+
// https://github.com/bitcoin/bitcoin/pull/18600#discussion_r420195993.
1132+
// 2. For most of these transactions, the wallet's internal conflict
1133+
// detection in the blockConnected handler will subsequently call
1134+
// MarkConflicted and update them with CONFLICTED status anyway. This
1135+
// applies to any wallet transaction that has inputs spent in the
1136+
// block, or that has ancestors in the wallet with inputs spent by
1137+
// the block.
1138+
// 3. Longstanding behavior since the sync implementation in
1139+
// https://github.com/bitcoin/bitcoin/pull/9371 and the prior sync
1140+
// implementation before that was to mark these transactions
1141+
// unconfirmed rather than conflicted.
1142+
//
1143+
// Nothing described above should be seen as an unchangeable requirement
1144+
// when improving this code in the future. The wallet's heuristics for
1145+
// distinguishing between conflicted and unconfirmed transactions are
1146+
// imperfect, and could be improved in general, see
1147+
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
1148+
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
1149+
}
11201150
}
11211151

11221152
void CWallet::blockConnected(const CBlock& block, int height)
@@ -1127,9 +1157,8 @@ void CWallet::blockConnected(const CBlock& block, int height)
11271157
m_last_block_processed_height = height;
11281158
m_last_block_processed = block_hash;
11291159
for (size_t index = 0; index < block.vtx.size(); index++) {
1130-
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index);
1131-
SyncTransaction(block.vtx[index], confirm);
1132-
transactionRemovedFromMempool(block.vtx[index]);
1160+
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index});
1161+
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK);
11331162
}
11341163
}
11351164

@@ -1144,8 +1173,7 @@ void CWallet::blockDisconnected(const CBlock& block, int height)
11441173
m_last_block_processed_height = height - 1;
11451174
m_last_block_processed = block.hashPrevBlock;
11461175
for (const CTransactionRef& ptx : block.vtx) {
1147-
CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
1148-
SyncTransaction(ptx, confirm);
1176+
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
11491177
}
11501178
}
11511179

@@ -1685,8 +1713,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
16851713
break;
16861714
}
16871715
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
1688-
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block_height, block_hash, posInBlock);
1689-
SyncTransaction(block.vtx[posInBlock], confirm, fUpdate);
1716+
SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate);
16901717
}
16911718
// scan succeeded, record block as most recent successfully scanned
16921719
result.last_scanned_block = block_hash;

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
921921
uint256 last_failed_block;
922922
};
923923
ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate);
924-
void transactionRemovedFromMempool(const CTransactionRef &ptx) override;
924+
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override;
925925
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
926926
void ResendWalletTransactions();
927927
struct Balance {

test/functional/feature_notifications.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,15 @@ def run_test(self):
125125

126126
# Bump tx2 as bump2 and generate a block on node 0 while
127127
# disconnected, then reconnect and check for notifications on node 1
128-
# about newly confirmed bump2 and newly conflicted tx2. Currently
129-
# only the bump2 notification is sent. Ideally, notifications would
130-
# be sent both for bump2 and tx2, which was the previous behavior
131-
# before being broken by an accidental change in PR
132-
# https://github.com/bitcoin/bitcoin/pull/16624. The bug is reported
133-
# in issue https://github.com/bitcoin/bitcoin/issues/18325.
128+
# about newly confirmed bump2 and newly conflicted tx2.
134129
disconnect_nodes(self.nodes[0], 1)
135130
bump2 = self.nodes[0].bumpfee(tx2)["txid"]
136131
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
137132
assert_equal(self.nodes[0].gettransaction(bump2)["confirmations"], 1)
138133
assert_equal(tx2 in self.nodes[1].getrawmempool(), True)
139134
connect_nodes(self.nodes[0], 1)
140135
self.sync_blocks()
141-
self.expect_wallet_notify([bump2])
136+
self.expect_wallet_notify([bump2, tx2])
142137
assert_equal(self.nodes[1].gettransaction(bump2)["confirmations"], 1)
143138

144139
# TODO: add test for `-alertnotify` large fork notifications

0 commit comments

Comments
 (0)