Skip to content

Commit 21af5af

Browse files
Merge dashpay#6051: refactor: proper support for composite commands such as 'bls generate'
9413ecd fix: disable linter 'check-rpc-mapping.py' for composite commands (Konstantin Akimov) f4bc19f refactor: proper support for composite commands such as 'bls generate' (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented We have composite commands such as 'bls generate' that do not exist in bitcoin's implementation of rpc. It doesn't let to backport yet bitcoin#18531 which enforced extra checks for arguments name (name of rpc and list arguments in rpc help and actual implementation must match). ## What was done? This PR improves support of composite commands in Dash Core. New style of composite commands are applied for `bls` composite commands: `bls generate` and `bls fromsecret` as proof of concept. Once this PR is merged, I will provide similar fixes for other "compose" rpc commands: `protx`, `masternode` (and everything else if any). Beside better validation of arguments and command names, it improves suggest menu in Qt app (see a screenshot) for composite commands. ![image](https://github.com/dashpay/dash/assets/545784/08dcc0b4-df92-4090-b163-af498bf200ef) ## How Has This Been Tested? Run unit and functional tests. Also extra tests to conduct in qt app: - check suggest for 'bls ....' and 'help bls ...' - check output of next commands: ``` help bls help bls generate help bls from secret bls bls generate bls generate 1 ``` - also let's see that old-fashion composite commands are not broken: ``` help protx help protx diff protx diff 00000021c7604b9992254f9f1ed91de5d65eaade33c773abea63a7b0e93293ee 000000e44f9894838ebf768b464177cfce8859dcf92b0509f5c2fba774315996 ``` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: PastaPastaPasta: utACK 9413ecd Tree-SHA512: c8accd2f1ea1d2168d7a2bf25311c92b7839782fc300c587c7537880df5d3fbbf7e79cedb2c6fa88ab067b696994beaf7d92185ae3e64fe1a5f70ad9e8620bec
2 parents 8f2a460 + 9413ecd commit 21af5af

File tree

9 files changed

+98
-57
lines changed

9 files changed

+98
-57
lines changed

src/interfaces/node.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class Node
253253
virtual UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) = 0;
254254

255255
//! List rpc commands.
256-
virtual std::vector<std::string> listRpcCommands() = 0;
256+
virtual std::vector<std::pair<std::string, std::string>> listRpcCommands() = 0;
257257

258258
//! Set RPC timer interface if unset.
259259
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;

src/node/interfaces.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ class NodeImpl : public Node
506506
req.URI = uri;
507507
return ::tableRPC.execute(req);
508508
}
509-
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
509+
std::vector<std::pair<std::string, std::string>> listRpcCommands() override { return ::tableRPC.listCommands(); }
510510
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
511511
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
512512
bool getUnspentOutput(const COutPoint& output, Coin& coin) override
@@ -710,14 +710,14 @@ class RpcHandlerImpl : public Handler
710710
throw;
711711
}
712712
};
713-
::tableRPC.appendCommand(m_command.name, &m_command);
713+
::tableRPC.appendCommand(m_command.name, m_command.subname, &m_command);
714714
}
715715

716716
void disconnect() override final
717717
{
718718
if (m_wrapped_command) {
719719
m_wrapped_command = nullptr;
720-
::tableRPC.removeCommand(m_command.name, &m_command);
720+
::tableRPC.removeCommand(m_command.name, m_command.subname, &m_command);
721721
}
722722
}
723723

src/qt/rpcconsole.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -752,11 +752,15 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
752752

753753
//Setup autocomplete and attach it
754754
QStringList wordList;
755-
std::vector<std::string> commandList = m_node.listRpcCommands();
755+
std::vector<std::pair<std::string, std::string>> commandList = m_node.listRpcCommands();
756756
for (size_t i = 0; i < commandList.size(); ++i)
757757
{
758-
wordList << commandList[i].c_str();
759-
wordList << ("help " + commandList[i]).c_str();
758+
std::string command = commandList[i].first;
759+
if (!commandList[i].second.empty()) {
760+
command = command + " " + commandList[i].second;
761+
}
762+
wordList << command.c_str();
763+
wordList << ("help " + command).c_str();
760764
}
761765

762766
wordList << "help-console";

src/rpc/evo.cpp

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,9 +1706,9 @@ static UniValue protx(const JSONRPCRequest& request)
17061706
}
17071707
}
17081708

1709-
static void bls_generate_help(const JSONRPCRequest& request)
1709+
static RPCHelpMan bls_generate()
17101710
{
1711-
RPCHelpMan{"bls generate",
1711+
return RPCHelpMan{"bls generate",
17121712
"\nReturns a BLS secret/public key pair.\n",
17131713
{
17141714
{"legacy", RPCArg::Type::BOOL, /* default */ "true until the v19 fork is activated, otherwise false", "Use legacy BLS scheme"},
@@ -1723,12 +1723,10 @@ static void bls_generate_help(const JSONRPCRequest& request)
17231723
RPCExamples{
17241724
HelpExampleCli("bls generate", "")
17251725
},
1726-
}.Check(request);
1727-
}
1728-
1729-
static UniValue bls_generate(const JSONRPCRequest& request, const ChainstateManager& chainman)
1726+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
17301727
{
1731-
bls_generate_help(request);
1728+
const NodeContext& node = EnsureAnyNodeContext(request.context);
1729+
const ChainstateManager& chainman = EnsureChainman(node);
17321730

17331731
CBLSSecretKey sk;
17341732
sk.MakeNewKey();
@@ -1742,11 +1740,13 @@ static UniValue bls_generate(const JSONRPCRequest& request, const ChainstateMana
17421740
std::string bls_scheme_str = bls_legacy_scheme ? "legacy" : "basic";
17431741
ret.pushKV("scheme", bls_scheme_str);
17441742
return ret;
1743+
},
1744+
};
17451745
}
17461746

1747-
static void bls_fromsecret_help(const JSONRPCRequest& request)
1747+
static RPCHelpMan bls_fromsecret()
17481748
{
1749-
RPCHelpMan{"bls fromsecret",
1749+
return RPCHelpMan{"bls fromsecret",
17501750
"\nParses a BLS secret key and returns the secret/public key pair.\n",
17511751
{
17521752
{"secret", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLS secret key"},
@@ -1762,12 +1762,10 @@ static void bls_fromsecret_help(const JSONRPCRequest& request)
17621762
RPCExamples{
17631763
HelpExampleCli("bls fromsecret", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
17641764
},
1765-
}.Check(request);
1766-
}
1767-
1768-
static UniValue bls_fromsecret(const JSONRPCRequest& request, const ChainstateManager& chainman)
1765+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
17691766
{
1770-
bls_fromsecret_help(request);
1767+
const NodeContext& node = EnsureAnyNodeContext(request.context);
1768+
const ChainstateManager& chainman = EnsureChainman(node);
17711769

17721770
bool bls_legacy_scheme{!DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)};
17731771
if (!request.params[1].isNull()) {
@@ -1780,11 +1778,13 @@ static UniValue bls_fromsecret(const JSONRPCRequest& request, const ChainstateMa
17801778
std::string bls_scheme_str = bls_legacy_scheme ? "legacy" : "basic";
17811779
ret.pushKV("scheme", bls_scheme_str);
17821780
return ret;
1781+
},
1782+
};
17831783
}
17841784

1785-
[[ noreturn ]] static void bls_help()
1785+
static RPCHelpMan bls_help()
17861786
{
1787-
RPCHelpMan{"bls",
1787+
return RPCHelpMan{"bls",
17881788
"Set of commands to execute BLS related actions.\n"
17891789
"To get help on individual commands, use \"help bls command\".\n"
17901790
"\nAvailable commands:\n"
@@ -1795,35 +1795,26 @@ static UniValue bls_fromsecret(const JSONRPCRequest& request, const ChainstateMa
17951795
},
17961796
RPCResults{},
17971797
RPCExamples{""},
1798-
}.Throw();
1799-
}
1800-
1801-
static UniValue _bls(const JSONRPCRequest& request)
1798+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
18021799
{
1803-
const JSONRPCRequest new_request{request.strMethod == "bls" ? request.squashed() : request};
1804-
const std::string command{new_request.strMethod};
1805-
1806-
const ChainstateManager& chainman = EnsureAnyChainman(request.context);
1807-
1808-
if (command == "blsgenerate") {
1809-
return bls_generate(new_request, chainman);
1810-
} else if (command == "blsfromsecret") {
1811-
return bls_fromsecret(new_request, chainman);
1812-
} else {
1813-
bls_help();
1814-
}
1800+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
1801+
},
1802+
};
18151803
}
1804+
18161805
void RegisterEvoRPCCommands(CRPCTable &tableRPC)
18171806
{
18181807
// clang-format off
18191808
static const CRPCCommand commands[] =
18201809
{ // category name actor (function)
18211810
// --------------------- ------------------------ -----------------------
1822-
{ "evo", "bls", &_bls, {} },
1811+
{ "evo", "bls", &bls_help, {"command"} },
1812+
{ "evo", "bls", "generate", &bls_generate, {"legacy"} },
1813+
{ "evo", "bls", "fromsecret", &bls_fromsecret, {"secret", "legacy"} },
18231814
{ "evo", "protx", &protx, {} },
18241815
};
18251816
// clang-format on
18261817
for (const auto& command : commands) {
1827-
tableRPC.appendCommand(command.name, &command);
1818+
tableRPC.appendCommand(command.name, command.subname, &command);
18281819
}
18291820
}

src/rpc/server.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ std::string CRPCTable::help(const std::string& strCommand, const std::string& st
9292
std::vector<std::pair<std::string, const CRPCCommand*> > vCommands;
9393

9494
for (const auto& entry : mapCommands)
95-
vCommands.push_back(make_pair(entry.second.front()->category + entry.first, entry.second.front()));
95+
vCommands.push_back(make_pair(entry.second.front()->category + entry.first.first + entry.first.second, entry.second.front()));
9696
sort(vCommands.begin(), vCommands.end());
9797

9898
JSONRPCRequest jreq = helpreq;
@@ -105,6 +105,9 @@ std::string CRPCTable::help(const std::string& strCommand, const std::string& st
105105
std::string strMethod = pcmd->name;
106106
if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand)
107107
continue;
108+
109+
if (strSubCommand != pcmd->subname) continue;
110+
108111
jreq.strMethod = strMethod;
109112
try
110113
{
@@ -294,15 +297,20 @@ CRPCTable::CRPCTable()
294297
}
295298

296299
void CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd)
300+
{
301+
appendCommand(name, "", pcmd);
302+
}
303+
304+
void CRPCTable::appendCommand(const std::string& name, const std::string& subname, const CRPCCommand* pcmd)
297305
{
298306
CHECK_NONFATAL(!IsRPCRunning()); // Only add commands before rpc is running
299307

300-
mapCommands[name].push_back(pcmd);
308+
mapCommands[std::make_pair(name, subname)].push_back(pcmd);
301309
}
302310

303-
bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd)
311+
bool CRPCTable::removeCommand(const std::string& name, const std::string& subname, const CRPCCommand* pcmd)
304312
{
305-
auto it = mapCommands.find(name);
313+
auto it = mapCommands.find(std::make_pair(name, subname));
306314
if (it != mapCommands.end()) {
307315
auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd);
308316
if (it->second.end() != new_end) {
@@ -498,12 +506,22 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
498506
throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus);
499507
}
500508

509+
std::string subcommand;
510+
if (request.params.size() > 0 && request.params[0].isStr()) {
511+
subcommand = request.params[0].get_str();
512+
}
513+
501514
// Find method
502-
auto it = mapCommands.find(request.strMethod);
515+
auto it = mapCommands.find(std::make_pair(request.strMethod, subcommand));
516+
if (it == mapCommands.end() && !subcommand.empty()) {
517+
subcommand = "";
518+
it = mapCommands.find(std::make_pair(request.strMethod, subcommand));
519+
}
503520
if (it != mapCommands.end()) {
504521
UniValue result;
505522
for (const auto& command : it->second) {
506-
if (ExecuteCommand(*command, request, result, &command == &it->second.back(), mapPlatformRestrictions)) {
523+
const JSONRPCRequest new_request{subcommand.empty() ? request : request.squashed() };
524+
if (ExecuteCommand(*command, new_request, result, &command == &it->second.back(), mapPlatformRestrictions)) {
507525
return result;
508526
}
509527
}
@@ -591,9 +609,9 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req
591609
}
592610
}
593611

594-
std::vector<std::string> CRPCTable::listCommands() const
612+
std::vector<std::pair<std::string, std::string>> CRPCTable::listCommands() const
595613
{
596-
std::vector<std::string> commandList;
614+
std::vector<std::pair<std::string, std::string>> commandList;
597615
for (const auto& i : mapCommands) commandList.emplace_back(i.first);
598616
return commandList;
599617
}

src/rpc/server.h

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ class CRPCCommand
9595
using Actor = std::function<bool(const JSONRPCRequest& request, UniValue& result, bool last_handler)>;
9696

9797
//! Constructor taking Actor callback supporting multiple handlers.
98-
CRPCCommand(std::string category, std::string name, Actor actor, std::vector<std::string> args, intptr_t unique_id)
99-
: category(std::move(category)), name(std::move(name)), actor(std::move(actor)), argNames(std::move(args)),
98+
CRPCCommand(std::string category, std::string name, std::string subname, Actor actor, std::vector<std::string> args, intptr_t unique_id)
99+
: category(std::move(category)), name(std::move(name)), subname(subname), actor(std::move(actor)), argNames(std::move(args)),
100100
unique_id(unique_id)
101101
{
102102
}
@@ -106,6 +106,7 @@ class CRPCCommand
106106
: CRPCCommand(
107107
category,
108108
fn().m_name,
109+
"",
109110
[fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn().HandleRequest(request); return true; },
110111
fn().GetArgNames(),
111112
intptr_t(fn))
@@ -114,16 +115,36 @@ class CRPCCommand
114115
CHECK_NONFATAL(fn().GetArgNames() == args_in);
115116
}
116117

118+
//! Simplified constructor taking plain RpcMethodFnType function pointer with sub-command.
119+
CRPCCommand(std::string category, std::string name_in, std::string subname_in, RpcMethodFnType fn, std::vector<std::string> args_in)
120+
: CRPCCommand(
121+
category,
122+
name_in,
123+
subname_in,
124+
[fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn().HandleRequest(request); return true; },
125+
fn().GetArgNames(),
126+
intptr_t(fn))
127+
{
128+
if (subname_in.empty()) {
129+
CHECK_NONFATAL(fn().m_name == name_in);
130+
} else {
131+
CHECK_NONFATAL(fn().m_name == name_in + " " + subname_in);
132+
}
133+
134+
CHECK_NONFATAL(fn().GetArgNames() == args_in);
135+
}
136+
117137
//! Simplified constructor taking plain rpcfn_type function pointer.
118138
CRPCCommand(const char* category, const char* name, rpcfn_type fn, std::initializer_list<const char*> args)
119-
: CRPCCommand(category, name,
139+
: CRPCCommand(category, name, "",
120140
[fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn(request); return true; },
121141
{args.begin(), args.end()}, intptr_t(fn))
122142
{
123143
}
124144

125145
std::string category;
126146
std::string name;
147+
std::string subname;
127148
Actor actor;
128149
std::vector<std::string> argNames;
129150
intptr_t unique_id;
@@ -135,7 +156,7 @@ class CRPCCommand
135156
class CRPCTable
136157
{
137158
private:
138-
std::map<std::string, std::vector<const CRPCCommand*>> mapCommands;
159+
std::map<std::pair<std::string, std::string>, std::vector<const CRPCCommand*>> mapCommands;
139160
std::multimap<std::string, std::vector<UniValue>> mapPlatformRestrictions;
140161
public:
141162
CRPCTable();
@@ -155,7 +176,7 @@ class CRPCTable
155176
* Returns a list of registered commands
156177
* @returns List of registered commands.
157178
*/
158-
std::vector<std::string> listCommands() const;
179+
std::vector<std::pair<std::string, std::string>> listCommands() const;
159180

160181
/**
161182
* Appends a CRPCCommand to the dispatch table.
@@ -170,7 +191,8 @@ class CRPCTable
170191
* register different names, types, and numbers of parameters.
171192
*/
172193
void appendCommand(const std::string& name, const CRPCCommand* pcmd);
173-
bool removeCommand(const std::string& name, const CRPCCommand* pcmd);
194+
void appendCommand(const std::string& name, const std::string& subname, const CRPCCommand* pcmd);
195+
bool removeCommand(const std::string& name, const std::string& subname, const CRPCCommand* pcmd);
174196
};
175197

176198
bool IsDeprecatedRPCEnabled(const std::string& method);

src/test/rpc_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ UniValue RPCTestingSetup::TransformParams(const UniValue& params, std::vector<st
4949
{
5050
UniValue transformed_params;
5151
CRPCTable table;
52-
CRPCCommand command{"category", "method", [&](const JSONRPCRequest& request, UniValue&, bool) -> bool { transformed_params = request.params; return true; }, arg_names, /*unique_id=*/0};
52+
CRPCCommand command{"category", "method", "subcommand", [&](const JSONRPCRequest& request, UniValue&, bool) -> bool { transformed_params = request.params; return true; }, arg_names, /*unique_id=*/0};
5353
table.appendCommand("method", &command);
5454
CoreContext context{m_node};
5555
JSONRPCRequest request(context);

src/wallet/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ class WalletLoaderImpl : public WalletLoader
572572
void registerRpcs() override
573573
{
574574
for (const CRPCCommand& command : GetWalletRPCCommands()) {
575-
m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
575+
m_rpc_commands.emplace_back(command.category, command.name, command.subname, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
576576
return command.actor({request, m_context}, result, last_handler);
577577
}, command.argNames, command.unique_id);
578578
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));

test/lint/check-rpc-mappings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ def process_commands(fname):
6060
in_rpcs = False
6161
elif '{' in line and '"' in line:
6262
m = re.search(r'{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line)
63+
# that's a quick fix for composite command
64+
# no proper implementation is needed so far as this linter would be removed soon with bitcoin#20012
65+
if not m:
66+
m = re.search(r'{ *("[^"]*"), *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line)
67+
if m:
68+
continue
6369
assert m, 'No match to table expression: %s' % line
6470
name = parse_string(m.group(2))
6571
args_str = m.group(4).strip()

0 commit comments

Comments
 (0)