rpc: Handle `getinfo` client-side in bitcoin-cli w/ `-getinfo` #8843

Closed
wants to merge 1 commit into
from
Jump to file or symbol
Failed to load files and symbols.
+118 −12
Split
View
@@ -46,6 +46,7 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT));
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("-getinfo", _("Get general information from the remote server"));
@jnewbery

jnewbery Jul 10, 2017

Member

nit: please place alphabetically within help messages.

return strUsage;
}
@@ -189,7 +190,93 @@ static void http_error_cb(enum evhttp_request_error err, void *ctx)
}
#endif
-UniValue CallRPC(const std::string& strMethod, const UniValue& params)
+/** 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& strMethod, const std::vector<std::string>& 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& strMethod, const std::vector<std::string>& 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<UniValue> 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("keypoololdest", batch[ID_WALLETINFO]["result"]["keypoololdest"]);
+ result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
@jnewbery

jnewbery Jul 10, 2017

Member

slight incompatibility here since HD split. For HD split wallets, keypoolsize refers to the size of the external keypool, and there's a new field returned in getwalletinfo called keypoolsize_hd_internal, which refers to the size of the internal keypool. This implementation should return the sum of those two if keypoolsize_hd_internal exists.

Otherwise:

→ bitcoin-cli getinfo
{
  "version": 149900,
...
  "keypoolsize": 199,
...
}

→ bitcoin-cli -getinfo
{
  "version": 149900,
...
  "keypoolsize": 99,
...
}
@jnewbery

jnewbery Jul 16, 2017

Member

Actually, I've got this backwards. getinfo should be updated to be consistent with getwalletinfo, not making -getinfo consistent with the incorrect getinfo.

I think that can be done as a separate commit in this PR. Let me know if you want me to make a branch with that commit.

+ 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("errors", batch[ID_NETWORKINFO]["result"]["warnings"]);
+ return JSONRPCReplyObj(result, NullUniValue, 1);
+ }
+};
+
+/** Process default single requests */
+class DefaultRequestHandler: public BaseRequestHandler {
+public:
+ UniValue PrepareRequest(const std::string& strMethod, const std::vector<std::string>& args) override
+ {
+ UniValue params;
+ if(GetBoolArg("-named", DEFAULT_NAMED)) {
+ params = RPCConvertNamedValues(strMethod, args);
+ } else {
+ params = RPCConvertValues(strMethod, args);
+ }
+ return JSONRPCRequestObj(strMethod, params, 1);
+ }
+
+ UniValue ProcessReply(const UniValue &reply) override
+ {
+ return reply.get_obj();
+ }
+};
+
+UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, const std::vector<std::string>& args)
{
std::string host = GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
int port = GetArg("-rpcport", BaseParams().RPCPort());
@@ -230,8 +317,8 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params)
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";
- struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
+ std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
+ struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req.get());
@jnewbery

jnewbery Jul 10, 2017

Member

nit: why add a space here?

assert(output_buffer);
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
@@ -256,7 +343,7 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params)
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");
@@ -280,23 +367,24 @@ int CommandLineRPC(int argc, char *argv[])
while (std::getline(std::cin,line))
args.push_back(line);
}
+ std::unique_ptr<BaseRequestHandler> rh;
+ if (GetBoolArg("-getinfo", false)) {
+ rh.reset(new GetinfoRequestHandler());
+ args.clear();
+ args.push_back("getinfo");
+ } else {
+ rh.reset(new DefaultRequestHandler());
+ }
if (args.size() < 1)
throw std::runtime_error("too few parameters (need at least command)");
std::string strMethod = args[0];
args.erase(args.begin()); // Remove trailing method name from arguments vector
- UniValue params;
- if(GetBoolArg("-named", DEFAULT_NAMED)) {
- params = RPCConvertNamedValues(strMethod, args);
- } else {
- params = RPCConvertValues(strMethod, args);
- }
-
// Execute and handle connection failures with -rpcwait
const bool fWait = GetBoolArg("-rpcwait", false);
do {
try {
- const UniValue reply = CallRPC(strMethod, params);
+ const UniValue reply = CallRPC(rh.get(), strMethod, args);
// Parse reply
const UniValue& result = find_value(reply, "result");
View
@@ -124,3 +124,19 @@ void DeleteAuthCookie()
}
}
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num)
+{
+ if (!in.isArray())
@jnewbery

jnewbery Jul 10, 2017

Member

nit: new code so should use braces for if blocks (sorry!)

+ throw std::runtime_error("Batch must be an array");
+ std::vector<UniValue> 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;
+}
View
@@ -97,5 +97,7 @@ bool GenerateAuthCookie(std::string *cookie_out);
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
+/** Parse JSON-RPC batch reply into a vector */
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num);
#endif // BITCOIN_RPCPROTOCOL_H