-
Notifications
You must be signed in to change notification settings - Fork 37.7k
RBF move (1/3): extract BIP125 Rule 5 into policy/rbf #22796
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
A circular dependency is added because policy now depends on txmempool and txmempool depends on validation. It is natural for [mempool] policy to rely on mempool; the problem is caused by txmempool depending on validation. bitcoin#22677 will resolve this.
This should have no effect in practice, since we only ever call PreChecks once per transaction.
No behavior change. While we're looking through the descendants and calculating how many transactions we might replace, quit early, as soon as we hit 100. Since we're failing faster, we can also separate the loops - yes, we loop through more times, but this helps us detangle the different BIP125 rules later.
Code review ACK f293c68 |
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.
Thanks for splitting up, this way the changes are easier to digest and should also potentially attract more reviewers.
Code-review ACK f293c68 📔
|
||
#include <policy/settings.h> | ||
#include <tinyformat.h> | ||
#include <util/moneystr.h> |
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.
nit: I think this include is not needed in this compilation unit (neither FormatMoney
nor ParseMoney
is used anywhere here)
#include <util/moneystr.h> |
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.
ah true, I guess not needed until 781e100
will update if i need to repush
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
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.
ACK f293c68
I verified that test_too_many_replacements
in test/functional/feature_rbf.py
still cover correctly the refactored bip 125 rule 5 check.
I think any comments if worthy can be addressed in a follow-up or be taken on another PR of this serie of works.
* SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by | ||
* non-replaceable transactions. All inputs rather than just one | ||
* is for the sake of multi-party protocols, where we don't | ||
* want a single party to be able to disable replacement. */ |
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.
nit: "...by opt-in out its contributed input"
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.
Noted, will add this to a33fdd0!
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.
Taken in #22855
CAmount m_conflicting_fees; | ||
size_t m_conflicting_size; | ||
/** Total modified fees of all transactions being replaced. */ | ||
CAmount m_conflicting_fees{0}; |
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.
I think what we called "modified fees" in before-this-PR PreChecks()
are currently the base_fee of the mempool candidate plus the delta from PrioritiseTransaction
(L698 in src/validation.cpp
). Maybe the "modified" adjective can be dropped ?
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.
Correct, modified is base + delta from prioritise. But why should we drop the "modified" adjective? It's still very widely used in the mempool and MemPoolAccept
code. In my view, just saying "fees" is less clear, as I would assume that to mean base fees.
|
||
std::set<uint256> setConflictsParents; | ||
for (const auto& mi : setIterConflicting) { |
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.
Note, I think this commit is changing a bit the processing, Previously, the 3 operations a) checking candidate feerate is better than any replaced transaction b) fulfilling the set of parents inputs and c) accounting replaced transactions with descendants in the same loop.
After this commit, setIterConflicting
is iterated 3 times. Overall I think it's okay, though it might add a bit of work in case of well-sized set of replaced transactions. From a DoS viewpoint no changes. An upper bound is still enforced with nConflictingCount
against MAX_BIP_125_REPLACEMENT_CANDIDATES
(that would be great to have a set of QA assets with a wide range of mempool candidates payloads to confirm or infirm that kind of intuitions, not sure that's an information you can currently get from qa-assets/fuzz_seed_corpus/validation_load_mempool
)
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.
Right, I was hoping to make this clear in the commit messages. Because of the changes in the preceding commit, we now quit immediately when we see more than MAX_BIP_125_REPLACEMENT_CANDIDATES
. Previously, we'd grab all potential candidates and only check the total number afterward. So this PR has, overall, reduced the complexity from O(n) to O(1) where n is the number of conflicts, which is why I think it's okay to iterate 3 times.
The only observable "behavior" change is that we might return too many potential replacements
instead of insufficient fee
when a transaction violates both.
* @param[out] err_string Used to return errors, if any. | ||
* @param[out] allConflicting Populated with all the mempool entries that would be replaced, | ||
* which includes descendants of setIterConflicting. Not cleared at | ||
* the start; any existing mempool entries will remain in the set. |
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.
What do you mean by "Not cleared at the start; any existing mempool entries will remain in the set" ?
If the replacement is successful, allConflicting
are going to be removed in Finalize
. For package support, I think this operation we'll remove the per-package tx allConflicting
set all at once. If PreChecks
or any new other package policy checks as failed, don't modify mempool state.
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.
It's similar to other mempool functions where you can pass in a setEntries
and it will insert
entries, but not expect it to be empty beforehand, so you can call it multiple times and end up with an aggregated set. I'm expecting to use this in package RBF. It has nothing to do with what's going to end up in the mempool; this is just a helper function.
The reason I document it here is I think it's a relevant detail in the interface. It's the caller's responsibility to clear the set beforehand if it needs that.
Please comment here when you're addressing followup comments / nits via the next PRs in the series. |
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.
Post-merge ACK f293c68
for (CTxMemPool::txiter it : allConflicting) { | ||
nConflictingFees += it->GetModifiedFee(); | ||
nConflictingSize += it->GetTxSize(); | ||
} |
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 the same spirit of exiting early, i think we could check if (nModifiedFees < nConflictingFees)
here directly. We could probably also check the delta fee.
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.
I'm not sure if that would be much of an improvement? There would be <=100 entries to aggregate fees for, so this is pretty tightly bounded already.
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.
cr ACK f293c68
but I don't like the symbol re-use
} | ||
|
||
// Calculate all conflicting entries and enforce Rule #5. | ||
if (!GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting, err_string)) { |
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.
f293c68: I think for new code we should avoid leaking mutable return args into a greater scope than needed. Especially when the goal is to re-use (https://github.com/bitcoin/bitcoin/pull/22675/files) the symbol (err_string), when it shouldn't be re-used. With C++17 this is trivial to fix.
One example:
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting)}) {
return ..., *err_string;
and make the util function return an error string or nullopt if no error is hit.
Re-using symbols has bitten us in ATMP in the past, so I don't think there is need to repeat that. See commit 116419e
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.
Good point, I've added a followup commit for GetEntriesForConflicts
and edited the rest of the functions to return a std::optional<std::string>
in #22675
See #22675 for motivation, this is one chunk of it. It extracts some BIP125 logic into policy/rbf:
SignalsOptInRBF
function instead of manually iterating through inputsAlso see cut the validation <-> txmempool circular dependency 2/2 #22677 for addressing the circular dependency.