Skip to content

Commit

Permalink
wallet: Minimal fix to restore conflicted transaction notifications
Browse files Browse the repository at this point in the history
This fix is a based on the fix by Antoine Riard <ariard@student.42.fr> 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@student.42.fr>
  • Loading branch information
2 people authored and ComputerCraftr committed Jun 5, 2020
1 parent be22583 commit 6178216
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 19 deletions.
8 changes: 8 additions & 0 deletions doc/release-notes-18982.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Notification changes
--------------------

`-walletnotify` notifications are now sent for wallet transactions that are
removed from the mempool because they conflict with a new block. These
notifications were sent previously before the v0.19 release, but had been
broken since that release (bug
[#18325](https://github.com/bitcoin/bitcoin/issues/18325)).
4 changes: 2 additions & 2 deletions src/interfaces/chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ class NotificationsProxy : public CValidationInterface
{
m_notifications->transactionAddedToMempool(tx);
}
void TransactionRemovedFromMempool(const CTransactionRef& tx) override
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override
{
m_notifications->transactionRemovedFromMempool(tx);
m_notifications->transactionRemovedFromMempool(tx, reason);
}
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CRPCCommand;
class CScheduler;
class Coin;
class uint256;
enum class MemPoolRemovalReason;
enum class RBFTransactionState;
struct CBlockLocator;
struct FeeCalculation;
Expand Down Expand Up @@ -221,7 +222,7 @@ class Chain
public:
virtual ~Notifications() {}
virtual void transactionAddedToMempool(const CTransactionRef& tx) {}
virtual void transactionRemovedFromMempool(const CTransactionRef& ptx) {}
virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}
virtual void blockConnected(const CBlock& block, int height) {}
virtual void blockDisconnected(const CBlock& block, int height) {}
virtual void updatedBlockTip() {}
Expand Down
2 changes: 1 addition & 1 deletion src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
// for any reason except being included in a block. Clients interested
// in transactions included in blocks can subscribe to the BlockConnected
// notification.
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx());
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason);
}

const uint256 hash = it->GetTx().GetHash();
Expand Down
6 changes: 3 additions & 3 deletions src/validationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) {
ptx->GetWitnessHash().ToString());
}

void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
auto event = [ptx, this] {
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(ptx); });
void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {
auto event = [ptx, reason, this] {
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(ptx, reason); });
};
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
ptx->GetHash().ToString(),
Expand Down
5 changes: 3 additions & 2 deletions src/validationinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class CConnman;
class CValidationInterface;
class uint256;
class CScheduler;
enum class MemPoolRemovalReason;

// These functions dispatch to one or all registered wallets

Expand Down Expand Up @@ -129,7 +130,7 @@ class CValidationInterface {
*
* Called on a background thread.
*/
virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) {}
virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {}
/**
* Notifies listeners of a block being connected.
* Provides a vector of transactions evicted from the mempool as a result.
Expand Down Expand Up @@ -197,7 +198,7 @@ class CMainSignals {

void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
void TransactionAddedToMempool(const CTransactionRef &);
void TransactionRemovedFromMempool(const CTransactionRef &);
void TransactionRemovedFromMempool(const CTransactionRef &, MemPoolRemovalReason);
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex);
void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex);
void ChainStateFlushed(const CBlockLocator &);
Expand Down
35 changes: 33 additions & 2 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
#include <txmempool.h>
#include <util/bip32.h>
#include <util/error.h>
#include <util/fees.h>
Expand Down Expand Up @@ -1105,12 +1106,42 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& ptx) {
}
}

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

void CWallet::blockConnected(const CBlock& block, int height)
Expand All @@ -1124,7 +1155,7 @@ void CWallet::blockConnected(const CBlock& block, int height)
for (size_t index = 0; index < block.vtx.size(); index++) {
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index);
SyncTransaction(block.vtx[index], confirm);
transactionRemovedFromMempool(block.vtx[index]);
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
uint256 last_failed_block;
};
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void transactionRemovedFromMempool(const CTransactionRef &ptx) override;
void transactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
struct Balance {
Expand Down
9 changes: 2 additions & 7 deletions test/functional/feature_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,15 @@ def run_test(self):

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

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

0 comments on commit 6178216

Please sign in to comment.