diff --git a/src/rpc/commandname.h b/src/rpc/commandname.h --- a/src/rpc/commandname.h +++ b/src/rpc/commandname.h @@ -16,7 +16,7 @@ public: RPCCommandName(std::string nameIn) : name(nameIn){}; - std::string GetName() const { return name; } + std::string GetNameStr() const { return name; } }; #endif // BITCOIN_RPC_COMMAND_NAME_H diff --git a/src/rpc/server.h b/src/rpc/server.h --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -4,10 +4,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_RPCSERVER_H -#define BITCOIN_RPCSERVER_H +#ifndef BITCOIN_RPC_SERVER_H +#define BITCOIN_RPC_SERVER_H #include "amount.h" +#include "rpc/command.h" #include "rpc/jsonrpcrequest.h" #include "rpc/protocol.h" #include "uint256.h" @@ -18,6 +19,7 @@ #include #include +#include #include static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1; @@ -47,6 +49,47 @@ }; /** + * Class for registering and managing all RPC calls. + */ +class RPCServer : public boost::noncopyable { +private: + // TODO: Using std::string to map commands for now, but will be replaced by + // more complex matching in the future to support short RPC names. + std::map commands; + +public: + RPCServer() {} + + const RPCCommand *operator[](const std::string &name) const; + + /** + * Registers an RPCCommand. + * Returns false if RPC server is already running (dump concurrency + * protection). + * Commands cannot be overwritten (returns false). + */ + bool RegisterCommand(const RPCCommand *command); + + /** + * Attempts to execute an RPC command from the given request. + * If no RPC command exists that matches the request, an error is returned. + */ + UniValue ExecuteCommand(Config &config, + const JSONRPCRequest &request) const; + + /** + * Returns a help string for the generic RPC interface. + */ + std::string Help(Config &config, const RPCCommandName &name, + const JSONRPCRequest &helpRequest) const; + + /** + * Returns a list of all RPC command names. + */ + std::vector ListCommandNames() const; +}; + +/** * Query whether RPC is running */ bool IsRPCRunning(); @@ -262,4 +305,4 @@ */ int RPCSerializationFlags(); -#endif // BITCOIN_RPCSERVER_H +#endif // BITCOIN_RPC_SERVER_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -19,6 +19,9 @@ #include // for to_upper() #include +#include +#include +#include #include #include @@ -35,6 +38,132 @@ /* Map of name to timer. */ static std::map> deadlineTimers; +const RPCCommand *RPCServer::operator[](const std::string &name) const { + auto iter = commands.find(name); + if (iter == commands.end()) { + return nullptr; + } + + return (*iter).second; +} + +bool RPCServer::RegisterCommand(const RPCCommand *command) { + // Exit immediately if the RPC server is already running + if (IsRPCRunning()) { + return false; + } + + // Do not allow commands to be overwritten + std::string commandNameStr = command->GetName().GetNameStr(); + if (commands.find(commandNameStr) == commands.end()) { + return false; + } + + commands[commandNameStr] = command; + return true; +} + +/** + * 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; i < keys.size(); ++i) { + argsIn[keys[i]] = &values[i]; + } + // Process expected parameters. + int hole = 0; + for (const std::string &argName : argNames) { + auto fr = argsIn.find(argName); + if (fr != argsIn.end()) { + for (int i = 0; i < hole; ++i) { + // Fill hole between specified parameters with JSON nulls, but + // not at the end (for backwards compatibility with calls that + // act based on number of specified parameters). + out.params.push_back(UniValue()); + } + hole = 0; + out.params.push_back(*fr->second); + 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; +} + +static std::string GetParameterName(const RPCParameter *param) { + return param->GetName(); +} + +UniValue RPCServer::ExecuteCommand(Config &config, + const JSONRPCRequest &request) const { + // Return immediately if in warmup + { + LOCK(cs_rpcWarmup); + if (fRPCInWarmup) { + throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); + } + } + + // Check if context-sensitive RPC method is valid + auto iter = commands.find(request.strMethod); + if (iter == commands.end()) { + // Check if context-free RPC method is valid and execute it + return tableRPC.execute(config, request); + + // TODO: Once migrated away from tableRPC, remove the above and + // uncomment the line below. + // throw JSONRPCError(RPC_METHOD_NOT_FOUND, "RPC command not found"); + } + + const RPCCommand *command = commands.at(request.strMethod); + + try { + // Execute, convert arguments to array if necessary + if (request.params.isObject()) { + std::vector paramNames; + boost::copy(command->GetParameters() | + boost::adaptors::transformed(GetParameterName), + std::back_inserter(paramNames)); + return command->Execute( + transformNamedArguments(request, paramNames)); + } else { + return command->Execute(request); + } + } catch (const std::exception &e) { + throw JSONRPCError(RPC_MISC_ERROR, e.what()); + } +} + +std::string RPCServer::Help(Config &config, const RPCCommandName &name, + const JSONRPCRequest &helpRequest) const { + // TODO + return ""; +} + +std::vector RPCServer::ListCommandNames() const { + std::vector commandNames; + boost::copy(commands | boost::adaptors::map_keys, + std::back_inserter(commandNames)); + return commandNames; +} + static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; @@ -274,6 +403,9 @@ strCommand = jsonRequest.params[0].get_str(); } + // TODO: Add RPCServer help call until this help() function is migrated to + // the new RPC classes. + return tableRPC.help(config, strCommand, jsonRequest); } @@ -431,50 +563,6 @@ 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; i < keys.size(); ++i) { - argsIn[keys[i]] = &values[i]; - } - // Process expected parameters. - int hole = 0; - for (const std::string &argName : argNames) { - auto fr = argsIn.find(argName); - if (fr != argsIn.end()) { - for (int i = 0; i < hole; ++i) { - // Fill hole between specified parameters with JSON nulls, but - // not at the end (for backwards compatibility with calls that - // act based on number of specified parameters). - out.params.push_back(UniValue()); - } - hole = 0; - out.params.push_back(*fr->second); - 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(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup @@ -485,7 +573,8 @@ } } - // Find method + // Check if legacy RPC method is valid. + // See RPCServer::ExecuteCommand for context-sensitive RPC commands. const ContextFreeRPCCommand *pcmd = tableRPC[request.strMethod]; if (!pcmd) { throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");