diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -71,7 +71,8 @@ // not possible as the whole application has too many global state. However, // this is a first step. auto &config = const_cast(GetConfig()); - HTTPRPCRequestProcessor httpRPCRequestProcessor(config); + RPCServer rpcServer; + HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); bool fRet = false; @@ -148,7 +149,7 @@ // up on console exit(1); } - if (!AppInitParameterInteraction(config)) { + if (!AppInitParameterInteraction(config, rpcServer)) { // InitError will have been called with detailed error, which ends // up on console exit(1); diff --git a/src/httprpc.h b/src/httprpc.h --- a/src/httprpc.h +++ b/src/httprpc.h @@ -17,11 +17,13 @@ class HTTPRPCRequestProcessor { private: Config &config; + RPCServer &rpcServer; bool ProcessHTTPRequest(HTTPRequest *request); public: - HTTPRPCRequestProcessor(Config &configIn) : config(configIn) {} + HTTPRPCRequestProcessor(Config &configIn, RPCServer &rpcServerIn) + : config(configIn), rpcServer(rpcServerIn) {} static bool DelegateHTTPRequest(HTTPRPCRequestProcessor *requestProcessor, HTTPRequest *request) { diff --git a/src/httprpc.cpp b/src/httprpc.cpp --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -315,13 +315,14 @@ if (valRequest.isObject()) { jreq.parse(valRequest); - UniValue result = tableRPC.execute(config, jreq); + UniValue result = rpcServer.ExecuteCommand(config, jreq); // Send reply strReply = JSONRPCReply(result, NullUniValue, jreq.id); } else if (valRequest.isArray()) { // array of requests - strReply = JSONRPCExecBatch(config, jreq, valRequest.get_array()); + strReply = JSONRPCExecBatch(config, rpcServer, jreq, + valRequest.get_array()); } else { throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); } diff --git a/src/init.h b/src/init.h --- a/src/init.h +++ b/src/init.h @@ -12,6 +12,7 @@ class CScheduler; class CWallet; class HTTPRPCRequestProcessor; +class RPCServer; namespace boost { class thread_group; @@ -38,7 +39,7 @@ * @pre Parameters should be parsed and config file should be read, * AppInitBasicSetup should have been called. */ -bool AppInitParameterInteraction(Config &config); +bool AppInitParameterInteraction(Config &config, RPCServer &rpcServer); /** * Initialization sanity checks: ecc init, sanity checks, dir lock. * @note This can be done before daemonization. diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1347,7 +1347,7 @@ return true; } -bool AppInitParameterInteraction(Config &config) { +bool AppInitParameterInteraction(Config &config, RPCServer &rpcServer) { const CChainParams &chainparams = config.GetChainParams(); // Step 2: parameter interactions @@ -1575,7 +1575,7 @@ fPruneMode = true; } - RegisterAllRPCCommands(tableRPC); + RegisterAllRPCCommands(config, rpcServer, tableRPC); #ifdef ENABLE_WALLET RegisterWalletRPCCommands(tableRPC); RegisterDumpRPCCommands(tableRPC); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -171,7 +171,8 @@ public Q_SLOTS: void initialize(Config *config, - HTTPRPCRequestProcessor *httpRPCRequestProcessor); + HTTPRPCRequestProcessor *httpRPCRequestProcessor, + RPCServer *rpcServer); void shutdown(); Q_SIGNALS: @@ -209,7 +210,8 @@ /// Request core initialization void requestInitialize(Config &config, - HTTPRPCRequestProcessor &httpRPCRequestProcessor); + HTTPRPCRequestProcessor &httpRPCRequestProcessor, + RPCServer &rpcServer); /// Request core shutdown void requestShutdown(Config &config); @@ -228,7 +230,8 @@ Q_SIGNALS: void requestedInitialize(Config *config, - HTTPRPCRequestProcessor *httpRPCRequestProcessor); + HTTPRPCRequestProcessor *httpRPCRequestProcessor, + RPCServer *rpcServer); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); @@ -260,8 +263,10 @@ } void BitcoinABC::initialize(Config *cfg, - HTTPRPCRequestProcessor *httpRPCRequestProcessor) { + HTTPRPCRequestProcessor *httpRPCRequestProcessor, + RPCServer *rpcSrv) { Config &config(*cfg); + RPCServer &rpcServer = *rpcSrv; try { qDebug() << __func__ << ": Running AppInit2 in thread"; if (!AppInitBasicSetup()) { @@ -269,7 +274,7 @@ return; } - if (!AppInitParameterInteraction(config)) { + if (!AppInitParameterInteraction(config, rpcServer)) { Q_EMIT initializeResult(false); return; } @@ -403,9 +408,10 @@ // temporary (eg it lives somewhere aside from the stack) or this will // crash because initialize() gets executed in another thread at some // unspecified time (after) requestedInitialize() is emitted! - connect(this, - SIGNAL(requestedInitialize(Config *, HTTPRPCRequestProcessor *)), - executor, SLOT(initialize(Config *, HTTPRPCRequestProcessor *))); + connect(this, SIGNAL(requestedInitialize( + Config *, HTTPRPCRequestProcessor *, RPCServer *)), + executor, + SLOT(initialize(Config *, HTTPRPCRequestProcessor *, RPCServer *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ @@ -421,13 +427,14 @@ } void BitcoinApplication::requestInitialize( - Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor) { + Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor, + RPCServer &rpcServer) { qDebug() << __func__ << ": Requesting initialize"; startThread(); // IMPORTANT: config must NOT be a reference to a temporary because below // signal may be connected to a slot that will be executed as a queued // connection in another thread! - Q_EMIT requestedInitialize(&config, &httpRPCRequestProcessor); + Q_EMIT requestedInitialize(&config, &httpRPCRequestProcessor, &rpcServer); } void BitcoinApplication::requestShutdown(Config &config) { @@ -758,11 +765,12 @@ !gArgs.GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); - HTTPRPCRequestProcessor httpRPCRequestProcessor(config); + RPCServer rpcServer; + HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); try { app.createWindow(&config, networkStyle.data()); - app.requestInitialize(config, httpRPCRequestProcessor); + app.requestInitialize(config, httpRPCRequestProcessor, rpcServer); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason( QObject::tr("%1 didn't yet exit safely...") 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 @@ -37,7 +37,7 @@ // Do some test setup could be moved to a more generic place when we add // more tests on QT level const Config &config = GetConfig(); - RegisterAllRPCCommands(tableRPC); + RegisterAllContextFreeRPCCommands(tableRPC); tableRPC.appendCommand("rpcNestedTest", &vRPCCommands[0]); ClearDatadirCache(); std::string path = diff --git a/src/rpc/register.h b/src/rpc/register.h --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -8,6 +8,7 @@ /** These are in one header file to avoid creating tons of single-function * headers for everything under src/rpc/ */ class CRPCTable; +class RPCServer; /** Register block chain RPC commands */ void RegisterBlockchainRPCCommands(CRPCTable &tableRPC); @@ -22,7 +23,11 @@ /** Register ABC RPC commands */ void RegisterABCRPCCommands(CRPCTable &tableRPC); -static inline void RegisterAllRPCCommands(CRPCTable &t) { +/** + * Register all context-free (legacy) RPC commands, except for wallet and dump + * RPC commands. + */ +static inline void RegisterAllContextFreeRPCCommands(CRPCTable &t) { RegisterBlockchainRPCCommands(t); RegisterNetRPCCommands(t); RegisterMiscRPCCommands(t); @@ -31,4 +36,15 @@ RegisterABCRPCCommands(t); } +/** + * Register all context-sensitive RPC commands. + */ +static inline void RegisterAllRPCCommands(const Config &config, + RPCServer &rpcServer, + CRPCTable &rpcTable) { + // TODO Register context-sensitive RPC commands using rpcServer + + RegisterAllContextFreeRPCCommands(rpcTable); +} + #endif // BITCOIN_RPC_REGISTER_H diff --git a/src/rpc/server.h b/src/rpc/server.h --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -4,8 +4,8 @@ // 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/jsonrpcrequest.h" @@ -18,6 +18,7 @@ #include #include +#include #include static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1; @@ -46,6 +47,21 @@ UniValue::VType type; }; +/** + * Class for registering and managing all RPC calls. + */ +class RPCServer : public boost::noncopyable { +public: + RPCServer() {} + + /** + * 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; +}; + /** * Query whether RPC is running */ @@ -253,8 +269,8 @@ bool StartRPC(); void InterruptRPC(); void StopRPC(); -std::string JSONRPCExecBatch(Config &config, const JSONRPCRequest &req, - const UniValue &vReq); +std::string JSONRPCExecBatch(Config &config, RPCServer &rpcServer, + const JSONRPCRequest &req, const UniValue &vReq); void RPCNotifyBlockChange(bool ibd, const CBlockIndex *); /** diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -34,6 +34,23 @@ /* Map of name to timer. */ static std::map> deadlineTimers; +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); + } + } + + // TODO Only call tableRPC.execute() if no context-sensitive RPC command + // exists + + // Check if context-free RPC method is valid and execute it + return tableRPC.execute(config, request); +} + static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; @@ -399,14 +416,14 @@ return fRPCInWarmup; } -static UniValue JSONRPCExecOne(Config &config, JSONRPCRequest jreq, - const UniValue &req) { +static UniValue JSONRPCExecOne(Config &config, RPCServer &rpcServer, + JSONRPCRequest jreq, const UniValue &req) { UniValue rpc_result(UniValue::VOBJ); try { jreq.parse(req); - UniValue result = tableRPC.execute(config, jreq); + UniValue result = rpcServer.ExecuteCommand(config, jreq); rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); } catch (const UniValue &objError) { rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); @@ -418,11 +435,11 @@ return rpc_result; } -std::string JSONRPCExecBatch(Config &config, const JSONRPCRequest &jreq, - const UniValue &vReq) { +std::string JSONRPCExecBatch(Config &config, RPCServer &rpcServer, + const JSONRPCRequest &jreq, const UniValue &vReq) { UniValue ret(UniValue::VARR); for (size_t i = 0; i < vReq.size(); i++) { - ret.push_back(JSONRPCExecOne(config, jreq, vReq[i])); + ret.push_back(JSONRPCExecOne(config, rpcServer, jreq, vReq[i])); } return ret.write() + "\n"; @@ -482,7 +499,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"); 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 @@ -82,7 +82,8 @@ // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. const Config &config = GetConfig(); - RegisterAllRPCCommands(tableRPC); + RPCServer rpcServer; + RegisterAllRPCCommands(config, rpcServer, tableRPC); ClearDatadirCache(); pathTemp = GetTempPath() / strprintf("test_bitcoin_%lu_%i", (unsigned long)GetTime(),