-
Notifications
You must be signed in to change notification settings - Fork 35.5k
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: fix unrelated parent conflict doesn't cause child tx to be marked as conflict #29680
base: master
Are you sure you want to change the base?
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. ConflictsNo conflicts as of last run. |
🚧 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
Seems easily addressed with a constructor, no? Something like: class RemovedReason {
public:
MemPoolRemovalReason m_reason;
std::variant<std::monostate, CTxReference, BlockData> m_extra_data;
// Constructor for reasons that don't require extra data
RemovedReason(MemPoolRemovalReason r) : reason(r) {
if (requiresExtraData(r)) {
throw std::invalid_argument("reason X requires data Y etc");
}
}
// Constructor needing CTxRef
RemovedReason(RemovalReason r, const CTxRef& data) : reason(r), extra_data(data) {
if (!IsA(r)) {
throw std::invalid_argument("CTxRef is required for reason A, got bla");
}
}
// Constructor needing BlockData
RemovedReason(RemovalReason r, const BlockData& data) : reason(r), extra_data(data) {
if (!IsB(r)) {
throw std::invalid_argument("BlockData is required for reason B, got bla");
}
} Maybe this is starting to be more complicated than just having a struct per each reason? But I'd still argue this is a better approach in that it keeps all the logic for mempool removal reasons in one place and avoids duplicating code on each struct. |
Same thing I was thinking. Using the class right now looks like it will make things more complicated. Maybe we should leave the class for a future change where it becomes necessary? @josibake @ryanofsky |
Yes, but those are manual constraints that you are writing by hand rather than automatic constraints expressed implicitly in the data definition. Depending on the constructors it may also only provide runtime checking rather than compile-time checking like in your example. And if the struct is mutable could allow invalid representations of state after construction. I don't know what is best in this particular case, I would just stand up for: struct MyState1 { int data; };
struct MyState2 { bool flag; };
struct MyState3 {};
using MyState = std::variant<MyState1, MyState2, MyState3>; as a good alternative to: enum class MyState {
STATE1,
STATE2,
STATE3,
};
class MyData {
MyState m_state,
// ... more data and methods...
}; in many cases.
I'm not sure the answer to this, but it is probably worth experimenting and choosing the approach that seems simplest. |
Fair point, in that bugs could be introduced by someone not writing these pre-checks correctly / efficiently. I'll admit I'm not fully convinced that the struct per state approach isn't going to be harder to maintain / extend in the future, but given that I haven't convinced you guys on that point and you have convinced me there is an advantage per the struct per state approach, I'll retract my suggestion we change it 😄 |
ea66044
to
7da8f98
Compare
7da8f98
to
f7ec03e
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.
Still reviewing the later commits, but had some initial feedback/questions for the first commit.
f7ec03e
to
e5bcb2d
Compare
e5bcb2d
to
93343d0
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. |
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
93343d0
to
b6d1a3f
Compare
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.