Merge bitcoin/bitcoin#32297: bitcoin-cli: Add -ipcconnect option

4565cff72c bitcoin-gui: Implement missing Init::makeMining method (Ryan Ofsky)
fbea576c26 test: add interface_ipc_cli.py testing bitcoin-cli -ipcconnect (Ryan Ofsky)
0448a19b1b ipc: Improve -ipcconnect error checking (Ryan Ofsky)
8d614bfa47 bitcoin-cli: Add -ipcconnect option (Ryan Ofsky)
6a54834895 ipc: Expose an RPC interface over the -ipcbind socket (Ryan Ofsky)
df76891a3b refactor: Add ExecuteHTTPRPC function (Ryan Ofsky)
3cd1cd3ad3 ipc: Add MakeBasicInit function (Ryan Ofsky)

Pull request description:

  This implements an idea from sipa in 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`.

  This 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 PR is part of the [process separation project](https://github.com/bitcoin/bitcoin/issues/28722).

ACKs for top commit:
  achow101:
    ACK 4565cff72c
  pinheadmz:
    ACK 4565cff72c
  enirox001:
    Tested ACK 4565cff72c

Tree-SHA512: cb0dc521d82591e4eb2723a37ae60949309a206265e0ccfbee1f4d59b426b770426fafa1e842819a2fa27322ecdfcd226f31da70f91c2c31b8095e1380666f1f
This commit is contained in:
Ava Chow
2026-03-30 15:12:04 -07:00
25 changed files with 389 additions and 87 deletions

View File

@@ -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)
@@ -791,7 +795,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:
@@ -872,7 +909,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());
@@ -888,17 +925,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");
@@ -958,9 +984,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) {