diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index d52b04d58..cee17bd22 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -1,222 +1,221 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::function G_TRANSLATION_FUN = nullptr; /* Introduction text for doxygen: */ /*! \mainpage Developer documentation * * \section intro_sec Introduction * * This is the developer documentation of Bitcoin ABC * (https://www.bitcoinabc.org/). Bitcoin ABC is a client for the digital * currency called Bitcoin Cash (https://www.bitcoincash.org/), which enables * instant payments to anyone, anywhere in the world. Bitcoin Cash uses * peer-to-peer technology to operate with no central authority: managing * transactions and issuing money are carried out collectively by the network. * * The software is a community-driven open source project, released under the * MIT license. * * \section Navigation * Use the buttons Namespaces, Classes or * Files at the top of the page to start navigating the code. */ static void WaitForShutdown(NodeContext &node) { while (!ShutdownRequested()) { UninterruptibleSleep(std::chrono::milliseconds{200}); } Interrupt(node); } ////////////////////////////////////////////////////////////////////////////// // // Start // static bool AppInit(int argc, char *argv[]) { // FIXME: Ideally, we'd like to build the config here, but that's currently // not possible as the whole application has too many global state. However, // this is a first step. auto &config = const_cast(GetConfig()); RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); NodeContext node; bool fRet = false; util::ThreadSetInternalName("init"); // // Parameters // // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's // main() SetupServerArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { return InitError( strprintf("Error parsing command line arguments: %s\n", error)); } // Process help and version before taking care about datadir if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = - PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; + PACKAGE_NAME " version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()); } else { strUsage += "\nUsage: bitcoind [options] " - "Start " PACKAGE_NAME " Daemon\n"; - + "Start " PACKAGE_NAME "\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage); return true; } try { if (!CheckDataDirOption()) { return InitError( strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } if (!gArgs.ReadConfigFiles(error, true)) { return InitError( strprintf("Error reading configuration file: %s\n", error)); } // Check for -chain, -testnet or -regtest parameter (Params() calls are // only valid after this clause) try { SelectParams(gArgs.GetChainName()); node.chain = interfaces::MakeChain(node, config.GetChainParams()); } catch (const std::exception &e) { return InitError(strprintf("%s\n", e.what())); } // Make sure we create the net-specific data directory early on: if it // is new, this has a side effect of also creating // //wallets/. // // TODO: this should be removed once GetDataDir() no longer creates the // wallets/ subdirectory. // See more info at: // https://reviews.bitcoinabc.org/D3312 GetDataDir(true); // Error out when loose non-argument tokens are encountered on command // line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { return InitError( strprintf("Command line contains unexpected token '%s', " "see bitcoind -h for a list of options.\n", argv[i])); } } // -server defaults to true for bitcoind but not for the GUI so do this // here gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console InitLogging(); InitParameterInteraction(); if (!AppInitBasicSetup()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitParameterInteraction(config)) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitSanityChecks()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (gArgs.GetBoolArg("-daemon", false)) { #if HAVE_DECL_DAEMON #if defined(MAC_OSX) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif - tfm::format(std::cout, PACKAGE_NAME " daemon starting\n"); + tfm::format(std::cout, PACKAGE_NAME " starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) return InitError( strprintf("daemon() failed: %s\n", strerror(errno))); } #if defined(MAC_OSX) #pragma GCC diagnostic pop #endif #else return InitError( "-daemon is not supported on this operating system\n"); #endif // HAVE_DECL_DAEMON } // Lock data directory after daemonization if (!AppInitLockDataDirectory()) { // If locking the data directory failed, exit immediately return false; } fRet = AppInitMain(config, rpcServer, httpRPCRequestProcessor, node); } catch (const std::exception &e) { PrintExceptionContinue(&e, "AppInit()"); } catch (...) { PrintExceptionContinue(nullptr, "AppInit()"); } if (!fRet) { Interrupt(node); } else { WaitForShutdown(node); } Shutdown(node); return fRet; } int main(int argc, char *argv[]) { #ifdef WIN32 util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); #endif SetupEnvironment(); // Connect bitcoind signal handlers noui_connect(); return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 26cf8d5f5..958cd4755 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,543 +1,543 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2018 The Bitcoin Core developers // Copyright (c) 2018-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 #include #include #include #include #include #include #include #include #include // for unique_ptr #include #include static RecursiveMutex cs_rpcWarmup; static std::atomic g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(cs_rpcWarmup) = true; static std::string rpcWarmupStatus GUARDED_BY(cs_rpcWarmup) = "RPC server started"; /* Timer-creating functions */ static RPCTimerInterface *timerInterface = nullptr; /* Map of name to timer. */ static std::map> deadlineTimers; static bool ExecuteCommand(Config &config, const CRPCCommand &command, const JSONRPCRequest &request, UniValue &result, bool last_handler); struct RPCCommandExecutionInfo { std::string method; int64_t start; }; struct RPCServerInfo { Mutex mutex; std::list active_commands GUARDED_BY(mutex); }; static RPCServerInfo g_rpc_server_info; struct RPCCommandExecution { std::list::iterator it; explicit RPCCommandExecution(const std::string &method) { LOCK(g_rpc_server_info.mutex); it = g_rpc_server_info.active_commands.insert( g_rpc_server_info.active_commands.cend(), {method, GetTimeMicros()}); } ~RPCCommandExecution() { LOCK(g_rpc_server_info.mutex); g_rpc_server_info.active_commands.erase(it); } }; UniValue RPCServer::ExecuteCommand(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup // This is retained from the old RPC implementation because a lot of state // is set during warmup that RPC commands may depend on. This can be // safely removed once global variable usage has been eliminated. { LOCK(cs_rpcWarmup); if (fRPCInWarmup) { throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } } std::string commandName = request.strMethod; { auto commandsReadView = commands.getReadView(); auto iter = commandsReadView->find(commandName); if (iter != commandsReadView.end()) { return iter->second.get()->Execute(request); } } // 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); } void RPCServer::RegisterCommand(std::unique_ptr command) { if (command != nullptr) { const std::string &commandName = command->GetName(); commands.getWriteView()->insert( std::make_pair(commandName, std::move(command))); } } static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; } g_rpcSignals; void RPCServerSignals::OnStarted(std::function slot) { g_rpcSignals.Started.connect(slot); } void RPCServerSignals::OnStopped(std::function slot) { g_rpcSignals.Stopped.connect(slot); } std::string CRPCTable::help(Config &config, const std::string &strCommand, const JSONRPCRequest &helpreq) const { std::string strRet; std::string category; std::set setDone; std::vector> vCommands; for (const auto &entry : mapCommands) { vCommands.push_back( std::make_pair(entry.second.front()->category + entry.first, entry.second.front())); } sort(vCommands.begin(), vCommands.end()); JSONRPCRequest jreq(helpreq); jreq.fHelp = true; jreq.params = UniValue(); for (const std::pair &command : vCommands) { const CRPCCommand *pcmd = command.second; std::string strMethod = pcmd->name; if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) { continue; } jreq.strMethod = strMethod; try { UniValue unused_result; if (setDone.insert(pcmd->unique_id).second) { pcmd->actor(config, jreq, unused_result, true /* last_handler */); } } catch (const std::exception &e) { // Help text is returned in an exception std::string strHelp = std::string(e.what()); if (strCommand == "") { if (strHelp.find('\n') != std::string::npos) { strHelp = strHelp.substr(0, strHelp.find('\n')); } if (category != pcmd->category) { if (!category.empty()) { strRet += "\n"; } category = pcmd->category; strRet += "== " + Capitalize(category) + " ==\n"; } } strRet += strHelp + "\n"; } } if (strRet == "") { strRet = strprintf("help: unknown command: %s\n", strCommand); } strRet = strRet.substr(0, strRet.size() - 1); return strRet; } static UniValue help(Config &config, const JSONRPCRequest &jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { throw std::runtime_error(RPCHelpMan{ "help", "List all commands, or get help for a specified command.\n", { {"command", RPCArg::Type::STR, /* default */ "all commands", "The command to get help on"}, }, RPCResult{"\"text\" (string) The help text\n"}, RPCExamples{""}, } .ToString()); } std::string strCommand; if (jsonRequest.params.size() > 0) { strCommand = jsonRequest.params[0].get_str(); } return tableRPC.help(config, strCommand, jsonRequest); } static UniValue stop(const Config &config, const JSONRPCRequest &jsonRequest) { // Accept the deprecated and ignored 'detach' boolean argument // Also accept the hidden 'wait' integer argument (milliseconds) // For instance, 'stop 1000' makes the call wait 1 second before returning // to the client (intended for testing) if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { throw std::runtime_error(RPCHelpMan{ "stop", - "Stop Bitcoin server.", + "\nRequest a graceful shutdown of " PACKAGE_NAME ".", {}, RPCResults{}, RPCExamples{""}, } .ToString()); } // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); if (jsonRequest.params[0].isNum()) { UninterruptibleSleep( std::chrono::milliseconds{jsonRequest.params[0].get_int()}); } - return "Bitcoin server stopping"; + return PACKAGE_NAME " stopping"; } static UniValue uptime(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "uptime", "Returns the total uptime of the server.\n", {}, RPCResult{"ttt (numeric) The number of seconds that the server " "has been running\n"}, RPCExamples{HelpExampleCli("uptime", "") + HelpExampleRpc("uptime", "")}, } .Check(request); return GetTime() - GetStartupTime(); } static UniValue getrpcinfo(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "getrpcinfo", "Returns details of the RPC server.\n", {}, RPCResult{ "{\n" " \"active_commands\" (array) All active commands\n" " [\n" " { (object) Information about an active command\n" " \"method\" (string) The name of the RPC command \n" " \"duration\" (numeric) The running time in microseconds\n" " },...\n" " ],\n" " \"logpath\": \"xxx\" (string) The complete file path to the " "debug log\n" "}\n"}, RPCExamples{HelpExampleCli("getrpcinfo", "") + HelpExampleRpc("getrpcinfo", "")}, } .Check(request); LOCK(g_rpc_server_info.mutex); UniValue active_commands(UniValue::VARR); for (const RPCCommandExecutionInfo &info : g_rpc_server_info.active_commands) { UniValue entry(UniValue::VOBJ); entry.pushKV("method", info.method); entry.pushKV("duration", GetTimeMicros() - info.start); active_commands.push_back(entry); } UniValue result(UniValue::VOBJ); result.pushKV("active_commands", active_commands); const std::string path = LogInstance().m_file_path.string(); UniValue log_path(UniValue::VSTR, path); result.pushKV("logpath", log_path); return result; } // clang-format off static const CRPCCommand vRPCCommands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- /* Overall control/query calls */ { "control", "getrpcinfo", getrpcinfo, {} }, { "control", "help", help, {"command"} }, { "control", "stop", stop, {"wait"} }, { "control", "uptime", uptime, {} }, }; // clang-format on CRPCTable::CRPCTable() { unsigned int vcidx; for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) { const CRPCCommand *pcmd; pcmd = &vRPCCommands[vcidx]; mapCommands[pcmd->name].push_back(pcmd); } } bool CRPCTable::appendCommand(const std::string &name, const CRPCCommand *pcmd) { if (IsRPCRunning()) { return false; } mapCommands[name].push_back(pcmd); return true; } bool CRPCTable::removeCommand(const std::string &name, const CRPCCommand *pcmd) { auto it = mapCommands.find(name); if (it != mapCommands.end()) { auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd); if (it->second.end() != new_end) { it->second.erase(new_end, it->second.end()); return true; } } return false; } void StartRPC() { LogPrint(BCLog::RPC, "Starting RPC\n"); g_rpc_running = true; g_rpcSignals.Started(); } void InterruptRPC() { LogPrint(BCLog::RPC, "Interrupting RPC\n"); // Interrupt e.g. running longpolls g_rpc_running = false; } void StopRPC() { LogPrint(BCLog::RPC, "Stopping RPC\n"); deadlineTimers.clear(); DeleteAuthCookie(); g_rpcSignals.Stopped(); } bool IsRPCRunning() { return g_rpc_running; } void SetRPCWarmupStatus(const std::string &newStatus) { LOCK(cs_rpcWarmup); rpcWarmupStatus = newStatus; } void SetRPCWarmupFinished() { LOCK(cs_rpcWarmup); assert(fRPCInWarmup); fRPCInWarmup = false; } bool RPCIsInWarmup(std::string *outStatus) { LOCK(cs_rpcWarmup); if (outStatus) { *outStatus = rpcWarmupStatus; } return fRPCInWarmup; } bool IsDeprecatedRPCEnabled(const ArgsManager &args, const std::string &method) { const std::vector enabled_methods = args.GetArgs("-deprecatedrpc"); return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end(); } static UniValue JSONRPCExecOne(Config &config, RPCServer &rpcServer, JSONRPCRequest jreq, const UniValue &req) { UniValue rpc_result(UniValue::VOBJ); try { jreq.parse(req); UniValue result = rpcServer.ExecuteCommand(config, jreq); rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); } catch (const UniValue &objError) { rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); } catch (const std::exception &e) { rpc_result = JSONRPCReplyObj( NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); } return rpc_result; } 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, rpcServer, jreq, vReq[i])); } 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 &argNamePattern : argNames) { std::vector vargNames; boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|")); auto fr = argsIn.end(); for (const std::string &argName : vargNames) { fr = argsIn.find(argName); if (fr != argsIn.end()) { break; } } 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 { LOCK(cs_rpcWarmup); if (fRPCInWarmup) { throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } } // Find method auto it = mapCommands.find(request.strMethod); if (it != mapCommands.end()) { UniValue result; for (const auto &command : it->second) { if (ExecuteCommand(config, *command, request, result, &command == &it->second.back())) { return result; } } } throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); } static bool ExecuteCommand(Config &config, const CRPCCommand &command, const JSONRPCRequest &request, UniValue &result, bool last_handler) { try { RPCCommandExecution execution(request.strMethod); // Execute, convert arguments to array if necessary if (request.params.isObject()) { return command.actor( config, transformNamedArguments(request, command.argNames), result, last_handler); } else { return command.actor(config, request, result, last_handler); } } catch (const std::exception &e) { throw JSONRPCError(RPC_MISC_ERROR, e.what()); } } std::vector CRPCTable::listCommands() const { std::vector commandList; for (const auto &i : mapCommands) { commandList.emplace_back(i.first); } return commandList; } void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface) { if (!timerInterface) { timerInterface = iface; } } void RPCSetTimerInterface(RPCTimerInterface *iface) { timerInterface = iface; } void RPCUnsetTimerInterface(RPCTimerInterface *iface) { if (timerInterface == iface) { timerInterface = nullptr; } } void RPCRunLater(const std::string &name, std::function func, int64_t nSeconds) { if (!timerInterface) { throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); } deadlineTimers.erase(name); LogPrint(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); deadlineTimers.emplace( name, std::unique_ptr( timerInterface->NewTimer(func, nSeconds * 1000))); } int RPCSerializationFlags() { return 0; } CRPCTable tableRPC;