Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet: track mempool conflicts with wallet transactions #27307

Merged
merged 5 commits into from Mar 27, 2024

Conversation

ishaanam
Copy link
Contributor

@ishaanam ishaanam commented Mar 22, 2023

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

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 22, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK achow101, ryanofsky, furszy
Approach ACK glozow
Stale ACK murchandamus

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #29680 (wallet: fix unrelated parent conflict doesn't cause child tx to be marked as conflict by Eunovo)
  • #28616 (Show transactions as not fully confirmed during background validation by Sjors)
  • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
  • #27286 (wallet: Keep track of the wallet's own transaction outputs in memory by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch 2 times, most recently from ef72243 to f153a00 Compare April 13, 2023 06:59
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch from f153a00 to ce757a5 Compare May 3, 2023 00:02
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch 3 times, most recently from 1479572 to d9356d2 Compare May 15, 2023 00:06
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch from d9356d2 to 03dc162 Compare May 18, 2023 00:58
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch from 03dc162 to f151405 Compare May 28, 2023 01:23
@ishaanam ishaanam marked this pull request as ready for review May 28, 2023 02:26
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch from f151405 to d4cbfee Compare June 15, 2023 20:37
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch 3 times, most recently from 8c1773b to 0538ad7 Compare June 18, 2023 17:16
src/wallet/transaction.h Outdated Show resolved Hide resolved
@ishaanam
Copy link
Contributor Author

I think a good approach would avoid adding a TxStateMempoolConflicted top-level state, and just add a bool mempool_conflicts or std::set mempool_conflicts member to the TxStateInactive struct.

Thanks for your feedback, I have implemented this.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review c891a65.

Thanks for the updates! A lot of nice changes and test coverage here, and I think the PR looks close to ready to ACK, though I did leave a number of suggestions below. Most of these should be very simple or simplify code, so should not create too much work.

src/wallet/transaction.h Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/transaction.h Show resolved Hide resolved
@@ -755,7 +755,7 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
// An output is considered spent if a spending transaction
// is either confirmed, or in the mempool, or inactive (but not abandoned)
const auto& wtx = mit->second;
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned()))
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned() && !wtx.isMempoolConflicted()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "wallet: track mempool conflicts" (2c86778)

I was surprised to see this change, since I thought intent of the preceding commit was to preserve current behavior and consider spending transactions active even they conflicted with mempool transactions. Both approaches seem ok, but if this behavior is intended, it seems like it would be simpler to write this condition as:

if (!wtx.IsAbandoned() && !wtx.isConflicted()) {
    return true; // spent
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preceding was more of a refactor, while this commit aims to have IsSpent return false for txos spent by mempool-conflicted transactions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "wallet: track mempool conflicts" (a650caa)

The preceding was more of a refactor, while this commit aims to have IsSpent return false for txos spent by mempool-conflicted transactions.

Sounds good. I guess I'm still curious why you prefer over:

if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned() && !wtx.isMempoolConflicted())

over

if (!wtx.IsAbandoned() && !wtx.isConflicted())

since both checks should equivalent. Either approach seems fine to me, though.

src/wallet/transaction.h Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
test/functional/wallet_conflicts.py Show resolved Hide resolved
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/21886408495

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 923734e. Seems like a very nice change that improves the wallet balance calculation and ability to spend, and also returns useful information about the mempool.

Thanks for the updates, too. I think comparing current a650caa to previous a6ae5b2, the resulting code is simpler and the charges are more direct and easier to review.

I left some more suggestions below, but most are not important and none are critical so feel free to ignore them.

src/wallet/transaction.h Outdated Show resolved Hide resolved
src/wallet/receive.cpp Outdated Show resolved Hide resolved
@@ -755,7 +755,7 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
// An output is considered spent if a spending transaction
// is either confirmed, or in the mempool, or inactive (but not abandoned)
const auto& wtx = mit->second;
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned()))
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned() && !wtx.isMempoolConflicted()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "wallet: track mempool conflicts" (a650caa)

The preceding was more of a refactor, while this commit aims to have IsSpent return false for txos spent by mempool-conflicted transactions.

Sounds good. I guess I'm still curious why you prefer over:

if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned() && !wtx.isMempoolConflicted())

over

if (!wtx.IsAbandoned() && !wtx.isConflicted())

since both checks should equivalent. Either approach seems fine to me, though.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
test/functional/wallet_conflicts.py Outdated Show resolved Hide resolved
Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK b853688. Just more simplifications since that last review. This PR keeps getting smaller and simpler, and seems like a very obvious improvement now.

