Skip to content

Conversation

promag
Copy link
Contributor

@promag promag commented Sep 25, 2020

This change allows to do

[&](const RPCContext& ctx) -> UniValue

instead of

[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue

So RPCContext ties RPCHelpMan and JSONRPCRequest. Then ctx is used to get actual parameter values. For instance, before:

int timeout = 0;
if (!request.params[0].isNull())	
    timeout = request.params[0].get_int();

and now

int timeout = ctx.param<int>(0);

Not that the default value defined in the RPC spec is used.

It is also possible to iterate an array parameter:

    std::set<std::string> stats;
    ctx.param(1).forEach([&](const UniValue& stat) {
        stats.insert(stat.get_str());
    });

Or even do custom parameter handling:

    int verbosity = ctx.param(1, [](const UniValue& param) -> int {
        if (param.isNull()) return 1;
        if (param.isNum()) return param.get_int();
        return param.get_bool() ? 1 : 0;
    });

@promag
Copy link
Contributor Author

promag commented Sep 25, 2020

Here's more goals of this change:

  • get param by name, like ctx.param<bool>("involves_watchonly") and have it with the correct format and make it non-breaking change,
  • try to use the default value specified in RPCArg (Done ✓)
  • support chunked response - don't rely on the return UniValue
  • this simplifies code review as request.param is no longer used in the new variant
  • detect unused arguments

@MarcoFalke @kallewoof

@kallewoof
Copy link
Contributor

Concept ACK

I think this looks nicer btw: kallewoof@fe349cf

@promag
Copy link
Contributor Author

promag commented Sep 30, 2020

@kallewoof thanks for taking a look! I'll wait for more concept acks before going forward. Regarding your suggestion to use operator(), I tend to see it used for functors. In this case, to get param values, it's not that expressiv.

@kallewoof
Copy link
Contributor

kallewoof commented Sep 30, 2020

Yeah, it may be confusing too. I do think we can afford to use one function that takes a null default, though. Overhead negligible. Gotcha on the concept ACKs.

Edit: alternatively, operator[] for the no default case, and ParamOrDefault() for the default case..

Edit again: I dug some more, and it doesn't seem like the RPCHelpMan or JSONRPCRequest are being deallocated, but some memory violation is happening, for sure. Anyway, will get back when we know if this is the way.

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 30, 2020

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25093 (rpc: Check for omitted, but required parameters by MarcoFalke)

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.

@promag promag force-pushed the 2020-09-rpcontext branch 2 times, most recently from 1a07390 to 871487c Compare October 1, 2020 00:58
@promag
Copy link
Contributor Author

promag commented Oct 1, 2020

Updated with IMO a better API. @ryanofsky care to take a look too?

@kallewoof
Copy link
Contributor

@promag Nice. I can't figure out what was wrong with the old code. Care to explain?

@DrahtBot DrahtBot mentioned this pull request Oct 1, 2020
@promag
Copy link
Contributor Author

promag commented Oct 1, 2020

@promag Nice. I can't figure out what was wrong with the old code. Care to explain?

Before fun was captured by reference, and it kabooms when called.

    RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCLegacyMethodImpl fun)
        : RPCHelpMan(std::move(name), std::move(description), std::move(args), std::move(results), std::move(examples), [&](const RPCContext& ctx) -> UniValue {
            return fun(ctx.m_helpman, ctx.m_request);
        })
    {}

@promag promag marked this pull request as ready for review October 1, 2020 07:52
@maflcko
Copy link
Member

maflcko commented Oct 1, 2020

Concept ACK. Would be nice if the default was already filled in by rpc help man. ;)

@promag
Copy link
Contributor Author

promag commented Oct 1, 2020

@MarcoFalke yes, it was already suggested in #20017 (comment) but I think that can be done next.

I also wonder if this should be an exhaustive change, at the moment I'm inclined to do it.

@maflcko
Copy link
Member

maflcko commented Oct 1, 2020

The benefit of doing it in one step would be to avoid changing the same line of code twice

@promag promag force-pushed the 2020-09-rpcontext branch from 01a1554 to 95453d7 Compare October 1, 2020 17:00
@promag
Copy link
Contributor Author

promag commented Oct 1, 2020

See last commit @MarcoFalke.

@promag
Copy link
Contributor Author

promag commented Apr 19, 2021

