-
Notifications
You must be signed in to change notification settings - Fork 38.6k
rpc: Add MaybeArg() and Arg() default helper #28230
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. 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. |
|
Concept ACK |
fa13ff4 to
fa8cb8b
Compare
fac0196 to
fa6b2da
Compare
stickies-v
left a comment
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.
Approach ACK fa6b2da57ef7ff125a493c7835cb15935255ff8c
This makes for a much more elegant interface throughout the RPC methods, with minimal overhead.
fa6b2da to
fa5468d
Compare
stickies-v
left a comment
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 fa5468dcf0109d11ddaeb4b3591e6e04b4ea6125, really like this!
|
Concept ACK |
faa8309 to
fa56ad7
Compare
src/rpc/util.h
Outdated
| * Helper to get a request argument or its default value. | ||
| * This function only works during m_fun(), i.e. it should only be used in RPC method implementations. | ||
| * Use Arg<Type> to get the argument or its default value. | ||
| * Use Arg<Type*> to get the argument or a falsy value. |
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 commit "rpc: Add Arg() default helper" (fa56ad7)
Haven't really looked at implementation yet, but can documentation be updated to say what happens with type mismatches? E.g. if arg is an int and you request a bool? Is the value just ignored and is a default returned instead? Is there an exception?
Also might be nice to add a short examples here showing what equivalent low level code is so developers can know how to use this helper and interpret it. Like Arg<int>(3) is equivalent to request.params[3].isNumber() ? request.params[3].getInt<int>() : default_value (or whatever the equivalent is)
Overall this seems like a nice improvement that should cut down on boilerplate. I am wondering if it might be possible later to extend this:
- to support arrays/objects
- to enforces only expected types are passed at compile time
- to enforce default not requested when there is no default at compile time
- to somehow be compatible with luke's RPC/Wallet: Convert walletprocesspsbt to use options parameter #24963 which allows multiple types per parameter (if that is a good idea)
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.
to enforces only expected types are passed at compile time
Do we want to revisit #22978 first before doing compile time type checking for RPC?
I think compile time RPC type checks would require templatising RPCArg by what is currently m_type (and m_inner perhaps), changing RPCHelpMan to be templatised based on a std::tuple<...> of RPCArg<X>s, and likewise for RPCMethodImpl which then gives the actual RPC implementation compile time access to its expected argument types.
I don't think you could reasonably access params by name and get compile time type checking; for the named-only params via options, you could turn options into an ordered tuple and access named-only params by their position from when you defined the options object.
For multitype parameters, I think it'd be best to just have a Type::ANY placeholder.
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.
Haven't really looked at implementation yet, but can documentation be updated to say what happens with type mismatches?
This sound like a bug in the source code, so I don't think it makes sense to document unreachable or unexpected code paths. Generally, in the RPC server non-fatal bugs are expected to throw an internal bug message.
I haven't looked at the code, but types of arguments passed in by the user should be checked before m_fun is run. m_fun, and thus, Arg() isn't run when the types passed in by the user mismatch the ones in the documentation. If Arg() specifies the wrong type, the getter will throw. Currently all getters are univalue getters, so they'll throw the univalue internal exception. (This is the current behavior and preserved)
I think this is fine as-is, but I am happy to change it to something else, for example:
- Re-implement the type check and throw a new, special exception. (This will be a change in "behavior", or rather "bug behavior")
- Implement a compile-time json and use the compiler to avoid the case altogether, or alternatively, use clang-tidy to do it?
Also might be nice to add a short examples here showing what equivalent low level code is so developers can know how to use this helper and interpret it.
The diff in this commit should explain it. I've also added docs about the internals (isNull check and fallback to RPCArg::m_fallback)
to support arrays/objects
Yeah, it should be possible to return an object whose operator[] is defined. Would it be fine to do this in a follow-up?
to enforces only expected types are passed at compile time
Maybe as follow-up, unless the patch is trivial?
to enforce default not requested when there is no default at compile time
This is already done, no?
to somehow be compatible with luke's
Not sure about adding dead code, but once and if it is needed, it should be trivial to implement by mapping the types to a Arg<std::variant<int, bool>>(1) in C++ code. However, I am not sure if this is worth it, when it is only used once. Might as well use the "legacy" method for now?
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 compile time RPC type checks would require templatising
RPCArgby what is currentlym_type(andm_innerperhaps), changingRPCHelpManto be templatised based on astd::tuple<...>ofRPCArg<X>s, and likewise forRPCMethodImplwhich then gives the actual RPC implementation compile time access to its expected argument types.
I haven't looked at this, but some RPCHelpMan have run-time inputs, so I am not sure if they can be trivially converted to compile-time inputs. Maybe this can be done in a follow-up? The diff should be a trivial change from Arg<int>(0) to Arg<int, 0>(), right? Or with future C++ versions no change in m_fun implementations is needed at all?
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.
This sound like a bug in the source code, so I don't think it makes sense to document unreachable or unexpected code paths.
If it's a bug to to call the the Arg function with an unexpected type, it's important for the documentation to at say what types it is allowed to be called with, because I don't think there's another way of knowing this without a having a pretty deep understanding of the RPCHelpMan framework and digging into template and macro code.
If you just want the documentation to say "It is a bug to pass X type" instead of "Passing X type will throw an exception" that is fine of course.
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 important for the documentation to at say what types it is allowed to be called with
I guess long term it could make sense to make RPCArg::Type an enum of C++ types and then have the documentation say that the type passed to Arg<Type>(i) must match exactly the corresponding RPCArg::Type.
For now, my recommendation would be to call Arg<std::string>(i) for STR* args, Arg<bool>(i) for BOOL args, and Arg<double/(u)int${n}_t>(i) for NUM args.
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.
Would it be fine to add "The Type passed to this helper must match the corresponding RPCArg::Type" to the doxygen of Arg?
Edit: Done.
src/rpc/util.cpp
Outdated
| return &std::get<RPCArg::Default>(fallback); | ||
| } | ||
|
|
||
| #define TMPL_INST(no_default, ret_type, get_arg, return_code) \ |
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.
We could remove no_default from the function signature here, and deduce it from the ret_type being a std::optional or pointer type? I think both approaches have merit, so no very strong preference either way atm, but since we're already doing those deductions in Arg I think it would make sense here, too?
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.
Sure, mind providing a patch that compiles, which I can take?
stickies-v
left a comment
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 extending this to cover all kinds of argument types, I think this will make it much more straightforward for users to add in their own template instantiations where needed.
I still don't really have a clear preference for std::optional vs pointer, and I think it's not that important, so ACK fa56ad7
fa448d5 to
faac216
Compare
|
Addressed all (style) feedback so far, I think 😅 |
|
utACK faac216d48b81a07c85c6185af237d7bfd57e23c |
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 faac216d48b81a07c85c6185af237d7bfd57e23c
nit: pull description & title have gotten a bit outdated as the PR changed quite a bit since its initial version. I think a brief mention of MaybeArg and the optionality of arguments could be helpful to include here?
As a nice bonus, it seems adding the RPCHelpMan::m_req pointer also makes it quite trivial to get an argument by name and builds nicely on this pull. I quickly pulled something together in stickies-v@c19d186 just as a PoC that passes functional tests, and (the delta to this PR) seems significantly less work than #27788 (but I've not properly tested/dug into this, just something I thought of).
|
Thanks, edited subject line and description. |
sedited
left a comment
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 faac216d48b81a07c85c6185af237d7bfd57e23c
|
ACK faac216d48b81a07c85c6185af237d7bfd57e23c |
src/rpc/util.cpp
Outdated
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.
../../../src/rpc/util.cpp:671:10: error: redundant redeclaration of ‘void force_semicolon()’ in same scope [-Werror=redundant-decls]
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:675:1: note: in expansion of macro ‘TMPL_INST’
675 | TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: note: previous declaration of ‘void force_semicolon()’
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:674:1: note: in expansion of macro ‘TMPL_INST’
674 | TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: error: redundant redeclaration of ‘void force_semicolon()’ in same scope [-Werror=redundant-decls]
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:676:1: note: in expansion of macro ‘TMPL_INST’
676 | TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: note: previous declaration of ‘void force_semicolon()’
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:675:1: note: in expansion of macro ‘TMPL_INST’
675 | TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: error: redundant redeclaration of ‘void force_semicolon()’ in same scope [-Werror=redundant-decls]
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:679:1: note: in expansion of macro ‘TMPL_INST’
679 | TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: note: previous declaration of ‘void force_semicolon()’
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:676:1: note: in expansion of macro ‘TMPL_INST’
676 | TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: error: redundant redeclaration of ‘void force_semicolon()’ in same scope [-Werror=redundant-decls]
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:680:1: note: in expansion of macro ‘TMPL_INST’
680 | TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: note: previous declaration of ‘void force_semicolon()’
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:679:1: note: in expansion of macro ‘TMPL_INST’
679 | TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: error: redundant redeclaration of ‘void force_semicolon()’ in same scope [-Werror=redundant-decls]
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:681:1: note: in expansion of macro ‘TMPL_INST’
681 | TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););
| ^~~~~~~~~
../../../src/rpc/util.cpp:671:10: note: previous declaration of ‘void force_semicolon()’
671 | void force_semicolon()
| ^~~~~~~~~~~~~~~
../../../src/rpc/util.cpp:680:1: note: in expansion of macro ‘TMPL_INST’
680 | TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
| ^~~~~~~~~
cc1plus: all warnings being treated as errors
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.
Seems to work fine without this line.
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.
Could drop it and just not have the semi-colons after the macro invocations too.
Could also change it to void force_semicolon(ret_type dummy) to avoid the warning.
clang accepts but ignores -Wredundant-decls, clang-tidy defaults to ignoring the warning in macros (yay!), but seems like the current code will be annoying for compiling with gcc.
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.
Any reason why CI didn't fail? I guess this is #25972
faac216 to
c00000d
Compare
|
Force pushed to fix the two nits, should be trivial to re-ACK |
stickies-v
left a comment
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.
re-ACK c00000d
Verified that this:
- updates commit msg to PR title
- implements #28230 (comment)
- fixes #28230 (comment)
sedited
left a comment
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.
re-ACK c00000d
|
reACK c00000d |
…ring literals to Parse{Hash,Hex}
bb91131 doc: remove out-of-date external link in src/util/strencodings.h (Jon Atack)
7d494a4 refactor: use string_view to pass string literals to Parse{Hash,Hex} (Jon Atack)
Pull request description:
as `string_view` is optimized to be trivially copiable, whereas the current code creates a `std::string` copy at each call.
These utility methods are called by quite a few RPCs and tests, as well as by each other.
```
$ git grep "ParseHashV\|ParseHashO\|ParseHexV\|ParseHexO" | wc -l
61
```
Also remove an out-of-date external link.
ACKs for top commit:
jonatack:
Rebased per `git range-diff c9273f6 b94581a bb91131` for an include header from the merge of bitcoin/bitcoin#28230. Should be trivial to re-ACK.
maflcko:
lgtm ACK bb91131
ns-xvrn:
ACK bitcoin/bitcoin@bb91131
achow101:
ACK bb91131
brunoerg:
crACK bb91131
Tree-SHA512: 9734fe022c9e43fd93c23a917770d332dbbd3132c80a234059714c32faa6469391e59349954749fc86c4ef0b18d5fd99bf8f4b7b82d9f799943799c1253272ae
Currently the RPC method implementations have many issues:
if.Fix all issues by adding default helper that can be called via
self.Arg<int>(0). The helper needs a few lines of code in thesrc/rpc/util.hheader file. Everything else will be implemented in the cpp file once and if an RPC method needs it.There is also an
self.MaybeArg<int>(0)helper that works on any arg to return the argument, the default, or a falsy value.