I left another suggestion (really 2 related suggestions), but it is not very important. This looks good in its current form.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
Comment on lines 758 to 761
// check for the inverse condition and only consider spending
// transactions in known, potentially active states.
const auto& wtx = mit->second;
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned()))
if (!wtx.isAbandoned() && !wtx.isMempoolConflicted() && !wtx.isBlockConflicted())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "wallet: track mempool conflicts" (9fa7f81)

Would suggest deleting the "An output is considered spent..." comment above now that the code is simpler and it no longer adds any new information. The part of the comment about checking for the "inverse condition" is also no longer true, because the code is now checking directly if wtx is conflicted or abandoned, instead of checking for the inverse condition (that it is confirmed, or in mempool, or inactive and not abandoned).

Comment on lines 755 to 761
// An output is considered spent by a spending transaction
// unless the spending transaction is conflicted or
// abandoned. The check below is written conservatively to
// check for the inverse condition and only consider spending
// transactions in known, potentially active states.
const auto& wtx = mit->second;
if (wtx.isConfirmed() || wtx.InMempool() || (wtx.isInactive() && !wtx.isAbandoned()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "wallet: use CWalletTx member functions to determine tx state" (92b3204)

Would suggest simplifying this code and deleting the comment so this is just:

const auto& wtx = mit->second;
if (!wtx.isAbandoned() && !wtx.isBlockConflicted())

Using the simpler condition would make this commit easier to understand, and make the change in the next commit more obvious where the line becomes:

if (!wtx.isAbandoned() && !wtx.isMempoolConflicted() && !wtx.isBlockConflicted())

Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left few small things. Will get deeper.

test/functional/wallet_conflicts.py Show resolved Hide resolved
raw_tx = alice.createrawtransaction(inputs=[unspents[1]], outputs=[{alice.getnewaddress() : 24.9999}])
tx2_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']

bob_unspents = [{"txid" : element, "vout" : 0} for element in [tx1_txid, tx2_txid]]
Copy link
Member

@furszy furszy Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In d3f7764:

Tiny note for reviewers:
vout is always 0 because "alice only contain 3 utxo of 25 btc each. So, tx1 and tx2 are changeless transactions. (could be good to mention this in the code too)

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
test/functional/wallet_conflicts.py Show resolved Hide resolved
test/functional/wallet_conflicts.py Show resolved Hide resolved
src/wallet/wallet.h Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
-BEGIN VERIFY SCRIPT-
sed -i 's/TxStateConflicted/TxStateBlockConflicted/g' src/wallet/wallet.cpp src/wallet/interfaces.cpp src/wallet/transaction.h src/wallet/transaction.cpp
sed -i 's/isConflicted/isBlockConflicted/g' src/wallet/transaction.h src/wallet/wallet.cpp
-END VERIFY SCRIPT-
Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK dbcc4a7. Changes since last review were a few test improvements and some more simplifications. This looks very good. Left suggestions below, all minor, none important. This PR seems like a clear improvement and should be merged if it gets more ACKs.

src/wallet/interfaces.cpp Show resolved Hide resolved
src/wallet/wallet.cpp Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
Behavior changes are:
- if a tx has a mempool conflict, the wallet will not attempt to
  rebroadcast it
- if a txo is spent by a mempool-conflicted tx, that txo is no
  longer considered spent
@ishaanam ishaanam force-pushed the track_mempool_wallet_conflicts branch from dbcc4a7 to 5952292 Compare March 20, 2024 21:22
@achow101
Copy link
Member

ACK 5952292

@DrahtBot DrahtBot requested a review from ryanofsky March 26, 2024 19:37
Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 5952292. Just small suggested changes since last review

Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 5952292

"""

self.test_block_conflicts()
self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 7, self.nodes[2].getnewaddress())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:
Would be good to mention why this generatetoaddress is needed. I removed it locally and the test still passes.

@@ -417,6 +421,10 @@ static std::vector<RPCResult> TransactionDescriptionString()
}},
{RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."},
{RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."},
{RPCResult::Type::ARR, "mempoolconflicts", "Transactions that directly conflict with either this transaction or an ancestor transaction",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR just so you don't have to re-touch it but it would be good to describe the difference between walletconflicts and mempoolconflicts inside the help text. walletconflicts description is quite vague.

@ryanofsky ryanofsky merged commit c8e3978 into bitcoin:master Mar 27, 2024
16 checks passed
@ryanofsky
Copy link
Contributor

This is merged but it would be good to followup with documentation / test improvements from #27307 (review).

Might also be useful to add release notes saying the wallet has a new heuristic to detect when wallet transactions conflict with the mempool, that conflicting mempool transactions are shown in a new gettransaction "mempoolconflicts" field, that this allows inputs to the conflicted transactions to be respent without manually abandoning the transactions, and can cause wallet balances to appear higher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants