-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: fix unrelated parent conflict doesn't cause child tx to be marked as conflict #29680
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
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29680. 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. |
🚧 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. |
abaa8ca
to
4853bb5
Compare
ba8c831
to
532d25f
Compare
Draft until I've gotten some more feedback on the approach. |
cc @achow101? |
I'm actually wondering now if it would be sufficient to just have the |
@achow101 I think this makes sense. Once we add conflicting block hash for conflicts then we can safely mark wallet tx as conflicted which should solve the issue. What would we still need the replacement txid for? EDIT When you say "replacement txid", are you referring to the txid of the tx causing the wallet tx to kicked out? if so, I believe the current fix implemented here does exactly what you're describing, See 635e2e4 |
Yes, we need the replacement txid for instances where the tx is removed by replacement rather than a block conflict.
It appears to also be watching for replacements of those replacements too, and I think that is unnecessary. |
Thanks @achow101. Yes, I added that when I realized that if the replacement is also replaced then the check in the |
532d25f
to
47750d3
Compare
@achow101 I took this out because the new replacement is not guaranteed to conflict with the original wallet transaction Added I had to use Curious to see what others think. EDIT This PR now modifies Now marking this PR as ready for review. |
47750d3
to
8ee9629
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. |
8ee9629
to
e868b2d
Compare
Putting in draft while I fix falling test |
e868b2d
to
e004ecf
Compare
19f3927
to
ef7b1e8
Compare
#30072 introduced Mempool Changesets for packages and single transactions. Previously, I captured the replacement_tx when a tx was being kicked out of the Mempool, but with the addition of packages, any of the tx in the package could be the reason for the conflict. This solution assumes that all transactions in a package will be added to a Block. Suppose the first transaction in a 1P1C package is the actual replacement tx and the package only partially gets added to a Block. In that case, the wallet will not abandon the conflicted wallet tx because it's watching the child of the package. |
ef7b1e8
to
20a4038
Compare
The CI failed once (intermittently?): https://cirrus-ci.com/task/5838005635645440?logs=ci#L2655
|
Thats https://github.com/bitcoin/bitcoin/pull/29680/files#r1849679192 Maybe turn into a draft for now, while CI is failing intermittently? |
Putting in draft while I fix failing CI |
20a4038
to
bba3235
Compare
This allows the mempool to send additional data with TransactionRemovedFromMempool event. Now, we can send conflicting_block_hash and conflicting_block_height for Conflicts and replacement_tx for Replacements.
Detect replacement of wallet txs and wait for confirmation of replacement tx before marking wallet tx as conflicted
Watch for wallet transaction conflicts triggered by adding conflicting blocks
bba3235
to
6db2961
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
Left some initial comments
Previously, I captured the replacement_tx when a tx was being kicked out of the Mempool, but with the addition of packages, any of the tx in the package could be the reason for the conflict.
I've changed the replacement_tx to be the last child tx in the package so the wallet doesn't have to watch the entire replacement package.
Can you elaborate on how that work, and the wallet detects that, what if the conflict is not from the child, it's from a parent transaction?
This solution assumes that all transactions in a package will be added to a Block.
This is Incorrect I think, you can have a package and not all the transaction will be added in the block.
Suppose the first transaction in a 1P1C package is the actual replacement tx and the package only partially gets added to a Block.
In that case, the wallet will not abandon the conflicted wallet tx because it's watching the child of the package.
How is this not an issue?
@@ -7,6 +7,7 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 4c64dd5 "Change MemPoolRemovalReason to variant type"
nit: Commit message should follow the guidelines in https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#committing-patches
The explanation lines are long, you should break it.
uint256 conflicting_block_hash; | ||
unsigned int conflicting_block_height; | ||
|
||
explicit ConflictReason(const uint256& conflicting_block_hash, int conflicting_block_height) : conflicting_block_hash(conflicting_block_hash), conflicting_block_height(conflicting_block_height) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 4c64dd5 "Change MemPoolRemovalReason to variant type"
nit: this line is too long, please break.
auto* replaced_reason = std::get_if<ReplacedReason>(&reason); | ||
|
||
// Check if wallet transaction is being replaced by a parent transaction which is not from this wallet | ||
if (IsFromMe(*tx) && replaced_reason != nullptr && replaced_reason->replacement_tx.has_value() && !IsFromMe(*replaced_reason->replacement_tx.value())) { | ||
m_unrelated_conflict_tx_watchlist.insert(std::make_pair(replaced_reason->replacement_tx.value()->GetHash(), tx)); | ||
} | ||
|
||
auto iter = m_unrelated_conflict_tx_watchlist.find(tx->GetHash()); | ||
if (iter != m_unrelated_conflict_tx_watchlist.end()) { | ||
// The replacement tx was removed from the mempool, remove it from map | ||
// This new replacement tx may not conflict with the original tx | ||
// so leave wallet tx to remain as TxStateInactive | ||
m_unrelated_conflict_tx_watchlist.erase(iter); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 71200b7 "Handle double-spending of unrelated parents to wallet txs"
auto* replaced_reason = std::get_if<ReplacedReason>(&reason); | |
// Check if wallet transaction is being replaced by a parent transaction which is not from this wallet | |
if (IsFromMe(*tx) && replaced_reason != nullptr && replaced_reason->replacement_tx.has_value() && !IsFromMe(*replaced_reason->replacement_tx.value())) { | |
m_unrelated_conflict_tx_watchlist.insert(std::make_pair(replaced_reason->replacement_tx.value()->GetHash(), tx)); | |
} | |
auto iter = m_unrelated_conflict_tx_watchlist.find(tx->GetHash()); | |
if (iter != m_unrelated_conflict_tx_watchlist.end()) { | |
// The replacement tx was removed from the mempool, remove it from map | |
// This new replacement tx may not conflict with the original tx | |
// so leave wallet tx to remain as TxStateInactive | |
m_unrelated_conflict_tx_watchlist.erase(iter); | |
} | |
auto* replaced_reason = std::get_if<ReplacedReason>(&reason); | |
if (IsFromMe(*tx) && replaced_reason != nullptr) { | |
// Check if wallet transaction is being replaced by a parent transaction which is not from this wallet | |
if (replaced_reason->replacement_tx.has_value() && !IsFromMe(*replaced_reason->replacement_tx.value())) { | |
m_unrelated_conflict_tx_watchlist.insert(std::make_pair(replaced_reason->replacement_tx.value()->GetHash(), tx)); | |
} | |
// Remove the replacement tx was removed from the mempool, remove it from map | |
// This new replacement tx may not conflict with the original tx | |
// so leave wallet tx to remain as TxStateInactive | |
m_unrelated_conflict_tx_watchlist.erase(tx->GetHash()); | |
} |
// This new replacement tx may not conflict with the original tx | ||
// so leave wallet tx to remain as TxStateInactive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 71200b7 "Handle double-spending of unrelated parents to wallet txs"
How do you know that without checking ?
@ismaelsadeeq I wanted to get other opinions on this. With the addition of changesets, the problem is more complicated.
It does work because it's guaranteed that the parent will be confirmed before the child. The wallet can mark the replaced wallet tx as conflicted when the child of the package gets confirmed. The problem though is that the child may never be confirmed even if the parent has been confirmed.
It is. |
🐙 This pull request conflicts with the target branch and needs rebase. |
⌛ There hasn't been much activity lately and the patch still needs rebase. What is the status here?
|
This PR implements a fix for the issue described in #29435.
The problem is that the wallet is unable to abandon transactions that have unrelated parent conflicts. The solution implemented here, augments the mempool transaction
REPLACED
signal with the double-spending transaction which the wallet stores and watches for in Block notifications. A map is added to the wallet to track conflicting tx ids and their child transactions. The entry is erased when the double-spending tx is removed from MemPool.