Skip to content

Commit 19865a8

Browse files
committed
Merge bitcoin/bitcoin#29277: RPC: access RPC arguments by name
30a6c99 rpc: access some args by name (stickies-v) bbb3126 rpc: add named arg helper (stickies-v) 13525e0 rpc: add arg helper unit test (stickies-v) Pull request description: Adds string overloads for the `RPCHelpMan::Arg` and `RPCHelpMan::MaybeArg` helpers to be able to access RPC arguments by name instead of index number. Especially in RPCs with a large number of parameters, this can be quite helpful. Example usage: ```cpp const auto action{self.Arg<std::string>("action")}; ``` Most of the LoC is adding test coverage and documentation updates. No behaviour change. An alternative approach to #27788 with significantly less overhaul. ACKs for top commit: fjahr: Code review ACK 30a6c99 maflcko: ACK 30a6c99 🥑 ryanofsky: Code review ACK 30a6c99. Nice change! Implementation is surprisingly simple and additional unit test coverage is welcome, too. Tree-SHA512: 4904f5f914fe1d421d32f60edb7c5a028c8ea0f140a2f207a106b4752d441164e073066a6bf2e17693f859fe847815a96609d3cf521e0ac4178d8cd09362ea3d
2 parents 0c45d73 + 30a6c99 commit 19865a8

File tree

8 files changed

+149
-29
lines changed

8 files changed

+149
-29
lines changed

src/rpc/blockchain.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,15 +2174,16 @@ static RPCHelpMan scantxoutset()
21742174
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
21752175
{
21762176
UniValue result(UniValue::VOBJ);
2177-
if (request.params[0].get_str() == "status") {
2177+
const auto action{self.Arg<std::string>("action")};
2178+
if (action == "status") {
21782179
CoinsViewScanReserver reserver;
21792180
if (reserver.reserve()) {
21802181
// no scan in progress
21812182
return UniValue::VNULL;
21822183
}
21832184
result.pushKV("progress", g_scan_progress.load());
21842185
return result;
2185-
} else if (request.params[0].get_str() == "abort") {
2186+
} else if (action == "abort") {
21862187
CoinsViewScanReserver reserver;
21872188
if (reserver.reserve()) {
21882189
// reserve was possible which means no scan was running
@@ -2191,7 +2192,7 @@ static RPCHelpMan scantxoutset()
21912192
// set the abort flag
21922193
g_should_abort_scan = true;
21932194
return true;
2194-
} else if (request.params[0].get_str() == "start") {
2195+
} else if (action == "start") {
21952196
CoinsViewScanReserver reserver;
21962197
if (!reserver.reserve()) {
21972198
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
@@ -2260,7 +2261,7 @@ static RPCHelpMan scantxoutset()
22602261
result.pushKV("unspents", unspents);
22612262
result.pushKV("total_amount", ValueFromAmount(total_in));
22622263
} else {
2263-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
2264+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action));
22642265
}
22652266
return result;
22662267
},

src/rpc/mining.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ static RPCHelpMan getnetworkhashps()
122122
{
123123
ChainstateManager& chainman = EnsureAnyChainman(request.context);
124124
LOCK(cs_main);
125-
return GetNetworkHashPS(self.Arg<int>(0), self.Arg<int>(1), chainman.ActiveChain());
125+
return GetNetworkHashPS(self.Arg<int>("nblocks"), self.Arg<int>("height"), chainman.ActiveChain());
126126
},
127127
};
128128
}
@@ -229,12 +229,12 @@ static RPCHelpMan generatetodescriptor()
229229
"\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
230230
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
231231
{
232-
const auto num_blocks{self.Arg<int>(0)};
233-
const auto max_tries{self.Arg<uint64_t>(2)};
232+
const auto num_blocks{self.Arg<int>("num_blocks")};
233+
const auto max_tries{self.Arg<uint64_t>("maxtries")};
234234

235235
CScript coinbase_script;
236236
std::string error;
237-
if (!getScriptFromDescriptor(self.Arg<std::string>(1), coinbase_script, error)) {
237+
if (!getScriptFromDescriptor(self.Arg<std::string>("descriptor"), coinbase_script, error)) {
238238
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
239239
}
240240

src/rpc/net.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ static RPCHelpMan addnode()
322322
},
323323
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
324324
{
325-
const std::string command{request.params[1].get_str()};
325+
const auto command{self.Arg<std::string>("command")};
326326
if (command != "onetry" && command != "add" && command != "remove") {
327327
throw std::runtime_error(
328328
self.ToString());
@@ -331,9 +331,9 @@ static RPCHelpMan addnode()
331331
NodeContext& node = EnsureAnyNodeContext(request.context);
332332
CConnman& connman = EnsureConnman(node);
333333

334-
const std::string node_arg{request.params[0].get_str()};
334+
const auto node_arg{self.Arg<std::string>("node")};
335335
bool node_v2transport = connman.GetLocalServices() & NODE_P2P_V2;
336-
bool use_v2transport = self.MaybeArg<bool>(2).value_or(node_v2transport);
336+
bool use_v2transport = self.MaybeArg<bool>("v2transport").value_or(node_v2transport);
337337

338338
if (use_v2transport && !node_v2transport) {
339339
throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: v2transport requested but not enabled (see -v2transport)");

src/rpc/signmessage.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ static RPCHelpMan verifymessage()
3838
},
3939
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
4040
{
41-
std::string strAddress = request.params[0].get_str();
42-
std::string strSign = request.params[1].get_str();
43-
std::string strMessage = request.params[2].get_str();
41+
std::string strAddress = self.Arg<std::string>("address");
42+
std::string strSign = self.Arg<std::string>("signature");
43+
std::string strMessage = self.Arg<std::string>("message");
4444

4545
switch (MessageVerify(strAddress, strSign, strMessage)) {
4646
case MessageVerificationResult::ERR_INVALID_ADDRESS:

src/rpc/util.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <util/string.h>
2525
#include <util/translation.h>
2626

27+
#include <algorithm>
28+
#include <iterator>
2729
#include <string_view>
2830
#include <tuple>
2931

@@ -729,6 +731,16 @@ std::vector<std::pair<std::string, bool>> RPCHelpMan::GetArgNames() const
729731
return ret;
730732
}
731733

734+
size_t RPCHelpMan::GetParamIndex(std::string_view key) const
735+
{
736+
auto it{std::find_if(
737+
m_args.begin(), m_args.end(), [&key](const auto& arg) { return arg.GetName() == key;}
738+
)};
739+
740+
CHECK_NONFATAL(it != m_args.end()); // TODO: ideally this is checked at compile time
741+
return std::distance(m_args.begin(), it);
742+
}
743+
732744
std::string RPCHelpMan::ToString() const
733745
{
734746
std::string ret;

src/rpc/util.h

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -404,18 +404,25 @@ class RPCHelpMan
404404

405405
UniValue HandleRequest(const JSONRPCRequest& request) const;
406406
/**
407-
* Helper to get a request argument.
408-
* This function only works during m_fun(), i.e. it should only be used in
409-
* RPC method implementations. The helper internally checks whether the
410-
* user-passed argument isNull() and parses (from JSON) and returns the
411-
* user-passed argument, or the default value derived from the RPCArg
412-
* documentation, or a falsy value if no default was given.
407+
* @brief Helper to get a required or default-valued request argument.
413408
*
414-
* Use Arg<Type>(i) to get the argument or its default value. Otherwise,
415-
* use MaybeArg<Type>(i) to get the optional argument or a falsy value.
409+
* Use this function when the argument is required or when it has a default value. If the
410+
* argument is optional and may not be provided, use MaybeArg instead.
416411
*
417-
* The Type passed to this helper must match the corresponding
418-
* RPCArg::Type.
412+
* This function only works during m_fun(), i.e., it should only be used in
413+
* RPC method implementations. It internally checks whether the user-passed
414+
* argument isNull() and parses (from JSON) and returns the user-passed argument,
415+
* or the default value derived from the RPCArg documentation.
416+
*
417+
* There are two overloads of this function:
418+
* - Use Arg<Type>(size_t i) to get the argument (or the default value) by index.
419+
* - Use Arg<Type>(const std::string& key) to get the argument (or the default value) by key.
420+
*
421+
* The Type passed to this helper must match the corresponding RPCArg::Type.
422+
*
423+
* @return The value of the RPC argument (or the default value) cast to type Type.
424+
*
425+
* @see MaybeArg for handling optional arguments without default values.
419426
*/
420427
template <typename R>
421428
auto Arg(size_t i) const
@@ -429,6 +436,34 @@ class RPCHelpMan
429436
return ArgValue<const R&>(i);
430437
}
431438
}
439+
template<typename R>
440+
auto Arg(std::string_view key) const
441+
{
442+
return Arg<R>(GetParamIndex(key));
443+
}
444+
/**
445+
* @brief Helper to get an optional request argument.
446+
*
447+
* Use this function when the argument is optional and does not have a default value. If the
448+
* argument is required or has a default value, use Arg instead.
449+
*
450+
* This function only works during m_fun(), i.e., it should only be used in
451+
* RPC method implementations. It internally checks whether the user-passed
452+
* argument isNull() and parses (from JSON) and returns the user-passed argument,
453+
* or a falsy value if no argument was passed.
454+
*
455+
* There are two overloads of this function:
456+
* - Use MaybeArg<Type>(size_t i) to get the optional argument by index.
457+
* - Use MaybeArg<Type>(const std::string& key) to get the optional argument by key.
458+
*
459+
* The Type passed to this helper must match the corresponding RPCArg::Type.
460+
*
461+
* @return For integral and floating-point types, a std::optional<Type> is returned.
462+
* For other types, a Type* pointer to the argument is returned. If the
463+
* argument is not provided, std::nullopt or a null pointer is returned.
464+
*
465+
* @see Arg for handling arguments that are required or have a default value.
466+
*/
432467
template <typename R>
433468
auto MaybeArg(size_t i) const
434469
{
@@ -441,6 +476,11 @@ class RPCHelpMan
441476
return ArgValue<const R*>(i);
442477
}
443478
}
479+
template<typename R>
480+
auto MaybeArg(std::string_view key) const
481+
{
482+
return MaybeArg<R>(GetParamIndex(key));
483+
}
444484
std::string ToString() const;
445485
/** Return the named args that need to be converted from string to another JSON type */
446486
UniValue GetArgMap() const;
@@ -460,6 +500,8 @@ class RPCHelpMan
460500
mutable const JSONRPCRequest* m_req{nullptr}; // A pointer to the request for the duration of m_fun()
461501
template <typename R>
462502
R ArgValue(size_t i) const;
503+
//! Return positional index of a parameter using its name as key.
504+
size_t GetParamIndex(std::string_view key) const;
463505
};
464506

465507
/**

src/test/rpc_tests.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,4 +582,72 @@ BOOST_AUTO_TEST_CASE(help_example)
582582
BOOST_CHECK_NE(HelpExampleRpcNamed("foo", {{"arg", true}}), HelpExampleRpcNamed("foo", {{"arg", "true"}}));
583583
}
584584

585+
static void CheckRpc(const std::vector<RPCArg>& params, const UniValue& args, RPCHelpMan::RPCMethodImpl test_impl)
586+
{
587+
auto null_result{RPCResult{RPCResult::Type::NONE, "", "None"}};
588+
const RPCHelpMan rpc{"dummy", "dummy description", params, null_result, RPCExamples{""}, test_impl};
589+
JSONRPCRequest req;
590+
req.params = args;
591+
592+
rpc.HandleRequest(req);
593+
}
594+
595+
BOOST_AUTO_TEST_CASE(rpc_arg_helper)
596+
{
597+
constexpr bool DEFAULT_BOOL = true;
598+
constexpr auto DEFAULT_STRING = "default";
599+
constexpr uint64_t DEFAULT_UINT64_T = 3;
600+
601+
//! Parameters with which the RPCHelpMan is instantiated
602+
const std::vector<RPCArg> params{
603+
// Required arg
604+
{"req_int", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
605+
{"req_str", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
606+
// Default arg
607+
{"def_uint64_t", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_UINT64_T}, ""},
608+
{"def_string", RPCArg::Type::STR, RPCArg::Default{DEFAULT_STRING}, ""},
609+
{"def_bool", RPCArg::Type::BOOL, RPCArg::Default{DEFAULT_BOOL}, ""},
610+
// Optional arg without default
611+
{"opt_double", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, ""},
612+
{"opt_string", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}
613+
};
614+
615+
//! Check that `self.Arg` returns the same value as the `request.params` accessors
616+
RPCHelpMan::RPCMethodImpl check_positional = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
617+
BOOST_CHECK_EQUAL(self.Arg<int>(0), request.params[0].getInt<int>());
618+
BOOST_CHECK_EQUAL(self.Arg<std::string>(1), request.params[1].get_str());
619+
BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), request.params[2].isNull() ? DEFAULT_UINT64_T : request.params[2].getInt<uint64_t>());
620+
BOOST_CHECK_EQUAL(self.Arg<std::string>(3), request.params[3].isNull() ? DEFAULT_STRING : request.params[3].get_str());
621+
BOOST_CHECK_EQUAL(self.Arg<bool>(4), request.params[4].isNull() ? DEFAULT_BOOL : request.params[4].get_bool());
622+
if (!request.params[5].isNull()) {
623+
BOOST_CHECK_EQUAL(self.MaybeArg<double>(5).value(), request.params[5].get_real());
624+
} else {
625+
BOOST_CHECK(!self.MaybeArg<double>(5));
626+
}
627+
if (!request.params[6].isNull()) {
628+
BOOST_CHECK(self.MaybeArg<std::string>(6));
629+
BOOST_CHECK_EQUAL(*self.MaybeArg<std::string>(6), request.params[6].get_str());
630+
} else {
631+
BOOST_CHECK(!self.MaybeArg<std::string>(6));
632+
}
633+
return UniValue{};
634+
};
635+
CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_positional);
636+
CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_positional);
637+
638+
//! Check that `self.Arg` returns the same value when using index and key
639+
RPCHelpMan::RPCMethodImpl check_named = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
640+
BOOST_CHECK_EQUAL(self.Arg<int>(0), self.Arg<int>("req_int"));
641+
BOOST_CHECK_EQUAL(self.Arg<std::string>(1), self.Arg<std::string>("req_str"));
642+
BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), self.Arg<uint64_t>("def_uint64_t"));
643+
BOOST_CHECK_EQUAL(self.Arg<std::string>(3), self.Arg<std::string>("def_string"));
644+
BOOST_CHECK_EQUAL(self.Arg<bool>(4), self.Arg<bool>("def_bool"));
645+
BOOST_CHECK(self.MaybeArg<double>(5) == self.MaybeArg<double>("opt_double"));
646+
BOOST_CHECK(self.MaybeArg<std::string>(6) == self.MaybeArg<std::string>("opt_string"));
647+
return UniValue{};
648+
};
649+
CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_named);
650+
CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_named);
651+
}
652+
585653
BOOST_AUTO_TEST_SUITE_END()

src/wallet/rpc/coins.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,12 @@ RPCHelpMan getbalance()
194194

195195
LOCK(pwallet->cs_wallet);
196196

197-
const auto dummy_value{self.MaybeArg<std::string>(0)};
197+
const auto dummy_value{self.MaybeArg<std::string>("dummy")};
198198
if (dummy_value && *dummy_value != "*") {
199199
throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
200200
}
201201

202-
int min_depth = 0;
203-
if (!request.params[1].isNull()) {
204-
min_depth = request.params[1].getInt<int>();
205-
}
202+
const auto min_depth{self.Arg<int>("minconf")};
206203

207204
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
208205

0 commit comments

Comments
 (0)