rpc: Named argument support for bitcoin-cli

Usage e.g.:

    $ src/bitcoin-cli -testnet -named echo arg0="dfdf"
    [
    "dfdf"
    ]

Argument conversion also works, for arguments thus flagged in the table in
`src/rpc/client.cpp`.

    $ src/bitcoin-cli -testnet -named echojson arg0="[1,2,3]"
    [
      [
        1,
        2,
        3
      ]
    ]

Unknown parameter (detected server-side):

    $ src/bitcoin-cli -testnet -named getinfo arg0="dfdf"
    error code: -8
    error message:
    Unknown named parameter arg0
This commit is contained in:
Wladimir J. van der Laan 2016-11-22 14:56:29 +01:00
parent 9adb4e1a59
commit 481f289765
4 changed files with 156 additions and 93 deletions

View file

@ -25,6 +25,7 @@
static const char DEFAULT_RPCCONNECT[] = "127.0.0.1"; static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
static const bool DEFAULT_NAMED=false;
static const int CONTINUE_EXECUTION=-1; static const int CONTINUE_EXECUTION=-1;
std::string HelpMessageCli() std::string HelpMessageCli()
@ -35,6 +36,7 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME)); strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory")); strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
AppendParamsHelpMessages(strUsage); AppendParamsHelpMessages(strUsage);
strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED));
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT)); strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), BaseParams(CBaseChainParams::MAIN).RPCPort(), BaseParams(CBaseChainParams::TESTNET).RPCPort())); strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), BaseParams(CBaseChainParams::MAIN).RPCPort(), BaseParams(CBaseChainParams::TESTNET).RPCPort()));
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
@ -80,6 +82,7 @@ static int AppInitRPC(int argc, char* argv[])
if (!IsArgSet("-version")) { if (!IsArgSet("-version")) {
strUsage += "\n" + _("Usage:") + "\n" + strUsage += "\n" + _("Usage:") + "\n" +
" bitcoin-cli [options] <command> [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" + " bitcoin-cli [options] <command> [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" +
" bitcoin-cli [options] -named <command> [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" +
" bitcoin-cli [options] help " + _("List commands") + "\n" + " bitcoin-cli [options] help " + _("List commands") + "\n" +
" bitcoin-cli [options] help <command> " + _("Get help for a command") + "\n"; " bitcoin-cli [options] help <command> " + _("Get help for a command") + "\n";
@ -278,7 +281,14 @@ int CommandLineRPC(int argc, char *argv[])
if (args.size() < 1) if (args.size() < 1)
throw std::runtime_error("too few parameters (need at least command)"); throw std::runtime_error("too few parameters (need at least command)");
std::string strMethod = args[0]; std::string strMethod = args[0];
UniValue params = RPCConvertValues(strMethod, std::vector<std::string>(args.begin()+1, args.end())); args.erase(args.begin()); // Remove trailing method name from arguments vector
UniValue params;
if(GetBoolArg("-named", DEFAULT_NAMED)) {
params = RPCConvertNamedValues(strMethod, args);
} else {
params = RPCConvertValues(strMethod, args);
}
// Execute and handle connection failures with -rpcwait // Execute and handle connection failures with -rpcwait
const bool fWait = GetBoolArg("-rpcwait", false); const bool fWait = GetBoolArg("-rpcwait", false);

View file

@ -20,104 +20,120 @@ class CRPCConvertParam
public: public:
std::string methodName; //!< method whose params want conversion std::string methodName; //!< method whose params want conversion
int paramIdx; //!< 0-based idx of param to convert int paramIdx; //!< 0-based idx of param to convert
std::string paramName; //!< parameter name
}; };
/**
* Specifiy a (method, idx, name) here if the argument is a non-string RPC
* argument and needs to be converted from JSON.
*
* @note Parameter indexes start from 0.
*/
static const CRPCConvertParam vRPCConvertParams[] = static const CRPCConvertParam vRPCConvertParams[] =
{ {
{ "stop", 0 }, { "setmocktime", 0, "timestamp" },
{ "setmocktime", 0 }, { "generate", 0, "nblocks" },
{ "generate", 0 }, { "generate", 1, "maxtries" },
{ "generate", 1 }, { "generatetoaddress", 0, "nblocks" },
{ "generatetoaddress", 0 }, { "generatetoaddress", 2, "maxtries" },
{ "generatetoaddress", 2 }, { "getnetworkhashps", 0, "nblocks" },
{ "getnetworkhashps", 0 }, { "getnetworkhashps", 1, "height" },
{ "getnetworkhashps", 1 }, { "sendtoaddress", 1, "amount" },
{ "sendtoaddress", 1 }, { "sendtoaddress", 4, "subtractfeefromamount" },
{ "sendtoaddress", 4 }, { "settxfee", 0, "amount" },
{ "settxfee", 0 }, { "getreceivedbyaddress", 1, "minconf" },
{ "getreceivedbyaddress", 1 }, { "getreceivedbyaccount", 1, "minconf" },
{ "getreceivedbyaccount", 1 }, { "listreceivedbyaddress", 0, "minconf" },
{ "listreceivedbyaddress", 0 }, { "listreceivedbyaddress", 1, "include_empty" },
{ "listreceivedbyaddress", 1 }, { "listreceivedbyaddress", 2, "include_watchonly" },
{ "listreceivedbyaddress", 2 }, { "listreceivedbyaccount", 0, "minconf" },
{ "listreceivedbyaccount", 0 }, { "listreceivedbyaccount", 1, "include_empty" },
{ "listreceivedbyaccount", 1 }, { "listreceivedbyaccount", 2, "include_watchonly" },
{ "listreceivedbyaccount", 2 }, { "getbalance", 1, "minconf" },
{ "getbalance", 1 }, { "getbalance", 2, "include_watchonly" },
{ "getbalance", 2 }, { "getblockhash", 0, "index" },
{ "getblockhash", 0 }, { "waitforblockheight", 0, "height" },
{ "waitforblockheight", 0 }, { "waitforblockheight", 1, "timeout" },
{ "waitforblockheight", 1 }, { "waitforblock", 1, "timeout" },
{ "waitforblock", 1 }, { "waitfornewblock", 0, "timeout" },
{ "waitforblock", 2 }, { "move", 2, "amount" },
{ "waitfornewblock", 0 }, { "move", 3, "minconf" },
{ "waitfornewblock", 1 }, { "sendfrom", 2, "amount" },
{ "move", 2 }, { "sendfrom", 3, "minconf" },
{ "move", 3 }, { "listtransactions", 1, "count" },
{ "sendfrom", 2 }, { "listtransactions", 2, "from" },
{ "sendfrom", 3 }, { "listtransactions", 3, "include_watchonly" },
{ "listtransactions", 1 }, { "listaccounts", 0, "minconf" },
{ "listtransactions", 2 }, { "listaccounts", 1, "include_watchonly" },
{ "listtransactions", 3 }, { "walletpassphrase", 1, "timeout" },
{ "listaccounts", 0 }, { "getblocktemplate", 0, "template_request" },
{ "listaccounts", 1 }, { "listsinceblock", 1, "target_confirmations" },
{ "walletpassphrase", 1 }, { "listsinceblock", 2, "include_watchonly" },
{ "getblocktemplate", 0 }, { "sendmany", 1, "amounts" },
{ "listsinceblock", 1 }, { "sendmany", 2, "minconf" },
{ "listsinceblock", 2 }, { "sendmany", 4, "subtractfeefrom" },
{ "sendmany", 1 }, { "addmultisigaddress", 0, "nrequired" },
{ "sendmany", 2 }, { "addmultisigaddress", 1, "keys" },
{ "sendmany", 4 }, { "createmultisig", 0, "nrequired" },
{ "addmultisigaddress", 0 }, { "createmultisig", 1, "keys" },
{ "addmultisigaddress", 1 }, { "listunspent", 0, "minconf" },
{ "createmultisig", 0 }, { "listunspent", 1, "maxconf" },
{ "createmultisig", 1 }, { "listunspent", 2, "addresses" },
{ "listunspent", 0 }, { "getblock", 1, "verbose" },
{ "listunspent", 1 }, { "getblockheader", 1, "verbose" },
{ "listunspent", 2 }, { "gettransaction", 1, "include_watchonly" },
{ "getblock", 1 }, { "getrawtransaction", 1, "verbose" },
{ "getblockheader", 1 }, { "createrawtransaction", 0, "transactions" },
{ "gettransaction", 1 }, { "createrawtransaction", 1, "outputs" },
{ "getrawtransaction", 1 }, { "createrawtransaction", 2, "locktime" },
{ "createrawtransaction", 0 }, { "signrawtransaction", 1, "prevtxs" },
{ "createrawtransaction", 1 }, { "signrawtransaction", 2, "privkeys" },
{ "createrawtransaction", 2 }, { "sendrawtransaction", 1, "allowhighfees" },
{ "signrawtransaction", 1 }, { "fundrawtransaction", 1, "options" },
{ "signrawtransaction", 2 }, { "gettxout", 1, "n" },
{ "sendrawtransaction", 1 }, { "gettxout", 2, "include_mempool" },
{ "fundrawtransaction", 1 }, { "gettxoutproof", 0, "txids" },
{ "gettxout", 1 }, { "lockunspent", 0, "unlock" },
{ "gettxout", 2 }, { "lockunspent", 1, "transactions" },
{ "gettxoutproof", 0 }, { "importprivkey", 2, "rescan" },
{ "lockunspent", 0 }, { "importaddress", 2, "rescan" },
{ "lockunspent", 1 }, { "importaddress", 3, "p2sh" },
{ "importprivkey", 2 }, { "importpubkey", 2, "rescan" },
{ "importaddress", 2 }, { "importmulti", 0, "requests" },
{ "importaddress", 3 }, { "importmulti", 1, "options" },
{ "importpubkey", 2 }, { "verifychain", 0, "checklevel" },
{ "importmulti", 0 }, { "verifychain", 1, "nblocks" },
{ "importmulti", 1 }, { "keypoolrefill", 0, "newsize" },
{ "verifychain", 0 }, { "getrawmempool", 0, "verbose" },
{ "verifychain", 1 }, { "estimatefee", 0, "nblocks" },
{ "keypoolrefill", 0 }, { "estimatepriority", 0, "nblocks" },
{ "getrawmempool", 0 }, { "estimatesmartfee", 0, "nblocks" },
{ "estimatefee", 0 }, { "estimatesmartpriority", 0, "nblocks" },
{ "estimatepriority", 0 }, { "prioritisetransaction", 1, "priority_delta" },
{ "estimatesmartfee", 0 }, { "prioritisetransaction", 2, "fee_delta" },
{ "estimatesmartpriority", 0 }, { "setban", 2, "bantime" },
{ "prioritisetransaction", 1 }, { "setban", 3, "absolute" },
{ "prioritisetransaction", 2 }, { "setnetworkactive", 0, "state" },
{ "setban", 2 }, { "getmempoolancestors", 1, "verbose" },
{ "setban", 3 }, { "getmempooldescendants", 1, "verbose" },
{ "setnetworkactive", 0 }, // Echo with conversion (For testing only)
{ "getmempoolancestors", 1 }, { "echojson", 0, "arg0" },
{ "getmempooldescendants", 1 }, { "echojson", 1, "arg1" },
{ "echojson", 2, "arg2" },
{ "echojson", 3, "arg3" },
{ "echojson", 4, "arg4" },
{ "echojson", 5, "arg5" },
{ "echojson", 6, "arg6" },
{ "echojson", 7, "arg7" },
{ "echojson", 8, "arg8" },
{ "echojson", 9, "arg9" },
}; };
class CRPCConvertTable class CRPCConvertTable
{ {
private: private:
std::set<std::pair<std::string, int> > members; std::set<std::pair<std::string, int>> members;
std::set<std::pair<std::string, std::string>> membersByName;
public: public:
CRPCConvertTable(); CRPCConvertTable();
@ -125,6 +141,9 @@ public:
bool convert(const std::string& method, int idx) { bool convert(const std::string& method, int idx) {
return (members.count(std::make_pair(method, idx)) > 0); return (members.count(std::make_pair(method, idx)) > 0);
} }
bool convert(const std::string& method, const std::string& name) {
return (membersByName.count(std::make_pair(method, name)) > 0);
}
}; };
CRPCConvertTable::CRPCConvertTable() CRPCConvertTable::CRPCConvertTable()
@ -135,6 +154,8 @@ CRPCConvertTable::CRPCConvertTable()
for (unsigned int i = 0; i < n_elem; i++) { for (unsigned int i = 0; i < n_elem; i++) {
members.insert(std::make_pair(vRPCConvertParams[i].methodName, members.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].paramIdx)); vRPCConvertParams[i].paramIdx));
membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].paramName));
} }
} }
@ -152,7 +173,6 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal)
return jVal[0]; return jVal[0];
} }
/** Convert strings to command-specific RPC representation */
UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{ {
UniValue params(UniValue::VARR); UniValue params(UniValue::VARR);
@ -171,3 +191,28 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::s
return params; return params;
} }
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
UniValue params(UniValue::VOBJ);
for (const std::string &s: strParams) {
size_t pos = s.find("=");
if (pos == std::string::npos) {
throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
}
std::string name = s.substr(0, pos);
std::string value = s.substr(pos+1);
if (!rpcCvtTable.convert(strMethod, name)) {
// insert string value directly
params.pushKV(name, value);
} else {
// parse string as JSON, insert bool/number/object/etc. value
params.pushKV(name, ParseNonRFCJSONValue(value));
}
}
return params;
}

