From eb7a6b08f12e24e4bbf393a2b38ff8a4ac7bcc7c Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 10 Jan 2017 13:52:49 +0100 Subject: [PATCH] Merge #8811: rpc: Add support for JSON-RPC named arguments 4e7e2e1 Update RPC argument names (John Newbery) 481f289 rpc: Named argument support for bitcoin-cli (Wladimir J. van der Laan) 9adb4e1 rpc: Argument name consistency (Wladimir J. van der Laan) 8d713f7 rpc: Named arguments for rawtransaction calls (Wladimir J. van der Laan) 37a166f rpc: Named arguments for wallet calls (Wladimir J. van der Laan) 78b684f rpc: Named arguments for mining calls (Wladimir J. van der Laan) b8ebc59 rpc: Named arguments for net calls (Wladimir J. van der Laan) 2ca9dcd test: Add test for RPC named arguments (Wladimir J. van der Laan) fba1a61 rpc: Named arguments for misc calls (Wladimir J. van der Laan) 286ec08 rpc: Add 'echo' call for testing (Wladimir J. van der Laan) 495eb44 rpc: Named arguments for blockchain calls (Wladimir J. van der Laan) 6f1c76a rpc: Support named arguments (Wladimir J. van der Laan) 5865d41 authproxy: Add support for RPC named arguments (Wladimir J. van der Laan) --- qa/pull-tester/rpc-tests.py | 1 + qa/rpc-tests/rpcnamedargs.py | 52 ++++ qa/rpc-tests/test_framework/authproxy.py | 6 +- qa/rpc-tests/test_framework/util.py | 12 + src/dash-cli.cpp | 12 +- src/rpc/blockchain.cpp | 104 ++++---- src/rpc/client.cpp | 291 +++++++++++++---------- src/rpc/client.h | 5 + src/rpc/governance.cpp | 11 +- src/rpc/masternode.cpp | 16 +- src/rpc/mining.cpp | 58 ++--- src/rpc/misc.cpp | 57 +++-- src/rpc/net.cpp | 32 +-- src/rpc/rawtransaction.cpp | 48 ++-- src/rpc/server.cpp | 65 ++++- src/rpc/server.h | 1 + src/wallet/rpcdump.cpp | 14 +- src/wallet/rpcwallet.cpp | 236 +++++++++--------- 18 files changed, 607 insertions(+), 414 deletions(-) create mode 100755 qa/rpc-tests/rpcnamedargs.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index af771b4a6..974528908 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -160,6 +160,7 @@ testScripts = [ 'importprunedfunds.py', 'signmessages.py', 'import-rescan.py', + 'rpcnamedargs.py', ] if ENABLE_ZMQ: testScripts.append('zmq_test.py') diff --git a/qa/rpc-tests/rpcnamedargs.py b/qa/rpc-tests/rpcnamedargs.py new file mode 100755 index 000000000..048420466 --- /dev/null +++ b/qa/rpc-tests/rpcnamedargs.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright (c) 2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import ( + assert_equal, + assert_raises_jsonrpc, + assert_is_hex_string, + assert_is_hash_string, + start_nodes, + connect_nodes_bi, +) + + +class NamedArgumentTest(BitcoinTestFramework): + """ + Test named arguments on RPC calls. + """ + + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 1 + + def setup_network(self, split=False): + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) + self.is_network_split = False + self.sync_all() + + def run_test(self): + node = self.nodes[0] + h = node.help(command='getinfo') + assert(h.startswith('getinfo\n')) + + assert_raises_jsonrpc(-8, node.help, random='getinfo') + + h = node.getblockhash(height=0) + node.getblock(blockhash=h) + + assert_equal(node.echo(), []) + assert_equal(node.echo(arg0=0,arg9=9), [0] + [None]*8 + [9]) + assert_equal(node.echo(arg1=1), [None, 1]) + assert_equal(node.echo(arg9=None), [None]*10) + assert_equal(node.echo(arg0=0,arg3=3,arg9=9), [0] + [None]*2 + [3] + [None]*5 + [9]) + +if __name__ == '__main__': + NamedArgumentTest().main() diff --git a/qa/rpc-tests/test_framework/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py index 9bee1962e..09ed61129 100644 --- a/qa/rpc-tests/test_framework/authproxy.py +++ b/qa/rpc-tests/test_framework/authproxy.py @@ -138,14 +138,16 @@ class AuthServiceProxy(object): self.__conn.request(method, path, postdata, headers) return self._get_response() - def __call__(self, *args): + def __call__(self, *args, **argsn): AuthServiceProxy.__id_count += 1 log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name, json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) + if args and argsn: + raise ValueError('Cannot handle both named and positional arguments') postdata = json.dumps({'version': '1.1', 'method': self._service_name, - 'params': args, + 'params': args or argsn, 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal, ensure_ascii=self.ensure_ascii) response = self._request('POST', self.__url.path, postdata.encode('utf-8')) if response['error'] is not None: diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index a09188172..77f8c75db 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -570,6 +570,18 @@ def assert_raises_message(exc, message, fun, *args, **kwds): else: raise AssertionError("No exception raised") +def assert_raises_jsonrpc(code, fun, *args, **kwds): + '''Check for specific JSONRPC exception code''' + try: + fun(*args, **kwds) + except JSONRPCException as e: + if e.error["code"] != code: + raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"]) + except Exception as e: + raise AssertionError("Unexpected exception raised: "+type(e).__name__) + else: + raise AssertionError("No exception raised") + def assert_is_hex_string(string): try: int(string, 16) diff --git a/src/dash-cli.cpp b/src/dash-cli.cpp index d89b6a69b..d453c45c4 100644 --- a/src/dash-cli.cpp +++ b/src/dash-cli.cpp @@ -26,6 +26,7 @@ static const char DEFAULT_RPCCONNECT[] = "127.0.0.1"; static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; +static const bool DEFAULT_NAMED=false; static const int CONTINUE_EXECUTION=-1; std::string HelpMessageCli() @@ -36,6 +37,7 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-conf=", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME)); strUsage += HelpMessageOpt("-datadir=", _("Specify data directory")); AppendParamsHelpMessages(strUsage); + strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED)); strUsage += HelpMessageOpt("-rpcconnect=", strprintf(_("Send commands to node running on (default: %s)"), DEFAULT_RPCCONNECT)); strUsage += HelpMessageOpt("-rpcport=", strprintf(_("Connect to JSON-RPC on (default: %u or testnet: %u)"), BaseParams(CBaseChainParams::MAIN).RPCPort(), BaseParams(CBaseChainParams::TESTNET).RPCPort())); strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); @@ -81,6 +83,7 @@ static int AppInitRPC(int argc, char* argv[]) if (!IsArgSet("-version")) { strUsage += "\n" + _("Usage:") + "\n" + " dash-cli [options] [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" + + " dash-cli [options] -named [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" + " dash-cli [options] help " + _("List commands") + "\n" + " dash-cli [options] help " + _("Get help for a command") + "\n"; @@ -284,7 +287,14 @@ int CommandLineRPC(int argc, char *argv[]) if (args.size() < 1) throw std::runtime_error("too few parameters (need at least command)"); std::string strMethod = args[0]; - UniValue params = RPCConvertValues(strMethod, std::vector(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 const bool fWait = GetBoolArg("-rpcwait", false); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index a897154e8..46527f2fe 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -236,8 +236,8 @@ UniValue waitforblock(const JSONRPCRequest& request) "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" - "1. blockhash to wait for (string)\n" - "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" + "1. \"blockhash\" (required, string) Block hash to wait for.\n" + "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" @@ -274,12 +274,12 @@ UniValue waitforblockheight(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw runtime_error( - "waitforblockheight (timeout)\n" + "waitforblockheight (timeout)\n" "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" - "1. block height to wait for (int)\n" + "1. height (required, int) Block height to wait for (int)\n" "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" "\nResult:\n" "{ (json object)\n" @@ -418,7 +418,7 @@ UniValue getrawmempool(const JSONRPCRequest& request) "getrawmempool ( verbose )\n" "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" "\nArguments:\n" - "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" + "1. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" "\nResult: (for verbose = false):\n" "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" @@ -449,8 +449,8 @@ UniValue getmempoolancestors(const JSONRPCRequest& request) "getmempoolancestors txid (verbose)\n" "\nIf txid is in the mempool, returns all in-mempool ancestors.\n" "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" - "2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" + "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" + "2. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" "\nResult (for verbose=false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool ancestor transaction\n" @@ -513,8 +513,8 @@ UniValue getmempooldescendants(const JSONRPCRequest& request) "getmempooldescendants txid (verbose)\n" "\nIf txid is in the mempool, returns all in-mempool descendants.\n" "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" - "2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" + "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" + "2. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" "\nResult (for verbose=false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool descendant transaction\n" @@ -641,10 +641,10 @@ UniValue getblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "getblockhash index\n" - "\nReturns hash of block in best-block-chain at index provided.\n" + "getblockhash height\n" + "\nReturns hash of block in best-block-chain at height provided.\n" "\nArguments:\n" - "1. index (numeric, required) The block index\n" + "1. height (numeric, required) The height index\n" "\nResult:\n" "\"hash\" (string) The block hash\n" "\nExamples:\n" @@ -814,12 +814,12 @@ UniValue getblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw runtime_error( - "getblock \"hash\" ( verbose )\n" + "getblock \"blockhash\" ( verbose )\n" "\nIf verbose is false, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbose is true, returns an Object with information about block .\n" "\nArguments:\n" - "1. \"hash\" (string, required) The block hash\n" - "2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n" + "1. \"blockhash\" (string, required) The block hash\n" + "2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n" "\nResult (for verbose = true):\n" "{\n" " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" @@ -992,12 +992,12 @@ UniValue gettxout(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw runtime_error( - "gettxout \"txid\" n ( includemempool )\n" + "gettxout \"txid\" n ( include_mempool )\n" "\nReturns details about an unspent transaction output.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id\n" "2. n (numeric, required) vout number\n" - "3. includemempool (boolean, optional) Whether to include the mempool\n" + "3. include_mempool (boolean, optional) Whether to include the mempool\n" "\nResult:\n" "{\n" " \"bestblock\" : \"hash\", (string) the block hash\n" @@ -1009,7 +1009,7 @@ UniValue gettxout(const JSONRPCRequest& request) " \"reqSigs\" : n, (numeric) Number of required signatures\n" " \"type\" : \"pubkeyhash\", (string) The type, eg pubkeyhash\n" " \"addresses\" : [ (array of string) array of dash addresses\n" - " \"dashaddress\" (string) dash address\n" + " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" " },\n" @@ -1074,11 +1074,11 @@ UniValue verifychain(const JSONRPCRequest& request) int nCheckDepth = GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); if (request.fHelp || request.params.size() > 2) throw runtime_error( - "verifychain ( checklevel numblocks )\n" + "verifychain ( checklevel nblocks )\n" "\nVerifies blockchain database.\n" "\nArguments:\n" "1. checklevel (numeric, optional, 0-4, default=" + strprintf("%d", nCheckLevel) + ") How thorough the block verification is.\n" - "2. numblocks (numeric, optional, default=" + strprintf("%d", nCheckDepth) + ", 0=all) The number of blocks to check.\n" + "2. nblocks (numeric, optional, default=" + strprintf("%d", nCheckDepth) + ", 0=all) The number of blocks to check.\n" "\nResult:\n" "true|false (boolean) Verified or not\n" "\nExamples:\n" @@ -1416,12 +1416,12 @@ UniValue preciousblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "preciousblock \"hash\"\n" + "preciousblock \"blockhash\"\n" "\nTreats a block as if it were received before others with the same work.\n" "\nA later preciousblock call can override the effect of an earlier one.\n" "\nThe effects of preciousblock are not retained across restarts.\n" "\nArguments:\n" - "1. hash (string, required) the hash of the block to mark as precious\n" + "1. \"blockhash\" (string, required) the hash of the block to mark as precious\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("preciousblock", "\"blockhash\"") @@ -1454,10 +1454,10 @@ UniValue invalidateblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "invalidateblock \"hash\"\n" + "invalidateblock \"blockhash\"\n" "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n" "\nArguments:\n" - "1. hash (string, required) the hash of the block to mark as invalid\n" + "1. \"blockhash\" (string, required) the hash of the block to mark as invalid\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("invalidateblock", "\"blockhash\"") @@ -1492,11 +1492,11 @@ UniValue reconsiderblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "reconsiderblock \"hash\"\n" + "reconsiderblock \"blockhash\"\n" "\nRemoves invalidity status of a block and its descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n" "\nArguments:\n" - "1. hash (string, required) the hash of the block to reconsider\n" + "1. \"blockhash\" (string, required) the hash of the block to reconsider\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("reconsiderblock", "\"blockhash\"") @@ -1526,35 +1526,35 @@ UniValue reconsiderblock(const JSONRPCRequest& request) } static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode - // --------------------- ------------------------ ----------------------- ---------- - { "blockchain", "getblockchaininfo", &getblockchaininfo, true }, - { "blockchain", "getbestblockhash", &getbestblockhash, true }, - { "blockchain", "getblockcount", &getblockcount, true }, - { "blockchain", "getblock", &getblock, true }, - { "blockchain", "getblockhashes", &getblockhashes, true }, - { "blockchain", "getblockhash", &getblockhash, true }, - { "blockchain", "getblockheader", &getblockheader, true }, - { "blockchain", "getblockheaders", &getblockheaders, true }, - { "blockchain", "getchaintips", &getchaintips, true }, - { "blockchain", "getdifficulty", &getdifficulty, true }, - { "blockchain", "getmempoolancestors", &getmempoolancestors, true }, - { "blockchain", "getmempooldescendants", &getmempooldescendants, true }, - { "blockchain", "getmempoolentry", &getmempoolentry, true }, - { "blockchain", "getmempoolinfo", &getmempoolinfo, true }, - { "blockchain", "getrawmempool", &getrawmempool, true }, - { "blockchain", "gettxout", &gettxout, true }, - { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true }, - { "blockchain", "verifychain", &verifychain, true }, +{ // category name actor (function) okSafe argNames + // --------------------- ------------------------ ----------------------- ------ ---------- + { "blockchain", "getblockchaininfo", &getblockchaininfo, true, {} }, + { "blockchain", "getbestblockhash", &getbestblockhash, true, {} }, + { "blockchain", "getblockcount", &getblockcount, true, {} }, + { "blockchain", "getblock", &getblock, true, {"blockhash","verbose"} }, + { "blockchain", "getblockhashes", &getblockhashes, true, {"high","low"} }, + { "blockchain", "getblockhash", &getblockhash, true, {"height"} }, + { "blockchain", "getblockheader", &getblockheader, true, {"blockhash","verbose"} }, + { "blockchain", "getblockheaders", &getblockheaders, true, {"blockhash","count","verbose"} }, + { "blockchain", "getchaintips", &getchaintips, true, {"count","branchlen"} }, + { "blockchain", "getdifficulty", &getdifficulty, true, {} }, + { "blockchain", "getmempoolancestors", &getmempoolancestors, true, {"txid","verbose"} }, + { "blockchain", "getmempooldescendants", &getmempooldescendants, true, {"txid","verbose"} }, + { "blockchain", "getmempoolentry", &getmempoolentry, true, {"txid"} }, + { "blockchain", "getmempoolinfo", &getmempoolinfo, true, {} }, + { "blockchain", "getrawmempool", &getrawmempool, true, {"verbose"} }, + { "blockchain", "gettxout", &gettxout, true, {"txid","n","include_mempool"} }, + { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, {} }, + { "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} }, - { "blockchain", "preciousblock", &preciousblock, true }, + { "blockchain", "preciousblock", &preciousblock, true, {"blockhash"} }, /* Not shown in help */ - { "hidden", "invalidateblock", &invalidateblock, true }, - { "hidden", "reconsiderblock", &reconsiderblock, true }, - { "hidden", "waitfornewblock", &waitfornewblock, true }, - { "hidden", "waitforblock", &waitforblock, true }, - { "hidden", "waitforblockheight", &waitforblockheight, true }, + { "hidden", "invalidateblock", &invalidateblock, true, {"blockhash"} }, + { "hidden", "reconsiderblock", &reconsiderblock, true, {"blockhash"} }, + { "hidden", "waitfornewblock", &waitfornewblock, true, {"timeout"} }, + { "hidden", "waitforblock", &waitforblock, true, {"blockhash","timeout"} }, + { "hidden", "waitforblockheight", &waitforblockheight, true, {"height","timeout"} }, }; void RegisterBlockchainRPCCommands(CRPCTable &t) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index dad90937f..512bd057b 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -21,136 +21,152 @@ class CRPCConvertParam public: std::string methodName; //!< method whose params want conversion 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[] = { - { "stop", 0 }, - { "setmocktime", 0 }, - { "generate", 0 }, - { "generate", 1 }, - { "generatetoaddress", 0 }, - { "generatetoaddress", 2 }, - { "getnetworkhashps", 0 }, - { "getnetworkhashps", 1 }, - { "sendtoaddress", 1 }, - { "sendtoaddress", 4 }, - { "sendtoaddress", 5 }, - { "sendtoaddress", 6 }, - { "instantsendtoaddress", 1 }, - { "instantsendtoaddress", 4 }, - { "settxfee", 0 }, - { "getreceivedbyaddress", 1 }, - { "getreceivedbyaddress", 2 }, - { "getreceivedbyaccount", 1 }, - { "getreceivedbyaccount", 2 }, - { "listreceivedbyaddress", 0 }, - { "listreceivedbyaddress", 1 }, - { "listreceivedbyaddress", 2 }, - { "listreceivedbyaddress", 3 }, - { "listreceivedbyaccount", 0 }, - { "listreceivedbyaccount", 1 }, - { "listreceivedbyaccount", 2 }, - { "listreceivedbyaccount", 3 }, - { "getbalance", 1 }, - { "getbalance", 2 }, - { "getbalance", 3 }, - { "getchaintips", 0 }, - { "getchaintips", 1 }, - { "getblockhash", 0 }, - { "getsuperblockbudget", 0 }, - { "waitforblockheight", 0 }, - { "waitforblockheight", 1 }, - { "waitforblock", 1 }, - { "waitforblock", 2 }, - { "waitfornewblock", 0 }, - { "waitfornewblock", 1 }, - { "move", 2 }, - { "move", 3 }, - { "sendfrom", 2 }, - { "sendfrom", 3 }, - { "sendfrom", 4 }, - { "listtransactions", 1 }, - { "listtransactions", 2 }, - { "listtransactions", 3 }, - { "listaccounts", 0 }, - { "listaccounts", 1 }, - { "listaccounts", 2 }, - { "walletpassphrase", 1 }, - { "walletpassphrase", 2 }, - { "getblocktemplate", 0 }, - { "listsinceblock", 1 }, - { "listsinceblock", 2 }, - { "sendmany", 1 }, - { "sendmany", 2 }, - { "sendmany", 3 }, - { "sendmany", 5 }, - { "sendmany", 6 }, - { "sendmany", 7 }, - { "addmultisigaddress", 0 }, - { "addmultisigaddress", 1 }, - { "createmultisig", 0 }, - { "createmultisig", 1 }, - { "listunspent", 0 }, - { "listunspent", 1 }, - { "listunspent", 2 }, - { "getblock", 1 }, - { "getblockheader", 1 }, - { "getblockheaders", 1 }, - { "getblockheaders", 2 }, - { "gettransaction", 1 }, - { "getrawtransaction", 1 }, - { "createrawtransaction", 0 }, - { "createrawtransaction", 1 }, - { "createrawtransaction", 2 }, - { "signrawtransaction", 1 }, - { "signrawtransaction", 2 }, - { "sendrawtransaction", 1 }, - { "sendrawtransaction", 2 }, - { "fundrawtransaction", 1 }, - { "gettxout", 1 }, - { "gettxout", 2 }, - { "gettxoutproof", 0 }, - { "lockunspent", 0 }, - { "lockunspent", 1 }, - { "importprivkey", 2 }, - { "importelectrumwallet", 1 }, - { "importaddress", 2 }, - { "importaddress", 3 }, - { "importpubkey", 2 }, - { "importmulti", 0 }, - { "importmulti", 1 }, - { "verifychain", 0 }, - { "verifychain", 1 }, - { "keypoolrefill", 0 }, - { "getrawmempool", 0 }, - { "estimatefee", 0 }, - { "estimatepriority", 0 }, - { "estimatesmartfee", 0 }, - { "estimatesmartpriority", 0 }, - { "prioritisetransaction", 1 }, - { "prioritisetransaction", 2 }, - { "setban", 2 }, - { "setban", 3 }, - { "getmempoolancestors", 1 }, - { "getmempooldescendants", 1 }, - { "setnetworkactive", 0 }, - { "spork", 1 }, - { "voteraw", 1 }, - { "voteraw", 5 }, - { "getblockhashes", 0 }, - { "getblockhashes", 1 }, - { "getspentinfo", 0}, - { "getaddresstxids", 0}, - { "getaddressbalance", 0}, - { "getaddressdeltas", 0}, - { "getaddressutxos", 0}, - { "getaddressmempool", 0}, + { "setmocktime", 0, "timestamp" }, + { "generate", 0, "nblocks" }, + { "generate", 1, "maxtries" }, + { "generatetoaddress", 0, "nblocks" }, + { "generatetoaddress", 2, "maxtries" }, + { "getnetworkhashps", 0, "nblocks" }, + { "getnetworkhashps", 1, "height" }, + { "sendtoaddress", 1, "amount" }, + { "sendtoaddress", 4, "subtractfeefromamount" }, + { "sendtoaddress", 5, "use_is" }, + { "sendtoaddress", 6, "use_ps" }, + { "instantsendtoaddress", 1, "address" }, + { "instantsendtoaddress", 4, "comment_to" }, + { "settxfee", 0, "amount" }, + { "getreceivedbyaddress", 1, "minconf" }, + { "getreceivedbyaddress", 2, "addlockconf" }, + { "getreceivedbyaccount", 1, "minconf" }, + { "getreceivedbyaccount", 2, "addlockconf" }, + { "listreceivedbyaddress", 0, "minconf" }, + { "listreceivedbyaddress", 1, "addlockconf" }, + { "listreceivedbyaddress", 2, "include_empty" }, + { "listreceivedbyaddress", 3, "include_watchonly" }, + { "listreceivedbyaccount", 0, "minconf" }, + { "listreceivedbyaccount", 1, "addlockconf" }, + { "listreceivedbyaccount", 2, "include_empty" }, + { "listreceivedbyaccount", 3, "include_watchonly" }, + { "getbalance", 1, "minconf" }, + { "getbalance", 2, "addlockconf" }, + { "getbalance", 3, "include_watchonly" }, + { "getchaintips", 0, "count" }, + { "getchaintips", 1, "branchlen" }, + { "getblockhash", 0, "height" }, + { "getsuperblockbudget", 0, "index" }, + { "waitforblockheight", 0, "height" }, + { "waitforblockheight", 1, "timeout" }, + { "waitforblock", 1, "timeout" }, + { "waitfornewblock", 0, "timeout" }, + { "move", 2, "amount" }, + { "move", 3, "minconf" }, + { "sendfrom", 2, "amount" }, + { "sendfrom", 3, "minconf" }, + { "sendfrom", 4, "addlockconf" }, + { "listtransactions", 1, "count" }, + { "listtransactions", 2, "skip" }, + { "listtransactions", 3, "include_watchonly" }, + { "listaccounts", 0, "minconf" }, + { "listaccounts", 1, "addlockconf" }, + { "listaccounts", 2, "include_watchonly" }, + { "walletpassphrase", 1, "timeout" }, + { "walletpassphrase", 2, "mixingonly" }, + { "getblocktemplate", 0, "template_request" }, + { "listsinceblock", 1, "target_confirmations" }, + { "listsinceblock", 2, "include_watchonly" }, + { "sendmany", 1, "amounts" }, + { "sendmany", 2, "minconf" }, + { "sendmany", 3, "addlockconf" }, + { "sendmany", 5, "subtractfeefromamount" }, + { "sendmany", 6, "use_is" }, + { "sendmany", 7, "use_ps" }, + { "addmultisigaddress", 0, "nrequired" }, + { "addmultisigaddress", 1, "keys" }, + { "createmultisig", 0, "nrequired" }, + { "createmultisig", 1, "keys" }, + { "listunspent", 0, "minconf" }, + { "listunspent", 1, "maxconf" }, + { "listunspent", 2, "addresses" }, + { "getblock", 1, "verbose" }, + { "getblockheader", 1, "verbose" }, + { "getblockheaders", 1, "count" }, + { "getblockheaders", 2, "verbose" }, + { "gettransaction", 1, "include_watchonly" }, + { "getrawtransaction", 1, "verbose" }, + { "createrawtransaction", 0, "transactions" }, + { "createrawtransaction", 1, "outputs" }, + { "createrawtransaction", 2, "locktime" }, + { "signrawtransaction", 1, "prevtxs" }, + { "signrawtransaction", 2, "privkeys" }, + { "sendrawtransaction", 1, "allowhighfees" }, + { "sendrawtransaction", 2, "instantsend" }, + { "fundrawtransaction", 1, "options" }, + { "gettxout", 1, "n" }, + { "gettxout", 2, "include_mempool" }, + { "gettxoutproof", 0, "txids" }, + { "lockunspent", 0, "unlock" }, + { "lockunspent", 1, "transactions" }, + { "importprivkey", 2, "rescan" }, + { "importelectrumwallet", 1, "index" }, + { "importaddress", 2, "rescan" }, + { "importaddress", 3, "p2sh" }, + { "importpubkey", 2, "rescan" }, + { "importmulti", 0, "requests" }, + { "importmulti", 1, "options" }, + { "verifychain", 0, "checklevel" }, + { "verifychain", 1, "nblocks" }, + { "keypoolrefill", 0, "newsize" }, + { "getrawmempool", 0, "verbose" }, + { "estimatefee", 0, "nblocks" }, + { "estimatepriority", 0, "nblocks" }, + { "estimatesmartfee", 0, "nblocks" }, + { "estimatesmartpriority", 0, "nblocks" }, + { "prioritisetransaction", 1, "priority_delta" }, + { "prioritisetransaction", 2, "fee_delta" }, + { "setban", 2, "bantime" }, + { "setban", 3, "absolute" }, + { "setnetworkactive", 0, "state" }, + { "getmempoolancestors", 1, "verbose" }, + { "getmempooldescendants", 1, "verbose" }, + { "spork", 1, "datetime" }, + { "voteraw", 1, "tx_index" }, + { "voteraw", 5, "time" }, + { "getblockhashes", 0, "high"}, + { "getblockhashes", 1, "low" }, + { "getspentinfo", 0, "json" }, + { "getaddresstxids", 0, "addresses" }, + { "getaddressbalance", 0, "addresses" }, + { "getaddressdeltas", 0, "addresses" }, + { "getaddressutxos", 0, "addresses" }, + { "getaddressmempool", 0, "addresses" }, + // Echo with conversion (For testing only) + { "echojson", 0, "arg0" }, + { "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 { private: - std::set > members; + std::set> members; + std::set> membersByName; public: CRPCConvertTable(); @@ -158,6 +174,9 @@ public: bool convert(const std::string& method, int idx) { 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() @@ -168,6 +187,8 @@ CRPCConvertTable::CRPCConvertTable() for (unsigned int i = 0; i < n_elem; i++) { members.insert(std::make_pair(vRPCConvertParams[i].methodName, vRPCConvertParams[i].paramIdx)); + membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName, + vRPCConvertParams[i].paramName)); } } @@ -185,7 +206,6 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal) return jVal[0]; } -/** Convert strings to command-specific RPC representation */ UniValue RPCConvertValues(const std::string &strMethod, const std::vector &strParams) { UniValue params(UniValue::VARR); @@ -204,3 +224,28 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector &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; +} diff --git a/src/rpc/client.h b/src/rpc/client.h index ae015860b..0b9fa648f 100644 --- a/src/rpc/client.h +++ b/src/rpc/client.h @@ -8,7 +8,12 @@ #include +/** Convert positional arguments to command-specific RPC representation */ UniValue RPCConvertValues(const std::string& strMethod, const std::vector& strParams); + +/** Convert named arguments to command-specific RPC representation */ +UniValue RPCConvertNamedValues(const std::string& strMethod, const std::vector& strParams); + /** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null) * as well as objects and arrays. */ diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index 952dad487..23716210c 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -997,12 +997,13 @@ UniValue getsuperblockbudget(const JSONRPCRequest& request) } static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode +{ // category name actor (function) okSafe argNames + // --------------------- ------------------------ ----------------------- ------ ---------- /* Dash features */ - { "dash", "getgovernanceinfo", &getgovernanceinfo, true }, - { "dash", "getsuperblockbudget", &getsuperblockbudget, true }, - { "dash", "gobject", &gobject, true }, - { "dash", "voteraw", &voteraw, true }, + { "dash", "getgovernanceinfo", &getgovernanceinfo, true, {} }, + { "dash", "getsuperblockbudget", &getsuperblockbudget, true, {"index"} }, + { "dash", "gobject", &gobject, true, {} }, + { "dash", "voteraw", &voteraw, true, {} }, }; diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 7c6131d68..8b813027d 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -854,15 +854,15 @@ UniValue sentinelping(const JSONRPCRequest& request) } static const CRPCCommand commands[] = -{ // category name actor (function) okSafeMode - /* Dash features */ - { "dash", "masternode", &masternode, true }, - { "dash", "masternodelist", &masternodelist, true }, - { "dash", "masternodebroadcast", &masternodebroadcast, true }, - { "dash", "getpoolinfo", &getpoolinfo, true }, - { "dash", "sentinelping", &sentinelping, true }, +{ // category name actor (function) okSafe argNames + // --------------------- ------------------------ ----------------------- ------ ---------- + { "dash", "masternode", &masternode, true, {} }, + { "dash", "masternodelist", &masternodelist, true, {} }, + { "dash", "masternodebroadcast", &masternodebroadcast, true, {} }, + { "dash", "getpoolinfo", &getpoolinfo, true, {} }, + { "dash", "sentinelping", &sentinelping, true, {} }, #ifdef ENABLE_WALLET - { "dash", "privatesend", &privatesend, false }, + { "dash", "privatesend", &privatesend, false, {} }, #endif // ENABLE_WALLET }; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 44deaccfa..98e1fb344 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -84,13 +84,13 @@ UniValue getnetworkhashps(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw runtime_error( - "getnetworkhashps ( blocks height )\n" + "getnetworkhashps ( nblocks height )\n" "\nReturns the estimated network hashes per second based on the last n blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change.\n" "Pass in [height] to estimate the network speed at the time when a certain block was found.\n" "\nArguments:\n" - "1. blocks (numeric, optional, default=120) The number of blocks, or -1 for blocks since last difficulty change.\n" - "2. height (numeric, optional, default=-1) To estimate at the time of the given height.\n" + "1. nblocks (numeric, optional, default=120) The number of blocks, or -1 for blocks since last difficulty change.\n" + "2. height (numeric, optional, default=-1) To estimate at the time of the given height.\n" "\nResult:\n" "x (numeric) Hashes per second estimated\n" "\nExamples:\n" @@ -156,10 +156,10 @@ UniValue generate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw runtime_error( - "generate numblocks ( maxtries )\n" - "\nMine up to numblocks blocks immediately (before the RPC call returns)\n" + "generate nblocks ( maxtries )\n" + "\nMine up to nblocks blocks immediately (before the RPC call returns)\n" "\nArguments:\n" - "1. numblocks (numeric, required) How many blocks are generated immediately.\n" + "1. nblocks (numeric, required) How many blocks are generated immediately.\n" "2. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n" "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" @@ -192,11 +192,11 @@ UniValue generatetoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw runtime_error( - "generatetoaddress numblocks address (maxtries)\n" + "generatetoaddress nblocks address (maxtries)\n" "\nMine blocks immediately to a specified address (before the RPC call returns)\n" "\nArguments:\n" - "1. numblocks (numeric, required) How many blocks are generated immediately.\n" - "2. address (string, required) The address to send the newly generated Dash to.\n" + "1. nblocks (numeric, required) How many blocks are generated immediately.\n" + "2. address (string, required) The address to send the newly generated Dash to.\n" "3. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n" "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" @@ -268,10 +268,10 @@ UniValue prioritisetransaction(const JSONRPCRequest& request) "Accepts the transaction into mined blocks at a higher (or lower) priority\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id.\n" - "2. priority delta (numeric, required) The priority to add or subtract.\n" + "2. priority_delta (numeric, required) The priority to add or subtract.\n" " The transaction selection algorithm considers the tx as it would have a higher priority.\n" " (priority of a transaction is calculated: coinage * value_in_duffs / txsize) \n" - "3. fee delta (numeric, required) The fee value (in duffs) to add (or subtract, if negative).\n" + "3. fee_delta (numeric, required) The fee value (in duffs) to add (or subtract, if negative).\n" " The fee is not actually paid, only the algorithm for selecting transactions into a block\n" " considers the transaction as it would have paid a higher (or lower) fee.\n" "\nResult:\n" @@ -332,7 +332,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request) " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" "\nArguments:\n" - "1. TemplateRequest (json object, optional) A json object in the following spec\n" + "1. template_request (json object, optional) A json object in the following spec\n" " {\n" " \"mode\":\"template\" (string, optional) This must be set to \"template\", \"proposal\" (see BIP 23), or omitted\n" " \"capabilities\":[ (array, optional) A list of strings\n" @@ -760,9 +760,9 @@ UniValue submitblock(const JSONRPCRequest& request) "The 'jsonparametersobject' parameter is currently ignored.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n" - "\nArguments:\n" - "1. \"hexdata\" (string, required) the hex-encoded block data to submit\n" - "2. \"jsonparametersobject\" (string, optional) object of optional parameters\n" + "\nArguments\n" + "1. \"hexdata\" (string, required) the hex-encoded block data to submit\n" + "2. \"parameters\" (string, optional) object of optional parameters\n" " {\n" " \"workid\" : \"id\" (string, optional) if the server provided a workid, it MUST be included with submissions\n" " }\n" @@ -816,7 +816,7 @@ UniValue estimatefee(const JSONRPCRequest& request) "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within nblocks blocks.\n" "\nArguments:\n" - "1. nblocks (numeric)\n" + "1. nblocks (numeric, required)\n" "\nResult:\n" "n (numeric) estimated fee-per-kilobyte\n" "\n" @@ -849,7 +849,7 @@ UniValue estimatepriority(const JSONRPCRequest& request) "\nDEPRECATED. Estimates the approximate priority a zero-fee transaction needs to begin\n" "confirmation within nblocks blocks.\n" "\nArguments:\n" - "1. nblocks (numeric)\n" + "1. nblocks (numeric, required)\n" "\nResult:\n" "n (numeric) estimated priority\n" "\n" @@ -914,7 +914,7 @@ UniValue estimatesmartpriority(const JSONRPCRequest& request) "confirmation within nblocks blocks if possible and return the number of blocks\n" "for which the estimate is valid.\n" "\nArguments:\n" - "1. nblocks (numeric)\n" + "1. nblocks (numeric, required)\n" "\nResult:\n" "{\n" " \"priority\" : x.x, (numeric) estimated priority\n" @@ -943,19 +943,19 @@ UniValue estimatesmartpriority(const JSONRPCRequest& request) static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- - { "mining", "getnetworkhashps", &getnetworkhashps, true }, - { "mining", "getmininginfo", &getmininginfo, true }, - { "mining", "prioritisetransaction", &prioritisetransaction, true }, - { "mining", "getblocktemplate", &getblocktemplate, true }, - { "mining", "submitblock", &submitblock, true }, + { "mining", "getnetworkhashps", &getnetworkhashps, true, {"nblocks","height"} }, + { "mining", "getmininginfo", &getmininginfo, true, {} }, + { "mining", "prioritisetransaction", &prioritisetransaction, true, {"txid","priority_delta","fee_delta"} }, + { "mining", "getblocktemplate", &getblocktemplate, true, {"template_request"} }, + { "mining", "submitblock", &submitblock, true, {"hexdata","parameters"} }, - { "generating", "generate", &generate, true }, - { "generating", "generatetoaddress", &generatetoaddress, true }, + { "generating", "generate", &generate, true, {"nblocks","maxtries"} }, + { "generating", "generatetoaddress", &generatetoaddress, true, {"nblocks","address","maxtries"} }, - { "util", "estimatefee", &estimatefee, true }, - { "util", "estimatepriority", &estimatepriority, true }, - { "util", "estimatesmartfee", &estimatesmartfee, true }, - { "util", "estimatesmartpriority", &estimatesmartpriority, true }, + { "util", "estimatefee", &estimatefee, true, {"nblocks"} }, + { "util", "estimatepriority", &estimatepriority, true, {"nblocks"} }, + { "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks"} }, + { "util", "estimatesmartpriority", &estimatesmartpriority, true, {"nblocks"} }, }; void RegisterMiningRPCCommands(CRPCTable &t) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 1a0ff1435..354d98c5a 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -281,14 +281,14 @@ UniValue validateaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "validateaddress \"dashaddress\"\n" + "validateaddress \"address\"\n" "\nReturn information about the given dash address.\n" "\nArguments:\n" - "1. \"dashaddress\" (string, required) The dash address to validate\n" + "1. \"address\" (string, required) The dash address to validate\n" "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" - " \"address\" : \"dashaddress\", (string) The dash address validated\n" + " \"address\" : \"address\", (string) The dash address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" @@ -455,10 +455,10 @@ UniValue verifymessage(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw runtime_error( - "verifymessage \"dashaddress\" \"signature\" \"message\"\n" + "verifymessage \"address\" \"signature\" \"message\"\n" "\nVerify a signed message\n" "\nArguments:\n" - "1. \"dashaddress\" (string, required) The dash address to use for the signature.\n" + "1. \"address\" (string, required) The dash address to use for the signature.\n" "2. \"signature\" (string, required) The signature provided by the signer in base 64 encoding (see signmessage).\n" "3. \"message\" (string, required) The message that was signed.\n" "\nResult:\n" @@ -1086,31 +1086,46 @@ UniValue getmemoryinfo(const JSONRPCRequest& request) return obj; } +UniValue echo(const JSONRPCRequest& request) +{ + if (request.fHelp) + throw runtime_error( + "echo|echojson \"message\" ...\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; +} + static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- - { "control", "debug", &debug, true }, - { "control", "getinfo", &getinfo, true }, /* uses wallet if enabled */ - { "control", "getmemoryinfo", &getmemoryinfo, true }, - { "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */ - { "util", "createmultisig", &createmultisig, true }, - { "util", "verifymessage", &verifymessage, true }, - { "util", "signmessagewithprivkey", &signmessagewithprivkey, true }, - { "blockchain", "getspentinfo", &getspentinfo, false }, + { "control", "debug", &debug, true, {} }, + { "control", "getinfo", &getinfo, true, {} }, /* uses wallet if enabled */ + { "control", "getmemoryinfo", &getmemoryinfo, true, {} }, + { "util", "validateaddress", &validateaddress, true, {"address"} }, /* uses wallet if enabled */ + { "util", "createmultisig", &createmultisig, true, {"nrequired","keys"} }, + { "util", "verifymessage", &verifymessage, true, {"address","signature","message"} }, + { "util", "signmessagewithprivkey", &signmessagewithprivkey, true, {"privkey","message"} }, + { "blockchain", "getspentinfo", &getspentinfo, false, {"json"} }, /* Address index */ - { "addressindex", "getaddressmempool", &getaddressmempool, true }, - { "addressindex", "getaddressutxos", &getaddressutxos, false }, - { "addressindex", "getaddressdeltas", &getaddressdeltas, false }, - { "addressindex", "getaddresstxids", &getaddresstxids, false }, - { "addressindex", "getaddressbalance", &getaddressbalance, false }, + { "addressindex", "getaddressmempool", &getaddressmempool, true, {"addresses"} }, + { "addressindex", "getaddressutxos", &getaddressutxos, false, {"addresses"} }, + { "addressindex", "getaddressdeltas", &getaddressdeltas, false, {"addresses"} }, + { "addressindex", "getaddresstxids", &getaddresstxids, false, {"addresses"} }, + { "addressindex", "getaddressbalance", &getaddressbalance, false, {"addresses"} }, /* Dash features */ - { "dash", "mnsync", &mnsync, true }, - { "dash", "spork", &spork, true }, + { "dash", "mnsync", &mnsync, true, {} }, + { "dash", "spork", &spork, true, {"datetime"} }, /* Not shown in help */ - { "hidden", "setmocktime", &setmocktime, true }, + { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, + { "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) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 8c756c8da..b1e649182 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -471,10 +471,10 @@ UniValue setban(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || (strCommand != "add" && strCommand != "remove")) throw runtime_error( - "setban \"ip(/netmask)\" \"add|remove\" (bantime) (absolute)\n" + "setban \"subnet\" \"add|remove\" (bantime) (absolute)\n" "\nAttempts add or remove a IP/Subnet from the banned list.\n" "\nArguments:\n" - "1. \"ip(/netmask)\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n" + "1. \"subnet\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n" "2. \"command\" (string, required) 'add' to add a IP/Subnet to the list, 'remove' to remove a IP/Subnet from the list\n" "3. \"bantime\" (numeric, optional) time in seconds how long (or until when if [absolute] is set) the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" "4. \"absolute\" (boolean, optional) If set, the bantime must be a absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n" @@ -583,7 +583,9 @@ UniValue setnetworkactive(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) { throw runtime_error( "setnetworkactive true|false\n" - "Disable/enable all p2p network activity." + "\nDisable/enable all p2p network activity.\n" + "\nArguments:\n" + "1. \"state\" (boolean, required) true to enable networking, false to disable\n" ); } @@ -599,18 +601,18 @@ UniValue setnetworkactive(const JSONRPCRequest& request) static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- - { "network", "getconnectioncount", &getconnectioncount, true }, - { "network", "ping", &ping, true }, - { "network", "getpeerinfo", &getpeerinfo, true }, - { "network", "addnode", &addnode, true }, - { "network", "disconnectnode", &disconnectnode, true }, - { "network", "getaddednodeinfo", &getaddednodeinfo, true }, - { "network", "getnettotals", &getnettotals, true }, - { "network", "getnetworkinfo", &getnetworkinfo, true }, - { "network", "setban", &setban, true }, - { "network", "listbanned", &listbanned, true }, - { "network", "clearbanned", &clearbanned, true }, - { "network", "setnetworkactive", &setnetworkactive, true, }, + { "network", "getconnectioncount", &getconnectioncount, true, {} }, + { "network", "ping", &ping, true, {} }, + { "network", "getpeerinfo", &getpeerinfo, true, {} }, + { "network", "addnode", &addnode, true, {"node","command"} }, + { "network", "disconnectnode", &disconnectnode, true, {"node"} }, + { "network", "getaddednodeinfo", &getaddednodeinfo, true, {"node"} }, + { "network", "getnettotals", &getnettotals, true, {} }, + { "network", "getnetworkinfo", &getnetworkinfo, true, {} }, + { "network", "setban", &setban, true, {"subnet", "command", "bantime", "absolute"} }, + { "network", "listbanned", &listbanned, true, {} }, + { "network", "clearbanned", &clearbanned, true, {} }, + { "network", "setnetworkactive", &setnetworkactive, true, {"state"} }, }; void RegisterNetRPCCommands(CRPCTable &t) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 41048efbc..f58284b2d 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -189,7 +189,7 @@ UniValue getrawtransaction(const JSONRPCRequest& request) " \"reqSigs\" : n, (numeric) The required sigs\n" " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" " \"addresses\" : [ (json array of string)\n" - " \"dashaddress\" (string) dash address\n" + " \"address\" (string) dash address\n" " ,...\n" " ]\n" " }\n" @@ -263,7 +263,7 @@ UniValue gettxoutproof(const JSONRPCRequest& request) " \"txid\" (string) A transaction hash\n" " ,...\n" " ]\n" - "2. \"block hash\" (string, optional) If specified, looks for txid in the block with this hash\n" + "2. \"blockhash\" (string, optional) If specified, looks for txid in the block with this hash\n" "\nResult:\n" "\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n" @@ -383,24 +383,24 @@ UniValue createrawtransaction(const JSONRPCRequest& request) "it is not stored in the wallet or transmitted to the network.\n" "\nArguments:\n" - "1. \"transactions\" (string, required) A json array of json objects\n" + "1. \"inputs\" (string, required) A json array of json objects\n" " [\n" " {\n" " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n (numeric, required) The output number\n" - " \"sequence\":n (numeric, optional) The sequence number\n" - " }\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"sequence\":n (numeric, optional) The sequence number\n" + " } \n" " ,...\n" " ]\n" - "2. \"outputs\" (string, required) a json object with outputs\n" + "2. \"outputs\" (string, required) a json object with outputs\n" " {\n" - " \"address\": x.xxx (numeric or string, required) The key is the dash address, the numeric value (can be string) is the " + CURRENCY_UNIT + " amount\n" - " \"data\": \"hex\", (string, required) The key is \"data\", the value is hex encoded data\n" - " ...\n" + " \"address\": x.xxx, (numeric or string, required) The key is the dash address, the numeric value (can be string) is the " + CURRENCY_UNIT + " amount\n" + " \"data\": \"hex\" (string, required) The key is \"data\", the value is hex encoded data\n" + " ,...\n" " }\n" - "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" + "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" "\nResult:\n" - "\"transaction\" (string) hex string of the transaction\n" + "\"transaction\" (string) hex string of the transaction\n" "\nExamples:\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") @@ -492,7 +492,7 @@ UniValue decoderawtransaction(const JSONRPCRequest& request) "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n" "\nArguments:\n" - "1. \"hex\" (string, required) The transaction hex string\n" + "1. \"hexstring\" (string, required) The transaction hex string\n" "\nResult:\n" "{\n" @@ -554,10 +554,10 @@ UniValue decodescript(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw runtime_error( - "decodescript \"hex\"\n" + "decodescript \"hexstring\"\n" "\nDecode a hex-encoded script.\n" "\nArguments:\n" - "1. \"hex\" (string) the hex encoded script\n" + "1. \"hexstring\" (string) the hex encoded script\n" "\nResult:\n" "{\n" " \"asm\":\"asm\", (string) Script public key\n" @@ -638,7 +638,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) " }\n" " ,...\n" " ]\n" - "3. \"privatekeys\" (string, optional) A json array of base58-encoded private keys for signing\n" + "3. \"privkeys\" (string, optional) A json array of base58-encoded private keys for signing\n" " [ (json array of strings, or 'null' if none provided)\n" " \"privatekey\" (string) private key in base58-encoding\n" " ,...\n" @@ -949,15 +949,15 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- - { "rawtransactions", "getrawtransaction", &getrawtransaction, true }, - { "rawtransactions", "createrawtransaction", &createrawtransaction, true }, - { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true }, - { "rawtransactions", "decodescript", &decodescript, true }, - { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, - { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ + { "rawtransactions", "getrawtransaction", &getrawtransaction, true, {"txid","verbose"} }, + { "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"transactions","outputs","locktime"} }, + { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} }, + { "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} }, + { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees","instantsend"} }, + { "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ - { "blockchain", "gettxoutproof", &gettxoutproof, true }, - { "blockchain", "verifytxoutproof", &verifytxoutproof, true }, + { "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} }, + { "blockchain", "verifytxoutproof", &verifytxoutproof, true, {"proof"} }, }; void RegisterRawTransactionRPCCommands(CRPCTable &t) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e30c84162..3ff336aac 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -27,6 +27,7 @@ #include // for to_upper() #include // for unique_ptr +#include using namespace RPCServer; using namespace std; @@ -269,11 +270,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest) * Call Table */ static const CRPCCommand vRPCCommands[] = -{ // category name actor (function) okSafeMode - // --------------------- ------------------------ ----------------------- ---------- +{ // category name actor (function) okSafe argNames + // --------------------- ------------------------ ----------------------- ------ ---------- /* Overall control/query calls */ - { "control", "help", &help, true }, - { "control", "stop", &stop, true }, + { "control", "help", &help, true, {"command"} }, + { "control", "stop", &stop, true, {} }, }; CRPCTable::CRPCTable() @@ -380,12 +381,12 @@ void JSONRPCRequest::parse(const UniValue& valRequest) // Parse params UniValue valParams = find_value(request, "params"); - if (valParams.isArray()) - params = valParams.get_array(); + if (valParams.isArray() || valParams.isObject()) + params = valParams; else if (valParams.isNull()) params = UniValue(UniValue::VARR); else - throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); + throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); } static UniValue JSONRPCExecOne(const UniValue& req) @@ -421,6 +422,48 @@ std::string JSONRPCExecBatch(const UniValue& vReq) return ret.write() + "\n"; } +/** + * Process named arguments into a vector of positional arguments, based on the + * passed-in specification for the RPC call's arguments. + */ +static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector& argNames) +{ + JSONRPCRequest out = in; + out.params = UniValue(UniValue::VARR); + // Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if + // there is an unknown one. + const std::vector& keys = in.params.getKeys(); + const std::vector& values = in.params.getValues(); + std::unordered_map argsIn; + for (size_t i=0; isecond); + argsIn.erase(fr); + } else { + hole += 1; + } + } + // If there are still arguments in the argsIn map, this is an error. + if (!argsIn.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first); + } + // Return request with named arguments transformed to positional arguments + return out; +} + UniValue CRPCTable::execute(const JSONRPCRequest &request) const { // Return immediately if in warmup @@ -439,8 +482,12 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const try { - // Execute - return pcmd->actor(request); + // Execute, convert arguments to array if necessary + if (request.params.isObject()) { + return pcmd->actor(transformNamedArguments(request, pcmd->argNames)); + } else { + return pcmd->actor(request); + } } catch (const std::exception& e) { diff --git a/src/rpc/server.h b/src/rpc/server.h index 28ac5ec67..ff87125a2 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -134,6 +134,7 @@ public: std::string name; rpcfn_type actor; bool okSafeMode; + std::vector argNames; }; /** diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 773fbc424..b56785622 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -83,7 +83,7 @@ UniValue importprivkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw runtime_error( - "importprivkey \"dashprivkey\" ( \"label\" rescan )\n" + "importprivkey \"dashprivkey\" ( \"label\" ) ( rescan )\n" "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" "\nArguments:\n" "1. \"dashprivkey\" (string, required) The private key (see dumpprivkey)\n" @@ -655,11 +655,11 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw runtime_error( - "dumpprivkey \"dashaddress\"\n" - "\nReveals the private key corresponding to 'dashaddress'.\n" + "dumpprivkey \"address\"\n" + "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n" "\nArguments:\n" - "1. \"dashaddress\" (string, required) The dash address for the private key\n" + "1. \"address\" (string, required) The bitcoin address for the private key\n" "\nResult:\n" "\"key\" (string) The private key\n" "\nExamples:\n" @@ -1167,10 +1167,10 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw runtime_error( - "importmulti '[]' '' \n\n" + "importmulti \"requests\" \"options\"\n\n" "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n" "Arguments:\n" - "1. request array (array, required) Data to be imported\n" + "1. requests (array, required) Data to be imported\n" " [ (array of json objects)\n" " {\n" " \"scriptPubKey\": \"