mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-07 22:28:02 +02:00
bitcoin-cli: Add -ipcconnect option
This implements an idea from Pieter Wuille <pieter@wuille.net> https://github.com/bitcoin/bitcoin/issues/28722#issuecomment-2807026958 to allow `bitcoin-cli` to connect to the node via IPC instead of TCP, if the `ENABLE_IPC` cmake option is enabled and the node has been started with `-ipcbind`. The feature can be tested with: build/bin/bitcoin-node -regtest -ipcbind=unix -debug=ipc build/bin/bitcoin-cli -regtest -ipcconnect=unix -getinfo The `-ipconnect` parameter can also be omitted, since this change also makes `bitcoin-cli` prefer IPC over HTTP by default, and falling back to HTTP if an IPC connection can't be established.
This commit is contained in:
@@ -11,6 +11,9 @@
|
||||
#include <common/system.h>
|
||||
#include <compat/compat.h>
|
||||
#include <compat/stdin.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/rpc.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <rpc/client.h>
|
||||
#include <rpc/mining.h>
|
||||
@@ -108,6 +111,7 @@ static void SetupCliArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-stdinrpcpass", "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. When combined with -stdinwalletpassphrase, -stdinrpcpass consumes the first line, and -stdinwalletpassphrase consumes the second.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-stdinwalletpassphrase", "Read wallet passphrase from standard input as a single line. When combined with -stdin, the first line from standard input is used for the wallet passphrase.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-ipcconnect=<address>", "Connect to bitcoin-node through IPC socket instead of TCP socket to execute requests. Valid <address> values are 'auto' to try to connect to default socket path at <datadir>/node.sock but fall back to TCP if it is not available, 'unix' to connect to the default socket and fail if it isn't available, or 'unix:<socket path>' to connect to a socket at a nonstandard path. -noipcconnect can be specified to avoid attempting to use IPC at all. Default value: auto", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
|
||||
}
|
||||
|
||||
std::optional<std::string> RpcWalletName(const ArgsManager& args)
|
||||
@@ -792,7 +796,40 @@ struct DefaultRequestHandler : BaseRequestHandler {
|
||||
}
|
||||
};
|
||||
|
||||
static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
|
||||
static std::optional<UniValue> CallIPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::string& endpoint, const std::string& username)
|
||||
{
|
||||
auto ipcconnect{gArgs.GetArg("-ipcconnect", "auto")};
|
||||
if (ipcconnect == "0") return {}; // Do not attempt IPC if -ipcconnect is disabled.
|
||||
if (gArgs.IsArgSet("-rpcconnect") && !gArgs.IsArgNegated("-rpcconnect")) {
|
||||
if (ipcconnect == "auto") return {}; // Use HTTP if -ipcconnect=auto is set and -rpcconnect is enabled.
|
||||
throw std::runtime_error("-rpcconnect and -ipcconnect options cannot both be enabled");
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::Init> local_init{interfaces::MakeBasicInit("bitcoin-cli")};
|
||||
if (!local_init || !local_init->ipc()) {
|
||||
if (ipcconnect == "auto") return {}; // Use HTTP if -ipcconnect=auto is set and there is no IPC support.
|
||||
throw std::runtime_error("bitcoin-cli was not built with IPC support");
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::Init> node_init;
|
||||
try {
|
||||
node_init = local_init->ipc()->connectAddress(ipcconnect);
|
||||
if (!node_init) return {}; // Fall back to HTTP if -ipcconnect=auto connect failed.
|
||||
} catch (const std::exception& e) {
|
||||
// Catch connect error if -ipcconnect=unix was specified
|
||||
throw CConnectionFailed{strprintf("%s\n\n"
|
||||
"Probably bitcoin-node is not running or not listening on a unix socket. Can be started with:\n\n"
|
||||
" bitcoin-node -chain=%s -ipcbind=unix", e.what(), gArgs.GetChainTypeString())};
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::Rpc> rpc{node_init->makeRpc()};
|
||||
assert(rpc);
|
||||
UniValue request{rh->PrepareRequest(strMethod, args)};
|
||||
UniValue reply{rpc->executeRpc(std::move(request), endpoint, username)};
|
||||
return rh->ProcessReply(reply);
|
||||
}
|
||||
|
||||
static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::string& endpoint, const std::string& username)
|
||||
{
|
||||
std::string host;
|
||||
// In preference order, we choose the following for the port:
|
||||
@@ -873,7 +910,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
|
||||
failedToGetAuthCookie = true;
|
||||
}
|
||||
} else {
|
||||
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
||||
strRPCUserColonPass = username + ":" + gArgs.GetArg("-rpcpassword", "");
|
||||
}
|
||||
|
||||
struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
|
||||
@@ -889,17 +926,6 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
|
||||
assert(output_buffer);
|
||||
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
|
||||
|
||||
// check if we should use a special wallet endpoint
|
||||
std::string endpoint = "/";
|
||||
if (rpcwallet) {
|
||||
char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->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.release(), EVHTTP_REQ_POST, endpoint.c_str());
|
||||
if (r != 0) {
|
||||
throw CConnectionFailed("send http request failed");
|
||||
@@ -959,9 +985,26 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
|
||||
const int timeout = gArgs.GetIntArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT);
|
||||
const auto deadline{std::chrono::steady_clock::now() + 1s * timeout};
|
||||
|
||||
// check if we should use a special wallet endpoint
|
||||
std::string endpoint = "/";
|
||||
if (rpcwallet) {
|
||||
char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
|
||||
if (encodedURI) {
|
||||
endpoint = "/wallet/" + std::string(encodedURI);
|
||||
free(encodedURI);
|
||||
} else {
|
||||
throw CConnectionFailed("uri-encode failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::string username{gArgs.GetArg("-rpcuser", "")};
|
||||
do {
|
||||
try {
|
||||
response = CallRPC(rh, strMethod, args, rpcwallet);
|
||||
if (auto ipc_response{CallIPC(rh, strMethod, args, endpoint, username)}) {
|
||||
response = std::move(*ipc_response);
|
||||
} else {
|
||||
response = CallRPC(rh, strMethod, args, endpoint, username);
|
||||
}
|
||||
if (fWait) {
|
||||
const UniValue& error = response.find_value("error");
|
||||
if (!error.isNull() && error["code"].getInt<int>() == RPC_IN_WARMUP) {
|
||||
|
||||
Reference in New Issue
Block a user