diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,6 +216,7 @@ rest.cpp rpc/abc.cpp rpc/blockchain.cpp + rpc/command.cpp rpc/jsonrpcrequest.cpp rpc/mining.cpp rpc/misc.cpp diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -257,6 +257,7 @@ rest.cpp \ rpc/abc.cpp \ rpc/blockchain.cpp \ + rpc/command.cpp \ rpc/jsonrpcrequest.cpp \ rpc/mining.cpp \ rpc/misc.cpp \ diff --git a/src/rpc/command.h b/src/rpc/command.h --- a/src/rpc/command.h +++ b/src/rpc/command.h @@ -11,8 +11,13 @@ #include +class JSONRPCRequest; + /** - * Base class for all RPC commands. + * Base class for all RPC commands. RPCCommand should only + * be inherited from directly if access to the entire request context is + * necessary. For more typical cases where only request arguments are + * required, see the RPCCommandWithArgsContext class. */ class RPCCommand : public boost::noncopyable { private: @@ -28,12 +33,30 @@ // messages as well) public: - RPCCommand(std::string nameIn) : name(nameIn) {} + RPCCommand(const std::string &nameIn) : name(nameIn) {} virtual ~RPCCommand() {} - virtual UniValue Execute(const UniValue &args) const = 0; + /** + * It is recommended to override Execute(JSONRPCRequest) only if the entire + * request context is required. Otherwise, use RPCCommandWithArgsContext + * instead. + */ + virtual UniValue Execute(const JSONRPCRequest &request) const = 0; const std::string &GetName() const { return name; }; }; -#endif // BITCOIN_RPC_COMMAND_H +/** + * By default, use RPCCommandWithArgsContext as the parent class for new RPC + * command classes that only depend on RPC arguments. + */ +class RPCCommandWithArgsContext : public RPCCommand { +public: + RPCCommandWithArgsContext(const std::string &nameIn) : RPCCommand(nameIn) {} + + virtual UniValue Execute(const UniValue &args) const = 0; + + UniValue Execute(const JSONRPCRequest &request) const final; +}; + +#endif // BITCOIN_RPC_COMMAND_WITH_REQUEST_CONTEXT_H diff --git a/src/rpc/command.cpp b/src/rpc/command.cpp new file mode 100644 --- /dev/null +++ b/src/rpc/command.cpp @@ -0,0 +1,11 @@ +// Copyright (c) 2019 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "command.h" +#include "jsonrpcrequest.h" + +UniValue +RPCCommandWithArgsContext::Execute(const JSONRPCRequest &request) const { + return Execute(request.params); +} diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -55,7 +55,7 @@ auto commandsReadView = commands.getReadView(); auto iter = commandsReadView->find(commandName); if (iter != commandsReadView.end()) { - return iter->second.get()->Execute(request.params); + return iter->second.get()->Execute(request); } } diff --git a/src/test/rpc_server_tests.cpp b/src/test/rpc_server_tests.cpp --- a/src/test/rpc_server_tests.cpp +++ b/src/test/rpc_server_tests.cpp @@ -16,13 +16,14 @@ BOOST_FIXTURE_TEST_SUITE(rpc_server_tests, TestingSetup) -class ArgsTestRPCCommand : public RPCCommand { +class ArgsTestRPCCommand : public RPCCommandWithArgsContext { public: - ArgsTestRPCCommand(std::string nameIn) : RPCCommand(nameIn) {} + ArgsTestRPCCommand(const std::string &nameIn) + : RPCCommandWithArgsContext(nameIn) {} UniValue Execute(const UniValue &args) const override { BOOST_CHECK_EQUAL(args["arg1"].get_str(), "value1"); - return UniValue("testing"); + return UniValue("testing1"); } }; @@ -33,7 +34,7 @@ BOOST_AUTO_TEST_CASE(rpc_server_execute_command) { DummyConfig config; RPCServer rpcServer; - const std::string commandName = "testcommand"; + const std::string commandName = "testcommand1"; rpcServer.RegisterCommand(MakeUnique(commandName)); UniValue args(UniValue::VOBJ); @@ -44,7 +45,7 @@ request.strMethod = commandName; request.params = args; UniValue output = rpcServer.ExecuteCommand(config, request); - BOOST_CHECK_EQUAL(output.get_str(), "testing"); + BOOST_CHECK_EQUAL(output.get_str(), "testing1"); // Not-registered commands throw an exception as expected JSONRPCRequest badCommandRequest; @@ -53,4 +54,36 @@ UniValue, isRpcMethodNotFound); } +class RequestContextRPCCommand : public RPCCommand { +public: + RequestContextRPCCommand(const std::string &nameIn) : RPCCommand(nameIn) {} + + // Sanity check that Execute(JSONRPCRequest) is called correctly from + // RPCServer + UniValue Execute(const JSONRPCRequest &request) const override { + const UniValue args = request.params; + BOOST_CHECK_EQUAL(request.strMethod, "testcommand2"); + BOOST_CHECK_EQUAL(args["arg2"].get_str(), "value2"); + return UniValue("testing2"); + } +}; + +BOOST_AUTO_TEST_CASE(rpc_server_execute_command_from_request_context) { + DummyConfig config; + RPCServer rpcServer; + const std::string commandName = "testcommand2"; + rpcServer.RegisterCommand( + MakeUnique(commandName)); + + UniValue args(UniValue::VOBJ); + args.pushKV("arg2", "value2"); + + // Registered commands execute and return values correctly + JSONRPCRequest request; + request.strMethod = commandName; + request.params = args; + UniValue output = rpcServer.ExecuteCommand(config, request); + BOOST_CHECK_EQUAL(output.get_str(), "testing2"); +} + BOOST_AUTO_TEST_SUITE_END()