Skip to content

Conversation

@ajtowns
Copy link
Contributor

@ajtowns ajtowns commented Dec 11, 2025

When defining RPCHelpMan objects, we usually return a lambda, and mostly we define those via [&](...) { ... } which explicitly captures any parameters or local variables by reference. If we were to actually use any of those captures (we don't), we would invoke undefined behaviour. So instead, convert all the [&] to [] to avoid capturing, and as part of RPCHelpMan check that the function we provide is convertible to a bare function pointer, so that any attempts to capture anything (even if it's by-copy, which is safe) results in an error.

While we're at it, rename RPCHelpMan to RPCMethod, reflecting it's greater responsibility since #19386.

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 11, 2025

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

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34049.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32138 (wallet, rpc: remove settxfee and paytxfee by polespinasa)
  • #29418 (rpc: provide per message stats for global traffic via new RPC 'getnetmsgstats' by vasild)
  • #26201 (Remove Taproot activation height by Sjors)

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.

@ajtowns ajtowns requested a review from maflcko December 11, 2025 03:25
@ajtowns ajtowns force-pushed the 202512-rpcimpl-noref branch from 7064e93 to 48c3c41 Compare December 11, 2025 03:56
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/20120913076/job/57740673204
LLM reason (✨ experimental): clang-tidy failed due to a bugprone-move-forwarding-reference error (forwarding reference passed to std::move()), treated as a warning-as-error.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

lgtm. Seems fine to remove this silent footgun. I left a nit in the second commit, because I think it is fine to relax it a bit.

Also, while doing a global scripted-diff here, might as well do the rename RPCHelpMan to RPCMethod from #19386 (comment)?

Comment on lines 1078 to 1080
[want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
[](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const bool want_psbt = request.strMethod == "psbtbumpfee";
Copy link
Member

Choose a reason for hiding this comment

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

nit in 48c3c41fa604b8634c509bb38d67de9bea2ab59d: I think the first commit makes a lot of sense and should be done. However, the second commit seems overly restrictive. Here, it is easy to re-create the calculation, but will it always be the case? I think it could make sense to either:

  • drop the second commit, and let asan/valgrind disallow the dangling references, like they have done in the past
  • Clarify the comment around the static_assert which is hit at compile time to say that any captured variables may have to be re-constructed a second time inside the lambda body

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, it is easy to re-create the calculation, but will it always be the case?

I think so -- the RpcMethodFnType functions don't take any params, so if you're doing a calculation (like a deprecatedrpc test) you can just do the same thing inside the RPCMethodImpl, and if you're providing something hardcoded, you can pass a boolean or enum via a template (eg template <bool wants_enum> static RPCHelpMan bumpfee_filter) and make your logic conditional on that.

Choose a reason for hiding this comment

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

I dont think the second commit is necessary either should be fine with just removal of the captures

Copy link
Member

Choose a reason for hiding this comment

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

Here, it is easy to re-create the calculation, but will it always be the case?

I think so -- the RpcMethodFnType functions don't take any params, so if you're doing a calculation (like a deprecatedrpc test) you can just do the same thing inside the RPCMethodImpl, and if you're providing something hardcoded, you can pass a boolean or enum via a template (eg template <bool wants_enum> static RPCHelpMan bumpfee_filter) and make your logic conditional on that.

Oh, I meant "succinct" instead of "easy". Imagine there was another (maybe even more verbose) const std::string incremental_fee{CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE).ToString(FeeEstimateMode::SAT_VB)};, which was used in both places (doc and impl). At some point it becomes tedious to keep the two in sync. Sure, it is possible to add a global function for it, but that also seems tedious.

We are somewhat stuck with C++ and can't use Rust, so it will be normal to use sanitizers. In this case, I think it is fine to defer to them and prefer the cleaner code. But no strong opinion, this is just my preference.

Copy link
Contributor Author

@ajtowns ajtowns Dec 11, 2025

Choose a reason for hiding this comment

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

Hmm; personally, I think if we can get compile time errors without too much hassle, that's much better than leaving it for CI to uncover.

Bumped the PR with a way of capturing succintly, but being very explicit about it when you're doing it.

(In that model, if you wanted to capture incremental_fee as well, you'd put both items of data into a struct Data { bool want_psbt; std::string incremental_fee_desc; } and write data.want_psbt instead of just want_psbt, etc)

Since this class defines the functionality of the RPC method, not
just its help text, this better reflects reality.

-BEGIN VERIFY SCRIPT-
sed -i -e 's/\bRPCHelpMan\b/RPCMethod/g' $(git grep -l RPCHelpMan src/)
-END VERIFY SCRIPT-
@ajtowns ajtowns force-pushed the 202512-rpcimpl-noref branch from 48c3c41 to 3cc9c87 Compare December 11, 2025 09:33
-BEGIN VERIFY SCRIPT-
sed -i 's/\[[&]\][(]const RPCMethod[&]/[](const RPCMethod\&/' $(git grep -l '\[\&\](const RPCMethod')
-END VERIFY SCRIPT-
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/20135737627/job/57788454545
LLM reason (✨ experimental): clang-tidy failed the CI due to a performance-move-const-arg error in spend.cpp (std::move on a const bool).

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

Capturing by reference in these functions is buggy (whatever it refers
to is gone by the time the lambda returns), so disallow it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants