Skip to content

Conversation

@stickies-v
Copy link
Contributor

@stickies-v stickies-v commented Oct 10, 2022

Upon reviewing the documentation for CTxMemPool::CalculateMemPoolAncestors, I noticed setAncestors was meant to be an out parameter but actually is an in,out parameter, as can be observed by adding assert(setAncestors.empty()); as the first line in the function and running make check. This PR fixes this unexpected behaviour and introduces refactoring improvements to make intents and effects of the code more clear.

Unexpected behaviour

This behaviour occurs only in the package acceptance path, currently only triggered by testmempoolaccept and submitpackage RPCs.

In MemPoolAccept::AcceptMultipleTransactions(), we first call PreChecks() and then SubmitPackage() with the same Workspace ws reference. PreChecks leaves ws.m_ancestors in a potentially non-empty state, before it is passed on to MemPoolAccept::SubmitPackage. SubmitPackage is the only place where setAncestors isn't guaranteed to be empty before calling CalculateMemPoolAncestors. The most straightforward fix is to just forcefully clear setAncestors at the beginning of CalculateMemPoolAncestors, which is done in the first bugfix commit.

Improvements

Return value instead of out-parameters

This PR updates the function signatures for CTxMemPool::CalculateMemPoolAncestors and CTxMemPool::CalculateAncestorsAndCheckLimits to use a util::Result return type and eliminate both the setAncestors in,out-parameter as well as the error string. It simplifies the code and makes the intent and effects more explicit.

Observability

There are 7 instances where we currently call CalculateMemPoolAncestors without actually checking if the function succeeded because we assume that it can't fail, such as in miner.cpp. This PR adds a new wrapper AssumeCalculateMemPoolAncestors function that logs such unexpected failures, or in case of debug builds even halts the program. It's not crucial to the objective, more of an observability improvement that seems sensible to add on here.

@DrahtBot
Copy link
Contributor

DrahtBot commented Oct 10, 2022

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK w0xlt, glozow, furszy, aureleoules, achow101
Concept ACK theStack
Stale ACK willcl-ark

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:

  • #26531 (mempool: Add mempool tracepoints by virtu)

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.

Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

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

Approach ACK

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

Concept ACK, definitely prefer this over out-params.

@theStack
Copy link
Contributor

Concept ACK

Copy link
Contributor

@aureleoules aureleoules left a comment

Choose a reason for hiding this comment

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

ACK a425537fbfc20d87c325b5ae2f06f2597a31cf0e - LGTM and no behavior change

@luke-jr
Copy link
Member

luke-jr commented Oct 25, 2022

If there's an actual bug, it should be fixed before/independently of the refactor (and only that fix backported).

@stickies-v
Copy link
Contributor Author

Force pushed to address review comments.

If there's an actual bug, it should be fixed before/independently of the refactor (and only that fix backported)

I added c25c6a6


Most of the changes address furszy's comments. In summary: ancestors/descendant limits unexpectedly getting hit no longer result in an assertion error (unless on debug builds) but in an error log message instead to avoid the node from crashing in case of a (non-fatal) unexpected error.

@stickies-v stickies-v marked this pull request as draft October 25, 2022 20:47
@stickies-v
Copy link
Contributor Author

Marking as draft until I've resolved the CI issues.

@stickies-v stickies-v marked this pull request as ready for review October 26, 2022 12:36
@stickies-v
Copy link
Contributor Author

stickies-v commented Oct 26, 2022

PR is now ready for re-review - CI issues fixed. Looks like the argument forwarding was causing issues on some platforms (ARM, Win64) - so I've just updated it to not use forwarding, since debugging it was going too slow and this approach is clean enough.

Copy link
Contributor

@aureleoules aureleoules left a comment

Choose a reason for hiding this comment

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

reACK 2f832f0c45aae3b51353db72691b4bc6450a7e9a

@JeremyRubin
Copy link
Contributor

JeremyRubin commented Oct 28, 2022

Would there be any merit to an alternative approach of logging how many current ancestors were passed in, and then subtracting that for the invariant check? That way you allocate less memory in the package code?

@glozow
Copy link
Member

glozow commented Nov 1, 2022

Would there be any merit to an alternative approach of logging how many current ancestors were passed in, and then subtracting that for the invariant check? That way you allocate less memory in the package code?

Having trouble understanding the question, can you elaborate on where the subtraction would be and which memory allocation you're referring to?

@stickies-v
Copy link
Contributor Author

stickies-v commented Nov 23, 2022

where the setAncestors is consumed -- afaict, it's eventually transformed into just a count

For example in CTxMemPool::PrioritiseTransaction and CTxMemPool::UpdateForRemoveFromMempool we actually use the calculated ancestors in a way that doesn't seem trivial to refactor out.

@glozow
Copy link
Member

glozow commented Nov 29, 2022

@JeremyRubin

I think I was mostly observing that the ancestors tracking is mostly being used for limits checking here, so there is an opportunity to (I think) not actually build a ancestor set and just use epochs to count the number of distinct ancestors directly where the setAncestors is consumed

For example in CTxMemPool::PrioritiseTransaction and CTxMemPool::UpdateForRemoveFromMempool we actually use the calculated ancestors in a way that doesn't seem trivial to refactor out.

Agree with @stickies-v, we definitely require the actual entries themselves at several CalculateMemPoolAncestors callsites externally as well, see

pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
for (CTxMemPool::txiter it : ancestors) {
if (SignalsOptInRBF(it->GetTx())) {
return RBFTransactionState::REPLACEABLE_BIP125;
}
}

And some only require that the limits are checked - we could split those into 2 different functions e.g. GetAncestorEntries() and CheckAncestorDescendantLimits() in the future, but in any case this refactor makes sense for a GetAncestorEntries() interface.

Copy link
Member

@willcl-ark willcl-ark left a comment

Choose a reason for hiding this comment

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

ACK 1f89af219a89da155d5302ca4fdc4f8f1f8612aa

I am unsure about the benefit of the first commit which is later removed, but it seems this might still be up for discussion.

The larger refactor related to removing out params and using Result looks good.

Avoid using setAncestors outparameter, simplify function signatures
and avoid creating unused dummy strings.
Copy link
Contributor Author

@stickies-v stickies-v left a comment

Choose a reason for hiding this comment

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

Force pushed to remove the bugfix commit and address review comments re instantiating invalid_too_long making the state invalid even when the failure path isn't hit

@stickies-v stickies-v requested review from aureleoules and willcl-ark and removed request for aureleoules and willcl-ark December 12, 2022 18:26
@aureleoules
Copy link
Contributor

CI is red @stickies-v

@stickies-v
Copy link
Contributor Author

CI is red @stickies-v

Sorry, fixed now by capturing the original error message in a variable, so we don't show the error message from trying the cpfp carve-out.

Avoid using setAncestors outparameter, simplify function signatures
and avoid creating unused dummy strings.
There are quite a few places that assume CalculateMemPoolAncestors
will return a value without raising an error. This helper function
adds logging (and Assume for debug builds) that ensures robustness
but increases visibility in case of unexpected failures
When CalculateMemPoolAncestors fails unexpectedly (e.g. it exceeds
ancestor/descendant limits even though we expect no limits to be applied),
add an error log entry for increased visibility. For debug builds,
the application will even halt completely since this is not supposed
to happen.
@stickies-v
Copy link
Contributor Author

Force pushed to avoid set copy operations when calling value_or() on results, see this comment for more background

Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

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

ACK 47c4b1f

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

ACK 47c4b1f
code review to verify no change in behavior, and bench via ComplexMemPool and #26695 suggests no change in performance

@glozow glozow requested a review from willcl-ark December 20, 2022 14:10
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.

light code review ACK 47c4b1f

left two comments, nothing blocking.

Comment on lines +257 to +260
CTxMemPool::setEntries CTxMemPool::AssumeCalculateMemPoolAncestors(
std::string_view calling_fn_name,
const CTxMemPoolEntry &entry,
const Limits& limits,
Copy link
Member

@furszy furszy Dec 30, 2022

Choose a reason for hiding this comment

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

Non-blocking point:

Could remove the limits arg. All the callers use CTxMemPool::Limits::NoLimits() and the only way CalculateMemPoolAncestors fail is due a limit restriction.

Comment on lines +257 to +258
CTxMemPool::setEntries CTxMemPool::AssumeCalculateMemPoolAncestors(
std::string_view calling_fn_name,
Copy link
Member

Choose a reason for hiding this comment

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

Non-blocking nit:

This should be a helper/util static function rather than a class method. It's weird for a class method to receive the caller's function name just to log it internally.

Copy link
Contributor

@aureleoules aureleoules left a comment

Choose a reason for hiding this comment

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

ACK 47c4b1f
Great refactor that fixes a confusing function signature.

@achow101
Copy link
Member

achow101 commented Jan 3, 2023

ACK 47c4b1f

@achow101 achow101 merged commit 80fc1af into bitcoin:master Jan 3, 2023
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 4, 2023
@bitcoin bitcoin locked and limited conversation to collaborators Jan 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.