Rebased now that #21679 is merged.

Not sure what is the best approach going forward, whether this should be an exhaustive change or not. I've included a commit to just change getnetworkhashps RPC to serve as example.

@promag promag force-pushed the 2020-09-rpcontext branch 2 times, most recently from 97bc6af to f473a17 Compare April 19, 2021 10:34
@promag promag marked this pull request as ready for review April 20, 2021 00:30
@maflcko
Copy link
Member

maflcko commented Apr 21, 2021

Concept ACK

Copy link
Contributor

@kallewoof kallewoof left a comment

Choose a reason for hiding this comment

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

reACK f473a17

@LarryRuane
Copy link
Contributor

reACK f473a17

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.

Concept ACK

src/rpc/util.h Outdated
Comment on lines 400 to 404
class RPCContext
{
public:
const RPCHelpMan& helpman;
const JSONRPCRequest& request;
Copy link
Member

Choose a reason for hiding this comment

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

6204785: It looks like you are using this like a struct (without m_ prefix and scoped access ctx.request), so it could make sense to make it a struct. Also, with RPCHelpMan being renamed to RPCMethod (#19386 (comment)), it could make sese to name the variable appropriately:

Suggested change
class RPCContext
{
public:
const RPCHelpMan& helpman;
const JSONRPCRequest& request;
struct RPCContext {
const RPCHelpMan& method;
const JSONRPCRequest& request;

src/rpc/util.h Outdated
CHECK_NONFATAL(m_arg.m_fallback.index() != 2);
m_defaults = defaults;
return *this;
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand how any of this works. I removed this and everything compiled just fine:

diff --git a/src/rpc/util.h b/src/rpc/util.h
index f7a1acd1b5..1d5b7fc318 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -348,11 +348,6 @@ struct RPCParam
     const RPCArg& m_arg;
     const UniValue& m_value;
     std::optional<T> m_defaults = {};
-    RPCParam<T>& defaults(const T& defaults) {
-        CHECK_NONFATAL(m_arg.m_fallback.index() != 2);
-        m_defaults = defaults;
-        return *this;
-    }
     T get() const {
         if (!m_value.isNull()) return value(m_value);
         if (m_defaults) return *m_defaults;

I think it would make sense to split this into at least two commits:

  • First one to introduce RPCContext
  • Second one to introduce RPCParam

Finally, would be nice to use clang-format on new code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Then maybe remove the dead, (not even compiled) code?

Copy link
Member

Choose a reason for hiding this comment

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

@promag are you still working on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’ll rebase and remove dead code.

@promag
Copy link
Contributor Author

promag commented Sep 30, 2021

@MarcoFalke updated, I think fail is unrelated.

@fanquake
Copy link
Member

Concept ACK

Copy link
Contributor

@ajtowns ajtowns 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

bool isNum() const { return value.isNum(); }
bool isStr() const { return value.isStr(); }
template <typename F>
void forEach(F fn) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to use UpperCamelCase?

}
}
private:
T convert(const UniValue& value) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Parameter name shadows member variable. Shouldn't this method be static anyway?

T get() const {
if (!value.isNull()) return convert(value);
// if (m_defaults) return *m_defaults;
CHECK_NONFATAL(arg.m_fallback.index() == 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

CHECK_NONFATAL(std::holds_alternative<Default>(arg.m_fallback)) ?

@@ -365,6 +395,25 @@ class RPCHelpMan
const std::vector<RPCArg> m_args;
const RPCResults m_results;
const RPCExamples m_examples;
friend struct RPCContext;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than having a friend accessing this class's privates directly, why not have RPCHelpMan be able to create a parameter directly and just call that from RPCContext:

class RPCHelpMan
{
    ...
    template <typename T>
    RPCParam<T> GetParam(size_t index, const JSONRPCRequest& request) const
    {
        return {m_args[index], request.params[index]};
    }
    ...
};

class RPCContext
{
...
    template <typename T=UniValue>
    RPCParam<T> param(size_t index) const
    {
        return method.GetParam<T>(index, request);
    }
};

template <typename T=UniValue>
RPCParam<T> param(size_t index) const
{
return {method.m_args[index], request.params[index]};
Copy link
Contributor

Choose a reason for hiding this comment

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

CHECK_NONFATAL(index < m_args.size()); ?

Copy link
Contributor

Choose a reason for hiding this comment

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

When constructing an RPCParam<T> wouldn't it be worth checking that T aligns with method.m_args[index].m_type along similar lines to RPCResult::MatchesType ?

auto param(size_t index, F fn) const -> decltype(fn(UniValue{}))
{
return fn(request.params[index]);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would you call ctx.param(i, fn) instead of fn(ctx.param(i)) -- ctx.param(i) defaults to being UniValue and automatically converts to UniValue, no?

const RPCArg& arg;
const UniValue& value;
T get() const {
if (!value.isNull()) return convert(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

if constexpr (std::is_same_v<T, UniValue>()) return convert(value);

would allow you to get a VNULL value out cleanly if you're treating the param as UniValue

@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft".

@achow101
Copy link
Member

Are you still working on this?

@maflcko
Copy link
Member

maflcko commented Aug 7, 2023

I took the concept from here (Thanks!), but implemented it differently (in just three lines of code in the header file). See #28230

vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Apr 22, 2024
bee56c7 rpc: Check default value type againts argument type (João Barbosa)
f81ef43 rpc: Keep default argument value in correct type (João Barbosa)

Pull request description:

  Store default values of RPC arguments in the corresponding type instead of a string. The value is then serialized when the help output is needed. This change simplifies bitcoin#20017.

  The following examples illustrates how to use the new `RPCArg::Default` and `RPCArg::DefaultHint`:

  ```diff
  - {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}
  + {"verbose", RPCArg::Type::BOOL, RPCArg::Default(false), "True for a json object, false for array of transaction ids"}
  ```

  ```diff
  - {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}
  + {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint("one month"), "Size of the window in number of blocks"}
  ```

  No behavior change is expected.

ACKs for top commit:
  LarryRuane:
    ACK bee56c7
  MarcoFalke:
    ACK bee56c7 🦅

Tree-SHA512: c47d78c918e996d36631d4ad3c933b270a34c5b446b8d736be94cf4a0a7b8c0e33d954149ec786cf9550639865b79deb6a130ad044de6030f95aac33f524293a
vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Jul 6, 2024
bee56c7 rpc: Check default value type againts argument type (João Barbosa)
f81ef43 rpc: Keep default argument value in correct type (João Barbosa)

Pull request description:

  Store default values of RPC arguments in the corresponding type instead of a string. The value is then serialized when the help output is needed. This change simplifies bitcoin#20017.

  The following examples illustrates how to use the new `RPCArg::Default` and `RPCArg::DefaultHint`:

  ```diff
  - {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}
  + {"verbose", RPCArg::Type::BOOL, RPCArg::Default(false), "True for a json object, false for array of transaction ids"}
  ```

  ```diff
  - {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}
  + {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint("one month"), "Size of the window in number of blocks"}
  ```

  No behavior change is expected.

ACKs for top commit:
  LarryRuane:
    ACK bee56c7
  MarcoFalke:
    ACK bee56c7 🦅

Tree-SHA512: c47d78c918e996d36631d4ad3c933b270a34c5b446b8d736be94cf4a0a7b8c0e33d954149ec786cf9550639865b79deb6a130ad044de6030f95aac33f524293a
vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Jul 6, 2024
bee56c7 rpc: Check default value type againts argument type (João Barbosa)
f81ef43 rpc: Keep default argument value in correct type (João Barbosa)

Pull request description:

  Store default values of RPC arguments in the corresponding type instead of a string. The value is then serialized when the help output is needed. This change simplifies bitcoin#20017.

  The following examples illustrates how to use the new `RPCArg::Default` and `RPCArg::DefaultHint`:

  ```diff
  - {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}
  + {"verbose", RPCArg::Type::BOOL, RPCArg::Default(false), "True for a json object, false for array of transaction ids"}
  ```

  ```diff
  - {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}
  + {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint("one month"), "Size of the window in number of blocks"}
  ```

  No behavior change is expected.

ACKs for top commit:
  LarryRuane:
    ACK bee56c7
  MarcoFalke:
    ACK bee56c7 🦅

Tree-SHA512: c47d78c918e996d36631d4ad3c933b270a34c5b446b8d736be94cf4a0a7b8c0e33d954149ec786cf9550639865b79deb6a130ad044de6030f95aac33f524293a
@bitcoin bitcoin locked and limited conversation to collaborators Aug 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants