Skip to content

Commit 804ca26

Browse files
author
MarcoFalke
committed
Merge bitcoin#19386: rpc: Assert that RPCArg names are equal to CRPCCommand ones (server)
fa7592b rpc: Update server to use new RPCHelpMan (MarcoFalke) aaaaad5 rpc: Add option to hide RPCArg (MarcoFalke) fa9708f rpc: Assert that passed arg names are equal to hardcoded ones (MarcoFalke) faaeb2b rpc: Add CRPCCommand constructor which takes RPCHelpMan (MarcoFalke) fa8ec00 rpc: Check that left section is not multiline (MarcoFalke) Pull request description: This is split out from bitcoin#18531 to just touch the RPC methods in server. Description from the main pr: ### Motivation RPCArg names in the rpc help are currently only used for documentation. However, in the future they could be used to teach the server the named arguments. Named arguments are currently registered by the `CRPCCommand`s and duplicate the RPCArg names from the documentation. This redundancy is fragile, and has lead to errors in the past (despite having linters to catch those kind of errors). See section "bugs found" for a list of bugs that have been found as a result of the changes here. ### Changes The changes here add an assert in the `CRPCCommand` constructor that the RPCArg names are identical to the ones in the `CRPCCommand`. ### Future work > Here or follow up, makes sense to also assert type of returned UniValue? Sure, but let's not get ahead of ourselves. I am going to submit any further works as follow-ups, including: * Removing the CRPCCommand arguments, now that they are asserted to be equal and thus redundant * Removing all python regex linters on the args, now that RPCMan can be used to generate any output, including the cli.cpp table * Auto-formatting and sanity checking the RPCExamples with RPCMan * Checking passed-in json in self-check. Removing redundant checks * Checking returned json against documentation to avoid regressions or false documentation * Compile the RPC documentation at compile-time to ensure it doesn't change at runtime and is completely static ### Bugs found * The assert identified issue bitcoin#18607 * The changes itself fixed bug bitcoin#19250 ACKs for top commit: laanwj: ACK fa7592b ryanofsky: Code review ACK fa7592b. Looks great! Just some hidden arg and Check() and comment cleanups since last review Tree-SHA512: e64b6a212f4a3aeedeee47557559bde104d5fd40cdc1746b27eb2f3d4c8885d5e6e4dd287595ea11cdbc6a939654fe103cae765fd505875444d851f0abb11308
2 parents d626a3b + fa7592b commit 804ca26

File tree

4 files changed

+85
-45
lines changed

4 files changed

+85
-45
lines changed

src/rpc/server.cpp

+27-23
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,9 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
130130
return strRet;
131131
}
132132

133-
UniValue help(const JSONRPCRequest& jsonRequest)
133+
static RPCHelpMan help()
134134
{
135-
if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
136-
throw std::runtime_error(
137-
RPCHelpMan{"help",
135+
return RPCHelpMan{"help",
138136
"\nList all commands, or get help for a specified command.\n",
139137
{
140138
{"command", RPCArg::Type::STR, /* default */ "all commands", "The command to get help on"},
@@ -143,44 +141,46 @@ UniValue help(const JSONRPCRequest& jsonRequest)
143141
RPCResult::Type::STR, "", "The help text"
144142
},
145143
RPCExamples{""},
146-
}.ToString()
147-
);
148-
144+
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
145+
{
149146
std::string strCommand;
150147
if (jsonRequest.params.size() > 0)
151148
strCommand = jsonRequest.params[0].get_str();
152149

153150
return tableRPC.help(strCommand, jsonRequest);
151+
},
152+
};
154153
}
155154

156-
157-
UniValue stop(const JSONRPCRequest& jsonRequest)
155+
static RPCHelpMan stop()
158156
{
159157
static const std::string RESULT{PACKAGE_NAME " stopping"};
160-
// Accept the deprecated and ignored 'detach' boolean argument
158+
return RPCHelpMan{"stop",
161159
// Also accept the hidden 'wait' integer argument (milliseconds)
162160
// For instance, 'stop 1000' makes the call wait 1 second before returning
163161
// to the client (intended for testing)
164-
if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
165-
throw std::runtime_error(
166-
RPCHelpMan{"stop",
167162
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
168-
{},
163+
{
164+
{"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
165+
},
169166
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
170167
RPCExamples{""},
171-
}.ToString());
168+
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
169+
{
172170
// Event loop will exit after current HTTP requests have been handled, so
173171
// this reply will get back to the client.
174172
StartShutdown();
175173
if (jsonRequest.params[0].isNum()) {
176174
UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()});
177175
}
178176
return RESULT;
177+
},
178+
};
179179
}
180180

181-
static UniValue uptime(const JSONRPCRequest& jsonRequest)
181+
static RPCHelpMan uptime()
182182
{
183-
RPCHelpMan{"uptime",
183+
return RPCHelpMan{"uptime",
184184
"\nReturns the total uptime of the server.\n",
185185
{},
186186
RPCResult{
@@ -190,14 +190,16 @@ static UniValue uptime(const JSONRPCRequest& jsonRequest)
190190
HelpExampleCli("uptime", "")
191191
+ HelpExampleRpc("uptime", "")
192192
},
193-
}.Check(jsonRequest);
194-
193+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
194+
{
195195
return GetTime() - GetStartupTime();
196196
}
197+
};
198+
}
197199

198-
static UniValue getrpcinfo(const JSONRPCRequest& request)
200+
static RPCHelpMan getrpcinfo()
199201
{
200-
RPCHelpMan{"getrpcinfo",
202+
return RPCHelpMan{"getrpcinfo",
201203
"\nReturns details of the RPC server.\n",
202204
{},
203205
RPCResult{
@@ -217,8 +219,8 @@ static UniValue getrpcinfo(const JSONRPCRequest& request)
217219
RPCExamples{
218220
HelpExampleCli("getrpcinfo", "")
219221
+ HelpExampleRpc("getrpcinfo", "")},
220-
}.Check(request);
221-
222+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
223+
{
222224
LOCK(g_rpc_server_info.mutex);
223225
UniValue active_commands(UniValue::VARR);
224226
for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
@@ -237,6 +239,8 @@ static UniValue getrpcinfo(const JSONRPCRequest& request)
237239

238240
return result;
239241
}
242+
};
243+
}
240244

241245
// clang-format off
242246
static const CRPCCommand vRPCCommands[] =

src/rpc/server.h

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <amount.h>
1010
#include <rpc/request.h>
11+
#include <rpc/util.h>
1112

1213
#include <functional>
1314
#include <map>
@@ -85,6 +86,7 @@ void RPCUnsetTimerInterface(RPCTimerInterface *iface);
8586
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds);
8687

8788
typedef UniValue(*rpcfn_type)(const JSONRPCRequest& jsonRequest);
89+
typedef RPCHelpMan (*RpcMethodFnType)();
8890

8991
class CRPCCommand
9092
{
@@ -101,6 +103,19 @@ class CRPCCommand
101103
{
102104
}
103105

106+
//! Simplified constructor taking plain RpcMethodFnType function pointer.
107+
CRPCCommand(std::string category, std::string name_in, RpcMethodFnType fn, std::vector<std::string> args_in)
108+
: CRPCCommand(
109+
category,
110+
fn().m_name,
111+
[fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn().HandleRequest(request); return true; },
112+
fn().GetArgNames(),
113+
intptr_t(fn))
114+
{
115+
CHECK_NONFATAL(fn().m_name == name_in);
116+
CHECK_NONFATAL(fn().GetArgNames() == args_in);
117+
}
118+
104119
//! Simplified constructor taking plain rpcfn_type function pointer.
105120
CRPCCommand(const char* category, const char* name, rpcfn_type fn, std::initializer_list<const char*> args)
106121
: CRPCCommand(category, name,
@@ -117,7 +132,7 @@ class CRPCCommand
117132
};
118133

119134
/**
120-
* Bitcoin RPC command dispatcher.
135+
* RPC command dispatcher.
121136
*/
122137
class CRPCTable
123138
{

src/rpc/util.cpp

+25-19
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,7 @@ struct Sections {
385385
PushSection({indent + "]" + (outer_type != OuterType::NONE ? "," : ""), ""});
386386
break;
387387
}
388-
389-
// no default case, so the compiler can warn about missing cases
390-
}
388+
} // no default case, so the compiler can warn about missing cases
391389
}
392390

393391
/**
@@ -398,6 +396,9 @@ struct Sections {
398396
std::string ret;
399397
const size_t pad = m_max_pad + 4;
400398
for (const auto& s : m_sections) {
399+
// The left part of a section is assumed to be a single line, usually it is the name of the JSON struct or a
400+
// brace like {, }, [, or ]
401+
CHECK_NONFATAL(s.m_left.find('\n') == std::string::npos);
401402
if (s.m_right.empty()) {
402403
ret += s.m_left;
403404
ret += "\n";
@@ -432,7 +433,11 @@ struct Sections {
432433
};
433434

434435
RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples)
436+
: RPCHelpMan{std::move(name), std::move(description), std::move(args), std::move(results), std::move(examples), nullptr} {}
437+
438+
RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun)
435439
: m_name{std::move(name)},
440+
m_fun{std::move(fun)},
436441
m_description{std::move(description)},
437442
m_args{std::move(args)},
438443
m_results{std::move(results)},
@@ -481,6 +486,16 @@ bool RPCHelpMan::IsValidNumArgs(size_t num_args) const
481486
}
482487
return num_required_args <= num_args && num_args <= m_args.size();
483488
}
489+
490+
std::vector<std::string> RPCHelpMan::GetArgNames() const
491+
{
492+
std::vector<std::string> ret;
493+
for (const auto& arg : m_args) {
494+
ret.emplace_back(arg.m_names);
495+
}
496+
return ret;
497+
}
498+
484499
std::string RPCHelpMan::ToString() const
485500
{
486501
std::string ret;
@@ -489,6 +504,7 @@ std::string RPCHelpMan::ToString() const
489504
ret += m_name;
490505
bool was_optional{false};
491506
for (const auto& arg : m_args) {
507+
if (arg.m_hidden) continue;
492508
const bool optional = arg.IsOptional();
493509
ret += " ";
494510
if (optional) {
@@ -510,6 +526,7 @@ std::string RPCHelpMan::ToString() const
510526
Sections sections;
511527
for (size_t i{0}; i < m_args.size(); ++i) {
512528
const auto& arg = m_args.at(i);
529+
if (arg.m_hidden) continue;
513530

514531
if (i == 0) ret += "\nArguments:\n";
515532

@@ -589,9 +606,7 @@ std::string RPCArg::ToDescriptionString() const
589606
ret += "json array";
590607
break;
591608
}
592-
593-
// no default case, so the compiler can warn about missing cases
594-
}
609+
} // no default case, so the compiler can warn about missing cases
595610
}
596611
if (m_fallback.which() == 1) {
597612
ret += ", optional, default=" + boost::get<std::string>(m_fallback);
@@ -609,9 +624,7 @@ std::string RPCArg::ToDescriptionString() const
609624
ret += ", required";
610625
break;
611626
}
612-
613-
// no default case, so the compiler can warn about missing cases
614-
}
627+
} // no default case, so the compiler can warn about missing cases
615628
}
616629
ret += ")";
617630
ret += m_description.empty() ? "" : " " + m_description;
@@ -706,10 +719,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
706719
sections.PushSection({indent + "}" + maybe_separator, ""});
707720
return;
708721
}
709-
710-
// no default case, so the compiler can warn about missing cases
711-
}
712-
722+
} // no default case, so the compiler can warn about missing cases
713723
CHECK_NONFATAL(false);
714724
}
715725

@@ -746,9 +756,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const
746756
case Type::OBJ_USER_KEYS:
747757
// Currently unused, so avoid writing dead code
748758
CHECK_NONFATAL(false);
749-
750-
// no default case, so the compiler can warn about missing cases
751-
}
759+
} // no default case, so the compiler can warn about missing cases
752760
CHECK_NONFATAL(false);
753761
}
754762

@@ -783,9 +791,7 @@ std::string RPCArg::ToString(const bool oneline) const
783791
}
784792
return "[" + res + "...]";
785793
}
786-
787-
// no default case, so the compiler can warn about missing cases
788-
}
794+
} // no default case, so the compiler can warn about missing cases
789795
CHECK_NONFATAL(false);
790796
}
791797

src/rpc/util.h

+17-2
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ struct RPCArg {
147147
using Fallback = boost::variant<Optional, /* default value for optional args */ std::string>;
148148
const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments)
149149
const Type m_type;
150+
const bool m_hidden;
150151
const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts
151152
const Fallback m_fallback;
152153
const std::string m_description;
@@ -159,9 +160,11 @@ struct RPCArg {
159160
const Fallback fallback,
160161
const std::string description,
161162
const std::string oneline_description = "",
162-
const std::vector<std::string> type_str = {})
163+
const std::vector<std::string> type_str = {},
164+
const bool hidden = false)
163165
: m_names{std::move(name)},
164166
m_type{std::move(type)},
167+
m_hidden{hidden},
165168
m_fallback{std::move(fallback)},
166169
m_description{std::move(description)},
167170
m_oneline_description{std::move(oneline_description)},
@@ -180,6 +183,7 @@ struct RPCArg {
180183
const std::vector<std::string> type_str = {})
181184
: m_names{std::move(name)},
182185
m_type{std::move(type)},
186+
m_hidden{false},
183187
m_inner{std::move(inner)},
184188
m_fallback{std::move(fallback)},
185189
m_description{std::move(description)},
@@ -329,8 +333,15 @@ class RPCHelpMan
329333
{
330334
public:
331335
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples);
336+
using RPCMethodImpl = std::function<UniValue(const RPCHelpMan&, const JSONRPCRequest&)>;
337+
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun);
332338

333339
std::string ToString() const;
340+
UniValue HandleRequest(const JSONRPCRequest& request)
341+
{
342+
Check(request);
343+
return m_fun(*this, request);
344+
}
334345
/** If the supplied number of args is neither too small nor too high */
335346
bool IsValidNumArgs(size_t num_args) const;
336347
/**
@@ -343,8 +354,12 @@ class RPCHelpMan
343354
}
344355
}
345356

346-
private:
357+
std::vector<std::string> GetArgNames() const;
358+
347359
const std::string m_name;
360+
361+
private:
362+
const RPCMethodImpl m_fun;
348363
const std::string m_description;
349364
const std::vector<RPCArg> m_args;
350365
const RPCResults m_results;

0 commit comments

Comments
 (0)