diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -162,6 +162,7 @@ reverselock.h \ rpc/blockchain.h \ rpc/client.h \ + rpc/command.h \ rpc/jsonrpcrequest.h \ rpc/mining.h \ rpc/misc.h \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -80,6 +80,7 @@ test/random_tests.cpp \ test/reverselock_tests.cpp \ test/rpc_tests.cpp \ + test/rpc_server_tests.cpp \ test/rwcollection_tests.cpp \ test/sanity_tests.cpp \ test/scheduler_tests.cpp \ diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -48,8 +48,6 @@ TestingSetup test; - SetRPCWarmupFinished(); - std::string result; std::string result2; std::string filtered; diff --git a/src/rpc/command.h b/src/rpc/command.h new file mode 100644 --- /dev/null +++ b/src/rpc/command.h @@ -0,0 +1,36 @@ +// Copyright (c) 2018 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_COMMAND_H +#define BITCOIN_RPC_COMMAND_H + +#include + +#include + +/** + * Base class for all RPC commands. + */ +class RPCCommand { +private: + // TODO: Parameter definitions (these will be used to generate help + // messages as well) + +public: + virtual UniValue Execute(const UniValue &args) const = 0; + + virtual std::string GetName() const = 0; + + /** + * Child classes of RPCCommand must implement a factory function to pass + * to RPCServer::RegisterCommand(). Dependency injection can be done via + * std::bind(). Example: + * rpcServer.RegisterCommand( + * MyRPCCommand::commandName, + * std::bind(&MyRPCCommand::CreateFromJSONRequest, + * _1, )); + */ +}; + +#endif // BITCOIN_RPC_COMMAND_H diff --git a/src/rpc/server.h b/src/rpc/server.h --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -8,6 +8,7 @@ #define BITCOIN_RPC_SERVER_H #include "amount.h" +#include "rpc/command.h" #include "rpc/jsonrpcrequest.h" #include "rpc/protocol.h" #include "uint256.h" @@ -50,6 +51,10 @@ * Class for registering and managing all RPC calls. */ class RPCServer : public boost::noncopyable { +private: + std::map> + commands; + public: RPCServer() {} @@ -59,6 +64,23 @@ */ UniValue ExecuteCommand(Config &config, const JSONRPCRequest &request) const; + + /** + * Register an RPC command factory function. + */ + void RegisterCommand( + const std::string &commandName, + std::function factory) { + commands[commandName] = factory; + } + + /** + * Return an RPCCommand factory if one matches the given command name. + * Returns an error factory that produces a nullptr RPCCommand* if no + * command matches. + */ + std::function + MatchCommand(std::string name) const; }; /** diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers +// Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -46,13 +47,35 @@ } } - // TODO Only call tableRPC.execute() if no context-sensitive RPC command - // exists + std::string commandName = request.strMethod; + RPCCommand *command = MatchCommand(commandName)(request); + if (command != nullptr) { + return command->Execute(request.params); + } + + // TODO Remove the below call to tableRPC.execute() and only call it for + // context-free RPC commands via an implementation of RPCCommand. // Check if context-free RPC method is valid and execute it return tableRPC.execute(config, request); } +namespace { +static RPCCommand *GetCommandErrorFactory(const JSONRPCRequest &) { + return nullptr; +} +} + +std::function +RPCServer::MatchCommand(std::string name) const { + auto iter = commands.find(name); + if (iter == commands.end()) { + return &::GetCommandErrorFactory; + } + + return iter->second; +} + static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -97,6 +97,7 @@ random_tests.cpp reverselock_tests.cpp rpc_tests.cpp + rpc_server_tests.cpp rwcollection_tests.cpp sanity_tests.cpp scheduler_tests.cpp diff --git a/src/test/rpc_server_tests.cpp b/src/test/rpc_server_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/rpc_server_tests.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2018 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "chainparams.h" +#include "config.h" +#include "rpc/jsonrpcrequest.h" +#include "rpc/server.h" + +#include "test/test_bitcoin.h" + +#include + +#include +#include + +struct TestRPCCommand : public RPCCommand { + UniValue Execute(const UniValue &args) const { return UniValue(); } + + static RPCCommand *CreateFromJSONRequest(const JSONRPCRequest &request) { + return new TestRPCCommand(); + } + + static const std::string commandName; + std::string GetName() const { return commandName; } +}; + +const std::string TestRPCCommand::commandName = "testcommand"; + +BOOST_FIXTURE_TEST_SUITE(rpc_server_tests, TestingSetup) + +BOOST_AUTO_TEST_CASE(rpc_server_match_command) { + RPCServer rpcServer; + rpcServer.RegisterCommand(TestRPCCommand::commandName, + &TestRPCCommand::CreateFromJSONRequest); + + BOOST_CHECK( + rpcServer.MatchCommand(TestRPCCommand::commandName)(JSONRPCRequest())); + + // Checking non-existent command errors as expected + BOOST_CHECK(rpcServer.MatchCommand("this-command-does-not-exist")( + JSONRPCRequest()) == nullptr); +} + +struct ArgsTestRPCCommand : public RPCCommand { + UniValue Execute(const UniValue &args) const { + BOOST_CHECK_EQUAL(args["arg1"].get_str(), "value1"); + return UniValue(); + } + + static RPCCommand *CreateFromJSONRequest(const JSONRPCRequest &request) { + return new ArgsTestRPCCommand(); + } + + static const std::string commandName; + std::string GetName() const { return commandName; } +}; + +const std::string ArgsTestRPCCommand::commandName = "testcommand"; + +BOOST_AUTO_TEST_CASE(rpc_server_execute_command) { + DummyConfig config; + RPCServer rpcServer; + rpcServer.RegisterCommand(ArgsTestRPCCommand::commandName, + &ArgsTestRPCCommand::CreateFromJSONRequest); + + UniValue args(UniValue::VOBJ); + args.pushKV("arg1", "value1"); + + JSONRPCRequest request; + request.strMethod = ArgsTestRPCCommand::commandName; + request.params = args; + + rpcServer.ExecuteCommand(config, request); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -85,6 +85,12 @@ // instead of unit tests, but for now we need these here. RPCServer rpcServer; RegisterAllRPCCommands(config, rpcServer, tableRPC); + + std::string rpcWarmupStatus; + if (RPCIsInWarmup(&rpcWarmupStatus)) { + SetRPCWarmupFinished(); + } + ClearDatadirCache(); pathTemp = GetTempPath() / strprintf("test_bitcoin_%lu_%i", (unsigned long)GetTime(),