mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-22 20:58:09 +02:00
Merge bitcoin/bitcoin#27307: wallet: track mempool conflicts with wallet transactions
5952292133wallet, rpc: show mempool conflicts in `gettransaction` result (ishaanam)54e07ee22fwallet: track mempool conflicts (ishaanam)d64922b590wallet refactor: use CWalletTx member functions to determine tx state (ishaanam)ffe5ff1fb6scripted-diff: wallet: s/TxStateConflicted/TxStateBlockConflicted (ishaanam)180973a941test: Add tests for wallet mempool conflicts (ishaanam) Pull request description: The `mempool_conflicts` variable is added to `CWalletTx`, it is a set of txids of txs in the mempool conflicting with the wallet tx or a wallet tx's parent. This PR only changes how mempool-conflicted txs are dealt with in memory. `IsSpent` now returns false for an output being spent by a mempool conflicted transaction where it previously returned true. A txid is added to `mempool_conflicts` during `transactionAddedToMempool`. A txid is removed from `mempool_conflicts` during `transactionRemovedFromMempool`. This PR also adds a `mempoolconflicts` field to the `gettransaction` wallet RPC result. Builds on #27145 Second attempt at #18600 ACKs for top commit: achow101: ACK5952292133ryanofsky: Code review ACK5952292133. Just small suggested changes since last review furszy: ACK59522921Tree-SHA512: 615779606723dbb6c2e302681d8e58ae2052ffee52d721ee0389746ddbbcf4b4c4afacf01ddf42b6405bc6f883520524186a955bf6b628fe9b3ae54cffc56a29
This commit is contained in:
@@ -752,8 +752,8 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
|
||||
const uint256& wtxid = it->second;
|
||||
const auto mit = mapWallet.find(wtxid);
|
||||
if (mit != mapWallet.end()) {
|
||||
int depth = GetTxDepthInMainChain(mit->second);
|
||||
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
|
||||
const auto& wtx = mit->second;
|
||||
if (!wtx.isAbandoned() && !wtx.isBlockConflicted() && !wtx.isMempoolConflicted())
|
||||
return true; // Spent
|
||||
}
|
||||
}
|
||||
@@ -1197,7 +1197,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
|
||||
auto it = mapWallet.find(txin.prevout.hash);
|
||||
if (it != mapWallet.end()) {
|
||||
CWalletTx& prevtx = it->second;
|
||||
if (auto* prev = prevtx.state<TxStateConflicted>()) {
|
||||
if (auto* prev = prevtx.state<TxStateBlockConflicted>()) {
|
||||
MarkConflicted(prev->conflicting_block_hash, prev->conflicting_block_height, wtx.GetHash());
|
||||
}
|
||||
}
|
||||
@@ -1309,7 +1309,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
|
||||
assert(!wtx.isConfirmed());
|
||||
assert(!wtx.InMempool());
|
||||
// If already conflicted or abandoned, no need to set abandoned
|
||||
if (!wtx.isConflicted() && !wtx.isAbandoned()) {
|
||||
if (!wtx.isBlockConflicted() && !wtx.isAbandoned()) {
|
||||
wtx.m_state = TxStateInactive{/*abandoned=*/true};
|
||||
return TxUpdate::NOTIFY_CHANGED;
|
||||
}
|
||||
@@ -1346,7 +1346,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
|
||||
if (conflictconfirms < GetTxDepthInMainChain(wtx)) {
|
||||
// Block is 'more conflicted' than current confirm; update.
|
||||
// Mark transaction as conflicted with this block.
|
||||
wtx.m_state = TxStateConflicted{hashBlock, conflicting_height};
|
||||
wtx.m_state = TxStateBlockConflicted{hashBlock, conflicting_height};
|
||||
return TxUpdate::CHANGED;
|
||||
}
|
||||
return TxUpdate::UNCHANGED;
|
||||
@@ -1360,7 +1360,10 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
|
||||
void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
|
||||
// Do not flush the wallet here for performance reasons
|
||||
WalletBatch batch(GetDatabase(), false);
|
||||
RecursiveUpdateTxState(&batch, tx_hash, try_updating_state);
|
||||
}
|
||||
|
||||
void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
|
||||
std::set<uint256> todo;
|
||||
std::set<uint256> done;
|
||||
|
||||
@@ -1377,7 +1380,7 @@ void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingSt
|
||||
TxUpdate update_state = try_updating_state(wtx);
|
||||
if (update_state != TxUpdate::UNCHANGED) {
|
||||
wtx.MarkDirty();
|
||||
batch.WriteTx(wtx);
|
||||
if (batch) batch->WriteTx(wtx);
|
||||
// Iterate over all its outputs, and update those tx states as well (if applicable)
|
||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
|
||||
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(Txid::FromUint256(now), i));
|
||||
@@ -1418,6 +1421,20 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx) {
|
||||
if (it != mapWallet.end()) {
|
||||
RefreshMempoolStatus(it->second, chain());
|
||||
}
|
||||
|
||||
const Txid& txid = tx->GetHash();
|
||||
|
||||
for (const CTxIn& tx_in : tx->vin) {
|
||||
// For each wallet transaction spending this prevout..
|
||||
for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
|
||||
const uint256& spent_id = range.first->second;
|
||||
// Skip the recently added tx
|
||||
if (spent_id == txid) continue;
|
||||
RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
|
||||
return wtx.mempool_conflicts.insert(txid).second ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
|
||||
@@ -1455,6 +1472,21 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
|
||||
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
|
||||
SyncTransaction(tx, TxStateInactive{});
|
||||
}
|
||||
|
||||
const Txid& txid = tx->GetHash();
|
||||
|
||||
for (const CTxIn& tx_in : tx->vin) {
|
||||
// Iterate over all wallet transactions spending txin.prev
|
||||
// and recursively mark them as no longer conflicting with
|
||||
// txid
|
||||
for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
|
||||
const uint256& spent_id = range.first->second;
|
||||
|
||||
RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
|
||||
return wtx.mempool_conflicts.erase(txid) ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& block)
|
||||
@@ -1506,11 +1538,11 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
|
||||
for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) {
|
||||
CWalletTx& wtx = mapWallet.find(_it->second)->second;
|
||||
|
||||
if (!wtx.isConflicted()) continue;
|
||||
if (!wtx.isBlockConflicted()) continue;
|
||||
|
||||
auto try_updating_state = [&](CWalletTx& tx) {
|
||||
if (!tx.isConflicted()) return TxUpdate::UNCHANGED;
|
||||
if (tx.state<TxStateConflicted>()->conflicting_block_height >= disconnect_height) {
|
||||
if (!tx.isBlockConflicted()) return TxUpdate::UNCHANGED;
|
||||
if (tx.state<TxStateBlockConflicted>()->conflicting_block_height >= disconnect_height) {
|
||||
tx.m_state = TxStateInactive{};
|
||||
return TxUpdate::CHANGED;
|
||||
}
|
||||
@@ -2787,7 +2819,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
|
||||
std::optional<uint256> block_hash;
|
||||
if (auto* conf = wtx.state<TxStateConfirmed>()) {
|
||||
block_hash = conf->confirmed_block_hash;
|
||||
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
|
||||
} else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
|
||||
block_hash = conf->conflicting_block_hash;
|
||||
}
|
||||
|
||||
@@ -3377,7 +3409,7 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
|
||||
if (auto* conf = wtx.state<TxStateConfirmed>()) {
|
||||
assert(conf->confirmed_block_height >= 0);
|
||||
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
|
||||
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
|
||||
} else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
|
||||
assert(conf->conflicting_block_height >= 0);
|
||||
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user