diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 5542a3897..1e28f17c8 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -1,485 +1,598 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 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 "config/bitcoin-config.h" #endif #include "chainparamsbase.h" #include "clientversion.h" #include "fs.h" #include "rpc/client.h" #include "rpc/protocol.h" #include "support/events.h" #include "util.h" #include "utilstrencodings.h" #include #include #include #include #include static const char DEFAULT_RPCCONNECT[] = "127.0.0.1"; static const int DEFAULT_HTTP_CLIENT_TIMEOUT = 900; static const bool DEFAULT_NAMED = false; static const int CONTINUE_EXECUTION = -1; std::string HelpMessageCli() { const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); std::string strUsage; strUsage += HelpMessageGroup(_("Options:")); strUsage += HelpMessageOpt("-?", _("This help message")); strUsage += HelpMessageOpt( "-conf=", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME)); strUsage += HelpMessageOpt("-datadir=", _("Specify data directory")); + strUsage += HelpMessageOpt( + "-getinfo", + _("Get general information from the remote server. Note that unlike " + "server-side RPC calls, the results of -getinfo is the result of " + "multiple non-atomic requests. Some entries in the result may " + "represent results from different states (e.g. wallet balance may be " + "as of a different block from the chain state reported)")); AppendParamsHelpMessages(strUsage); strUsage += HelpMessageOpt( "-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED)); strUsage += HelpMessageOpt( "-rpcconnect=", strprintf(_("Send commands to node running on (default: %s)"), DEFAULT_RPCCONNECT)); strUsage += HelpMessageOpt( "-rpcport=", strprintf( _("Connect to JSON-RPC on (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort())); strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); strUsage += HelpMessageOpt("-rpcuser=", _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcclienttimeout=", strprintf(_("Timeout in seconds during HTTP requests, " "or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); strUsage += HelpMessageOpt( "-stdinrpcpass", strprintf(_("Read RPC password from standard input as a single line. " "When combined with -stdin, the first line from standard " "input is used for the RPC password."))); strUsage += HelpMessageOpt( "-stdin", _("Read extra arguments from standard input, one per line " "until EOF/Ctrl-D (recommended for sensitive information " "such as passphrases)")); strUsage += HelpMessageOpt( "-rpcwallet=", _("Send RPC for non-default wallet on RPC server (argument is wallet " "filename in bitcoind directory, required if bitcoind/-Qt runs with " "multiple wallets)")); return strUsage; } ////////////////////////////////////////////////////////////////////////////// // // Start // // // Exception thrown on connection error. This error is used to determine when // to wait if -rpcwait is given. // class CConnectionFailed : public std::runtime_error { public: explicit inline CConnectionFailed(const std::string &msg) : std::runtime_error(msg) {} }; // // This function returns either one of EXIT_ codes when it's expected to stop // the process or CONTINUE_EXECUTION when it's expected to continue further. // static int AppInitRPC(int argc, char *argv[]) { // // Parameters // gArgs.ParseParameters(argc, argv); if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = strprintf(_("%s RPC client version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n"; if (!gArgs.IsArgSet("-version")) { strUsage += "\n" + _("Usage:") + "\n" + " bitcoin-cli [options] [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" + " bitcoin-cli [options] -named [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" + " bitcoin-cli [options] help " + _("List commands") + "\n" + " bitcoin-cli [options] help " + _("Get help for a command") + "\n"; strUsage += "\n" + HelpMessageCli(); } fprintf(stdout, "%s", strUsage.c_str()); if (argc < 2) { fprintf(stderr, "Error: too few parameters\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } if (!fs::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } try { gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); } catch (const std::exception &e) { fprintf(stderr, "Error reading configuration file: %s\n", e.what()); return EXIT_FAILURE; } // Check for -testnet or -regtest parameter (BaseParams() calls are only // valid after this clause) try { SelectBaseParams(gArgs.GetChainName()); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return EXIT_FAILURE; } if (gArgs.GetBoolArg("-rpcssl", false)) { fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n"); return EXIT_FAILURE; } return CONTINUE_EXECUTION; } /** Reply structure for request_done to fill in */ struct HTTPReply { HTTPReply() : status(0), error(-1) {} int status; int error; std::string body; }; const char *http_errorstring(int code) { switch (code) { #if LIBEVENT_VERSION_NUMBER >= 0x02010300 case EVREQ_HTTP_TIMEOUT: return "timeout reached"; case EVREQ_HTTP_EOF: return "EOF reached"; case EVREQ_HTTP_INVALID_HEADER: return "error while reading header, or invalid header"; case EVREQ_HTTP_BUFFER_ERROR: return "error encountered while reading or writing"; case EVREQ_HTTP_REQUEST_CANCEL: return "request was canceled"; case EVREQ_HTTP_DATA_TOO_LONG: return "response body is larger than allowed"; #endif default: return "unknown"; } } static void http_request_done(struct evhttp_request *req, void *ctx) { HTTPReply *reply = static_cast(ctx); if (req == nullptr) { /** * If req is nullptr, it means an error occurred while connecting: the * error code will have been passed to http_error_cb. */ reply->status = 0; return; } reply->status = evhttp_request_get_response_code(req); struct evbuffer *buf = evhttp_request_get_input_buffer(req); if (buf) { size_t size = evbuffer_get_length(buf); const char *data = (const char *)evbuffer_pullup(buf, size); if (data) reply->body = std::string(data, size); evbuffer_drain(buf, size); } } #if LIBEVENT_VERSION_NUMBER >= 0x02010300 static void http_error_cb(enum evhttp_request_error err, void *ctx) { HTTPReply *reply = static_cast(ctx); reply->error = err; } #endif -static UniValue CallRPC(const std::string &strMethod, const UniValue ¶ms) { +/** + * Class that handles the conversion from a command-line to a JSON-RPC request, + * as well as converting back to a JSON object that can be shown as result. + */ +class BaseRequestHandler { +public: + virtual UniValue PrepareRequest(const std::string &method, + const std::vector &args) = 0; + virtual UniValue ProcessReply(const UniValue &batch_in) = 0; +}; + +/** Process getinfo requests */ +class GetinfoRequestHandler : public BaseRequestHandler { +public: + const int ID_NETWORKINFO = 0; + const int ID_BLOCKCHAININFO = 1; + const int ID_WALLETINFO = 2; + + /** Create a simulated `getinfo` request. */ + UniValue PrepareRequest(const std::string &method, + const std::vector &args) override { + UniValue result(UniValue::VARR); + result.push_back( + JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO)); + result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, + ID_BLOCKCHAININFO)); + result.push_back( + JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO)); + return result; + } + + /** Collect values from the batch and form a simulated `getinfo` reply. */ + UniValue ProcessReply(const UniValue &batch_in) override { + UniValue result(UniValue::VOBJ); + std::vector batch = JSONRPCProcessBatchReply(batch_in, 3); + // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass + // them on getwalletinfo() is allowed to fail in case there is no + // wallet. + if (!batch[ID_NETWORKINFO]["error"].isNull()) { + return batch[ID_NETWORKINFO]; + } + if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) { + return batch[ID_BLOCKCHAININFO]; + } + result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]); + result.pushKV("protocolversion", + batch[ID_NETWORKINFO]["result"]["protocolversion"]); + if (!batch[ID_WALLETINFO].isNull()) { + result.pushKV("walletversion", + batch[ID_WALLETINFO]["result"]["walletversion"]); + result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); + } + result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]); + result.pushKV("timeoffset", + batch[ID_NETWORKINFO]["result"]["timeoffset"]); + result.pushKV("connections", + batch[ID_NETWORKINFO]["result"]["connections"]); + result.pushKV("proxy", + batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]); + result.pushKV("difficulty", + batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); + result.pushKV( + "testnet", + UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"].get_str() == + "test")); + if (!batch[ID_WALLETINFO].isNull()) { + result.pushKV("walletversion", + batch[ID_WALLETINFO]["result"]["walletversion"]); + result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); + result.pushKV("keypoololdest", + batch[ID_WALLETINFO]["result"]["keypoololdest"]); + result.pushKV("keypoolsize", + batch[ID_WALLETINFO]["result"]["keypoolsize"]); + if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) { + result.pushKV("unlocked_until", + batch[ID_WALLETINFO]["result"]["unlocked_until"]); + } + result.pushKV("paytxfee", + batch[ID_WALLETINFO]["result"]["paytxfee"]); + } + result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]); + result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]); + return JSONRPCReplyObj(result, NullUniValue, 1); + } +}; + +/** Process default single requests */ +class DefaultRequestHandler : public BaseRequestHandler { +public: + UniValue PrepareRequest(const std::string &method, + const std::vector &args) override { + UniValue params; + if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) { + params = RPCConvertNamedValues(method, args); + } else { + params = RPCConvertValues(method, args); + } + return JSONRPCRequestObj(method, params, 1); + } + + UniValue ProcessReply(const UniValue &reply) override { + return reply.get_obj(); + } +}; + +static UniValue CallRPC(BaseRequestHandler *rh, const std::string &strMethod, + const std::vector &args) { std::string host; // In preference order, we choose the following for the port: // 1. -rpcport // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6) // 3. default port for chain int port = BaseParams().RPCPort(); SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host); port = gArgs.GetArg("-rpcport", port); // Obtain event base raii_event_base base = obtain_event_base(); // Synchronously look up hostname raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); evhttp_connection_set_timeout( evcon.get(), gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT)); HTTPReply response; raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void *)&response); if (req == nullptr) throw std::runtime_error("create http request failed"); #if LIBEVENT_VERSION_NUMBER >= 0x02010300 evhttp_request_set_error_cb(req.get(), http_error_cb); #endif // Get credentials std::string strRPCUserColonPass; if (gArgs.GetArg("-rpcpassword", "") == "") { // Try fall back to cookie-based authentication if no password is // provided if (!GetAuthCookie(&strRPCUserColonPass)) { throw std::runtime_error(strprintf( _("Could not locate RPC credentials. No authentication cookie " "could be found, and RPC password is not set. See " "-rpcpassword and -stdinrpcpass. Configuration file: (%s)"), GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)) .string() .c_str())); } } else { strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req.get()); assert(output_headers); evhttp_add_header(output_headers, "Host", host.c_str()); evhttp_add_header(output_headers, "Connection", "close"); evhttp_add_header( output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); // Attach request data - std::string strRequest = - JSONRPCRequestObj(strMethod, params, 1).write() + "\n"; + std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n"; struct evbuffer *output_buffer = evhttp_request_get_output_buffer(req.get()); assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); // check if we should use a special wallet endpoint std::string endpoint = "/"; std::string walletName = gArgs.GetArg("-rpcwallet", ""); if (!walletName.empty()) { char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false); if (encodedURI) { endpoint = "/wallet/" + std::string(encodedURI); free(encodedURI); } else { throw CConnectionFailed("uri-encode failed"); } } int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str()); // ownership moved to evcon in above call req.release(); if (r != 0) { throw CConnectionFailed("send http request failed"); } event_base_dispatch(base.get()); if (response.status == 0) { throw CConnectionFailed(strprintf( "couldn't connect to server: %s (code %d)\n(make sure server is " "running and you are connecting to the correct RPC port)", http_errorstring(response.error), response.error)); } else if (response.status == HTTP_UNAUTHORIZED) { throw std::runtime_error( "incorrect rpcuser or rpcpassword (authorization failed)"); } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) { throw std::runtime_error( strprintf("server returned HTTP error %d", response.status)); } else if (response.body.empty()) { throw std::runtime_error("no response from server"); } // Parse reply UniValue valReply(UniValue::VSTR); if (!valReply.read(response.body)) { throw std::runtime_error("couldn't parse reply from server"); } - const UniValue &reply = valReply.get_obj(); + const UniValue reply = rh->ProcessReply(valReply); if (reply.empty()) { throw std::runtime_error( "expected reply to have result, error and id properties"); } return reply; } int CommandLineRPC(int argc, char *argv[]) { std::string strPrint; int nRet = 0; try { // Skip switches while (argc > 1 && IsSwitchChar(argv[1][0])) { argc--; argv++; } std::string rpcPass; if (gArgs.GetBoolArg("-stdinrpcpass", false)) { if (!std::getline(std::cin, rpcPass)) throw std::runtime_error("-stdinrpcpass specified but failed " "to read from standard input"); gArgs.ForceSetArg("-rpcpassword", rpcPass); } std::vector args = std::vector(&argv[1], &argv[argc]); if (gArgs.GetBoolArg("-stdin", false)) { // Read one arg per line from stdin and append std::string line; while (std::getline(std::cin, line)) { args.push_back(line); } } - if (args.size() < 1) { - throw std::runtime_error( - "too few parameters (need at least command)"); - } - std::string strMethod = args[0]; - // Remove trailing method name from arguments vector - args.erase(args.begin()); - - UniValue params; - if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) { - params = RPCConvertNamedValues(strMethod, args); + std::unique_ptr rh; + std::string method; + if (gArgs.GetBoolArg("-getinfo", false)) { + rh.reset(new GetinfoRequestHandler()); + method = ""; } else { - params = RPCConvertValues(strMethod, args); + rh.reset(new DefaultRequestHandler()); + if (args.size() < 1) { + throw std::runtime_error( + "too few parameters (need at least command)"); + } + method = args[0]; + // Remove trailing method name from arguments vector + args.erase(args.begin()); } // Execute and handle connection failures with -rpcwait const bool fWait = gArgs.GetBoolArg("-rpcwait", false); do { try { - const UniValue reply = CallRPC(strMethod, params); + const UniValue reply = CallRPC(rh.get(), method, args); // Parse reply const UniValue &result = find_value(reply, "result"); const UniValue &error = find_value(reply, "error"); if (!error.isNull()) { // Error int code = error["code"].get_int(); if (fWait && code == RPC_IN_WARMUP) throw CConnectionFailed("server in warmup"); strPrint = "error: " + error.write(); nRet = abs(code); if (error.isObject()) { UniValue errCode = find_value(error, "code"); UniValue errMsg = find_value(error, "message"); strPrint = errCode.isNull() ? "" : "error code: " + errCode.getValStr() + "\n"; if (errMsg.isStr()) { strPrint += "error message:\n" + errMsg.get_str(); } if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) { strPrint += "\nTry adding " "\"-rpcwallet=\" option to " "bitcoin-cli command line."; } } } else { // Result if (result.isNull()) { strPrint = ""; } else if (result.isStr()) { strPrint = result.get_str(); } else { strPrint = result.write(2); } } // Connection succeeded, no need to retry. break; } catch (const CConnectionFailed &) { if (fWait) { MilliSleep(1000); } else { throw; } } } while (fWait); } catch (const boost::thread_interrupted &) { throw; } catch (const std::exception &e) { strPrint = std::string("error: ") + e.what(); nRet = EXIT_FAILURE; } catch (...) { PrintExceptionContinue(nullptr, "CommandLineRPC()"); throw; } if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; } int main(int argc, char *argv[]) { SetupEnvironment(); if (!SetupNetworking()) { fprintf(stderr, "Error: Initializing networking failed\n"); return EXIT_FAILURE; } try { int ret = AppInitRPC(argc, argv); if (ret != CONTINUE_EXECUTION) { return ret; } } catch (const std::exception &e) { PrintExceptionContinue(&e, "AppInitRPC()"); return EXIT_FAILURE; } catch (...) { PrintExceptionContinue(nullptr, "AppInitRPC()"); return EXIT_FAILURE; } int ret = EXIT_FAILURE; try { ret = CommandLineRPC(argc, argv); } catch (const std::exception &e) { PrintExceptionContinue(&e, "CommandLineRPC()"); } catch (...) { PrintExceptionContinue(nullptr, "CommandLineRPC()"); } return ret; } diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp index cec46ea7a..5dd2ee09f 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/protocol.cpp @@ -1,129 +1,148 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "rpc/protocol.h" #include "random.h" #include "tinyformat.h" #include "util.h" #include "utilstrencodings.h" #include "utiltime.h" #include "version.h" #include #include /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, but * uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were * unspecified (HTTP errors and contents of 'error'). * * 1.0 spec: http://json-rpc.org/wiki/specification * 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html */ UniValue JSONRPCRequestObj(const std::string &strMethod, const UniValue ¶ms, const UniValue &id) { UniValue request(UniValue::VOBJ); request.pushKV("method", strMethod); request.pushKV("params", params); request.pushKV("id", id); return request; } UniValue JSONRPCReplyObj(const UniValue &result, const UniValue &error, const UniValue &id) { UniValue reply(UniValue::VOBJ); if (!error.isNull()) { reply.pushKV("result", NullUniValue); } else { reply.pushKV("result", result); } reply.pushKV("error", error); reply.pushKV("id", id); return reply; } std::string JSONRPCReply(const UniValue &result, const UniValue &error, const UniValue &id) { UniValue reply = JSONRPCReplyObj(result, error, id); return reply.write() + "\n"; } UniValue JSONRPCError(int code, const std::string &message) { UniValue error(UniValue::VOBJ); error.pushKV("code", code); error.pushKV("message", message); return error; } /** Username used when cookie authentication is in use (arbitrary, only for * recognizability in debugging/logging purposes) */ static const std::string COOKIEAUTH_USER = "__cookie__"; /** Default name for auth cookie file */ static const std::string COOKIEAUTH_FILE = ".cookie"; fs::path GetAuthCookieFile() { fs::path path(gArgs.GetArg("-rpccookiefile", COOKIEAUTH_FILE)); if (!path.is_complete()) { path = GetDataDir() / path; } return path; } bool GenerateAuthCookie(std::string *cookie_out) { const size_t COOKIE_SIZE = 32; uint8_t rand_pwd[COOKIE_SIZE]; GetRandBytes(rand_pwd, COOKIE_SIZE); std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd, rand_pwd + COOKIE_SIZE); /** the umask determines what permissions are used to create this file - * these are set to 077 in init.cpp unless overridden with -sysperms. */ std::ofstream file; fs::path filepath = GetAuthCookieFile(); file.open(filepath.string().c_str()); if (!file.is_open()) { LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string()); return false; } file << cookie; file.close(); LogPrintf("Generated RPC authentication cookie %s\n", filepath.string()); if (cookie_out) { *cookie_out = cookie; } return true; } bool GetAuthCookie(std::string *cookie_out) { std::ifstream file; std::string cookie; fs::path filepath = GetAuthCookieFile(); file.open(filepath.string().c_str()); if (!file.is_open()) { return false; } std::getline(file, cookie); file.close(); if (cookie_out) { *cookie_out = cookie; } return true; } void DeleteAuthCookie() { try { fs::remove(GetAuthCookieFile()); } catch (const fs::filesystem_error &e) { LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what()); } } + +std::vector JSONRPCProcessBatchReply(const UniValue &in, size_t num) { + if (!in.isArray()) { + throw std::runtime_error("Batch must be an array"); + } + std::vector batch(num); + for (size_t i = 0; i < in.size(); ++i) { + const UniValue &rec = in[i]; + if (!rec.isObject()) { + throw std::runtime_error("Batch member must be object"); + } + size_t id = rec["id"].get_int(); + if (id >= num) { + throw std::runtime_error("Batch member id larger than size"); + } + batch[id] = rec; + } + return batch; +} diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 21bcce942..aa700042f 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -1,139 +1,141 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_RPCPROTOCOL_H #define BITCOIN_RPCPROTOCOL_H #include "fs.h" #include #include #include #include #include //! HTTP status codes enum HTTPStatusCode { HTTP_OK = 200, HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, HTTP_NOT_FOUND = 404, HTTP_BAD_METHOD = 405, HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_SERVICE_UNAVAILABLE = 503, }; //! Bitcoin RPC error codes enum RPCErrorCode { //! Standard JSON-RPC 2.0 errors // RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400). // It should not be used for application-layer errors. RPC_INVALID_REQUEST = -32600, // RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404). // It should not be used for application-layer errors. RPC_METHOD_NOT_FOUND = -32601, RPC_INVALID_PARAMS = -32602, // RPC_INTERNAL_ERROR should only be used for genuine errors in bitcoind // (for exampled datadir corruption). RPC_INTERNAL_ERROR = -32603, RPC_PARSE_ERROR = -32700, //! General application defined errors //!< std::exception thrown in command handling RPC_MISC_ERROR = -1, //!< Server is in safe mode, and command is not allowed in safe mode RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Unexpected type was passed as parameter RPC_TYPE_ERROR = -3, //!< Invalid address or key RPC_INVALID_ADDRESS_OR_KEY = -5, //!< Ran out of memory during operation RPC_OUT_OF_MEMORY = -7, //!< Invalid, missing or duplicate parameter RPC_INVALID_PARAMETER = -8, //!< Database error RPC_DATABASE_ERROR = -20, //!< Error parsing or validating structure in raw format RPC_DESERIALIZATION_ERROR = -22, //!< General error during transaction or block submission RPC_VERIFY_ERROR = -25, //!< Transaction or block was rejected by network rules RPC_VERIFY_REJECTED = -26, //!< Transaction already in chain RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Client still warming up RPC_IN_WARMUP = -28, //!< RPC method is deprecated RPC_METHOD_DEPRECATED = -32, //! Aliases for backward compatibility RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR, RPC_TRANSACTION_REJECTED = RPC_VERIFY_REJECTED, RPC_TRANSACTION_ALREADY_IN_CHAIN = RPC_VERIFY_ALREADY_IN_CHAIN, //! P2P client errors //!< Bitcoin is not connected RPC_CLIENT_NOT_CONNECTED = -9, //!< Still downloading initial blocks RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, //!< Node is already added RPC_CLIENT_NODE_ALREADY_ADDED = -23, //!< Node has not been added before RPC_CLIENT_NODE_NOT_ADDED = -24, //!< Node to disconnect not found in connected nodes RPC_CLIENT_NODE_NOT_CONNECTED = -29, //!< Invalid IP/Subnet RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //!< No valid connection manager instance found RPC_CLIENT_P2P_DISABLED = -31, //! Wallet errors //!< Unspecified problem with wallet (key not found etc.) RPC_WALLET_ERROR = -4, //!< Not enough funds in wallet or account RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Invalid label name RPC_WALLET_INVALID_LABEL_NAME = -11, //!< Keypool ran out, call keypoolrefill first RPC_WALLET_KEYPOOL_RAN_OUT = -12, //!< Enter the wallet passphrase with walletpassphrase first RPC_WALLET_UNLOCK_NEEDED = -13, //!< The wallet passphrase entered was incorrect RPC_WALLET_PASSPHRASE_INCORRECT = -14, //!< Command given in wrong wallet encryption state (encrypting an encrypted //! wallet etc.) RPC_WALLET_WRONG_ENC_STATE = -15, //!< Failed to encrypt the wallet RPC_WALLET_ENCRYPTION_FAILED = -16, //!< Wallet is already unlocked RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Invalid wallet specified RPC_WALLET_NOT_FOUND = -18, //!< No wallet specified (error when there are multiple wallets loaded) RPC_WALLET_NOT_SPECIFIED = -19, //!< Backwards compatible aliases RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME, }; UniValue JSONRPCRequestObj(const std::string &strMethod, const UniValue ¶ms, const UniValue &id); UniValue JSONRPCReplyObj(const UniValue &result, const UniValue &error, const UniValue &id); std::string JSONRPCReply(const UniValue &result, const UniValue &error, const UniValue &id); UniValue JSONRPCError(int code, const std::string &message); /** Get name of RPC authentication cookie file */ fs::path GetAuthCookieFile(); /** Generate a new RPC authentication cookie and write it to disk */ bool GenerateAuthCookie(std::string *cookie_out); /** Read the RPC authentication cookie from disk */ bool GetAuthCookie(std::string *cookie_out); /** Delete RPC authentication cookie from disk */ void DeleteAuthCookie(); +/** Parse JSON-RPC batch reply into a vector */ +std::vector JSONRPCProcessBatchReply(const UniValue &in, size_t num); #endif // BITCOIN_RPCPROTOCOL_H diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 6b1652213..713b94b70 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -1,51 +1,80 @@ #!/usr/bin/env python3 # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test bitcoin-cli""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_process_error, get_auth_cookie, ) class TestBitcoinCli(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def run_test(self): """Main test logic""" self.log.info( "Compare responses from gewalletinfo RPC and `bitcoin-cli getwalletinfo`") cli_response = self.nodes[0].cli.getwalletinfo() rpc_response = self.nodes[0].getwalletinfo() assert_equal(cli_response, rpc_response) self.log.info( "Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`") cli_response = self.nodes[0].cli.getblockchaininfo() rpc_response = self.nodes[0].getblockchaininfo() assert_equal(cli_response, rpc_response) user, password = get_auth_cookie(self.nodes[0].datadir) self.log.info("Test -stdinrpcpass option") assert_equal(0, self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdinrpcpass', input=password).getblockcount()) assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdinrpcpass', input="foo").echo) self.log.info("Test -stdin and -stdinrpcpass") assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo()) assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input="foo").echo) + self.log.info( + "Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.") + cli_get_info = self.nodes[0].cli('-getinfo').help() + wallet_info = self.nodes[0].getwalletinfo() + network_info = self.nodes[0].getnetworkinfo() + blockchain_info = self.nodes[0].getblockchaininfo() + + assert_equal(cli_get_info['version'], network_info['version']) + assert_equal(cli_get_info['protocolversion'], + network_info['protocolversion']) + assert_equal(cli_get_info['walletversion'], + wallet_info['walletversion']) + assert_equal(cli_get_info['balance'], wallet_info['balance']) + assert_equal(cli_get_info['blocks'], blockchain_info['blocks']) + assert_equal(cli_get_info['timeoffset'], network_info['timeoffset']) + assert_equal(cli_get_info['connections'], network_info['connections']) + assert_equal(cli_get_info['proxy'], + network_info['networks'][0]['proxy']) + assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty']) + assert_equal(cli_get_info['testnet'], + blockchain_info['chain'] == "test") + assert_equal(cli_get_info['balance'], wallet_info['balance']) + assert_equal(cli_get_info['keypoololdest'], + wallet_info['keypoololdest']) + assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize']) + assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee']) + assert_equal(cli_get_info['relayfee'], network_info['relayfee']) + # unlocked_until is not tested because the wallet is not encrypted + if __name__ == '__main__': TestBitcoinCli().main()