wallet: track mempool conflicts with wallet transactions#27307
wallet: track mempool conflicts with wallet transactions#27307ryanofsky merged 5 commits intobitcoin:masterfrom
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
ef72243 to
f153a00
Compare
f153a00 to
ce757a5
Compare
1479572 to
d9356d2
Compare
d9356d2 to
03dc162
Compare
03dc162 to
f151405
Compare
f151405 to
d4cbfee
Compare
8c1773b to
0538ad7
Compare
3471ebc to
09bc269
Compare
|
re: #27307 (comment) Thanks ishaanam for pointing out the lines of code that answer my questions. Since original links got broken in rebase, here are stable links that point to the two lines of code which prioritize the abandoned state over the conflicted state:
This is better than what I was afraid of, since the abandoned state consistently takes priority over the mempoolconflicted state. But this behavior is fragile and unnecessarily complicates our internal model and potential RPC and GUI interfaces, because there's no logical reason for the abandoned and mempool conflicted states to contradict each other, when both can be true at the same time. If we take the current approach in 3471ebc8c6ee268ad501525121ea3f3a221fb5ad, we will have to be careful going forward, whenever we do any state change, to remember that the abandoned state is supposed to take priority over the mempool conflicted state (this is not even documented anywhere), and decide how we want to deal with potentially odd states like For background, I already think it's bad that abandoned and block-conflicted states exclude each other, and that the block conflicted state takes priority over the abandoned state and resets it. But the block conflicted state is pretty permanent, so it's basically just a UI quirk if a block conflict causes the abandoned state to be forgotten about. There are also backwards compatibility benefits to storing the block conflict and abandoned bit exclusively in the same field. In the current case, though, there are no backwards compatibility concerns, so it seems wrong and unnecessary to go out our way to create an unenforced, undocumented, and confusing hierarchy of states: MempoolConflicted < Abandoned < BlockConflicted, when there is a simpler alternative of just straightforwardly representing the information we have about transactions directly. It seems pretty clear to me that it would make this change simpler, safer and easier to review, make the code more straightforward and maintainable long term, and avoid potential bugs and confusion in the UI if it just represented abandoned and mempool conflicted states independently and didn't try to establish a hierarchy between them. I think a good approach would avoid adding a If it would be helpful, I could make another PR next week to compare the two approaches. But I'd be curious to know what you think. There is a lot of a complexity and a long history of bugs in wallet transaction state tracking (https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking) so I think think it is worth taking a little more time to get this right. |
09bc269 to
c891a65
Compare
Thanks for your feedback, I have implemented this. |
ryanofsky
left a comment
There was a problem hiding this comment.
Code review c891a65067f57ba87e443d0cdc4f7e6b434c436f.
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/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "wallet: track mempool conflicts" (2c867784b2a719c9e701e19e389c3adfa7d7faae)
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
}There was a problem hiding this comment.
The preceding was more of a refactor, while this commit aims to have IsSpent return false for txos spent by mempool-conflicted transactions.
There was a problem hiding this comment.
In commit "wallet: track mempool conflicts" (a650caab9f11be7f5927f9aa556bb7d2a8ebed33)
The preceding was more of a refactor, while this commit aims to have
IsSpentreturn 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.
c891a65 to
923734e
Compare
|
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. |
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 923734ef6528e5d4e13748db7387f44d02237d4d. 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 a650caab9f11be7f5927f9aa556bb7d2a8ebed33 to previous a6ae5b23b1497ab6f4899db49348db623700a2d8, 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/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "wallet: track mempool conflicts" (a650caab9f11be7f5927f9aa556bb7d2a8ebed33)
The preceding was more of a refactor, while this commit aims to have
IsSpentreturn 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.
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK b853688c92d3745d5ec2ec5119584148084e7c1c. 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
There was a problem hiding this comment.
In commit "wallet: track mempool conflicts" (9fa7f813281d8520a5eb5adbd29fe3a0d2ab5a4f)
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).
src/wallet/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "wallet: use CWalletTx member functions to determine tx state" (92b3204b69e5ec25cbb29bd158f25d2b07e48cf6)
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())
furszy
left a comment
There was a problem hiding this comment.
left few small things. Will get deeper.
test/functional/wallet_conflicts.py
Outdated
There was a problem hiding this comment.
In d3f7764c802fe:
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)
-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-
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK dbcc4a75e4a1108f4a04c22cfae363848994c24d. 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.
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
|
ACK 5952292 |
| """ | ||
|
|
||
| self.test_block_conflicts() | ||
| self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 7, self.nodes[2].getnewaddress()) |
There was a problem hiding this comment.
nit:
Would be good to mention why this generatetoaddress is needed. I removed it locally and the test still passes.
| }}, | ||
| {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", |
There was a problem hiding this comment.
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.
|
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 |
The
mempool_conflictsvariable is added toCWalletTx, 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.IsSpentnow returns false for an output being spent by a mempool conflicted transaction where it previously returned true.A txid is added to
mempool_conflictsduringtransactionAddedToMempool. A txid is removed frommempool_conflictsduringtransactionRemovedFromMempool.This PR also adds a
mempoolconflictsfield to thegettransactionwallet RPC result.Builds on #27145
Second attempt at #18600