View file

@ -8,7 +8,12 @@
#include <univalue.h> #include <univalue.h>
/** Convert positional arguments to command-specific RPC representation */
UniValue RPCConvertValues(const std::string& strMethod, const std::vector<std::string>& strParams); UniValue RPCConvertValues(const std::string& strMethod, const std::vector<std::string>& strParams);
/** Convert named arguments to command-specific RPC representation */
UniValue RPCConvertNamedValues(const std::string& strMethod, const std::vector<std::string>& strParams);
/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null) /** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
* as well as objects and arrays. * as well as objects and arrays.
*/ */

View file

@ -496,8 +496,10 @@ UniValue echo(const JSONRPCRequest& request)
{ {
if (request.fHelp) if (request.fHelp)
throw runtime_error( throw runtime_error(
"echo \"message\" ...\n" "echo|echojson \"message\" ...\n"
"\nSimply echo back the input arguments\n" "\nSimply echo back the input arguments. This command is for testing.\n"
"\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in"
"bitcoin-cli and the GUI. There is no server-side difference."
); );
return request.params; return request.params;
@ -516,6 +518,7 @@ static const CRPCCommand commands[] =
/* Not shown in help */ /* Not shown in help */
{ "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}},
{ "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echojson", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
}; };
void RegisterMiscRPCCommands(CRPCTable &t) void RegisterMiscRPCCommands(CRPCTable &t)