mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04: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:
parent
ea98a42640
commit
113b46d310
15 changed files with 216 additions and 15 deletions
|
@ -391,6 +391,12 @@ target_link_libraries(bitcoin_cli
|
|||
# Bitcoin Core RPC client
|
||||
if(BUILD_CLI)
|
||||
add_executable(bitcoin-cli bitcoin-cli.cpp)
|
||||
if(ENABLE_IPC)
|
||||
target_sources(bitcoin-cli PRIVATE init/basic-ipc.cpp)
|
||||
target_link_libraries(bitcoin-cli bitcoin_ipc)
|
||||
else()
|
||||
target_sources(bitcoin-cli PRIVATE init/basic.cpp)
|
||||
endif()
|
||||
add_windows_resources(bitcoin-cli bitcoin-cli-res.rc)
|
||||
target_link_libraries(bitcoin-cli
|
||||
core_interface
|
||||
|
|
|
@ -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)
|
||||
|
@ -775,7 +779,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 std::runtime_error{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:
|
||||
|
@ -856,7 +893,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());
|
||||
|
@ -872,17 +909,7 @@ 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.get(), EVHTTP_REQ_POST, endpoint.c_str());
|
||||
req.release(); // ownership moved to evcon in above call
|
||||
if (r != 0) {
|
||||
|
@ -943,9 +970,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", "")};
|
||||
if (auto response{CallIPC(rh, strMethod, args, endpoint, username)}) {
|
||||
return *response;
|
||||
}
|
||||
|
||||
do {
|
||||
try {
|
||||
response = CallRPC(rh, strMethod, args, rpcwallet);
|
||||
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) {
|
||||
|
|
25
src/init/basic-ipc.cpp
Normal file
25
src/init/basic-ipc.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2025 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 <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
|
||||
namespace init {
|
||||
namespace {
|
||||
class BitcoinBasicInit : public interfaces::Init
|
||||
{
|
||||
public:
|
||||
BitcoinBasicInit(const char* exe_name, const char* process_argv0) : m_ipc(interfaces::MakeIpc(exe_name, process_argv0, *this)) {}
|
||||
interfaces::Ipc* ipc() override { return m_ipc.get(); }
|
||||
std::unique_ptr<interfaces::Ipc> m_ipc;
|
||||
};
|
||||
} // namespace
|
||||
} // namespace init
|
||||
|
||||
namespace interfaces {
|
||||
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0)
|
||||
{
|
||||
return std::make_unique<init::BitcoinBasicInit>(exe_name, process_argv0);
|
||||
}
|
||||
} // namespace interfaces
|
12
src/init/basic.cpp
Normal file
12
src/init/basic.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2025 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 <interfaces/init.h>
|
||||
|
||||
namespace interfaces {
|
||||
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0)
|
||||
{
|
||||
return std::make_unique<Init>();
|
||||
}
|
||||
} // namespace interfaces
|
|
@ -8,6 +8,7 @@
|
|||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/rpc.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <node/context.h>
|
||||
#include <util/check.h>
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
return MakeWalletLoader(chain, *Assert(m_node.args));
|
||||
}
|
||||
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
|
||||
std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
|
||||
interfaces::Ipc* ipc() override { return m_ipc.get(); }
|
||||
// bitcoin-gui accepts -ipcbind option even though it does not use it
|
||||
// directly. It just returns true here to accept the option because
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/rpc.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <node/context.h>
|
||||
#include <util/check.h>
|
||||
|
@ -36,6 +37,7 @@ public:
|
|||
return MakeWalletLoader(chain, *Assert(m_node.args));
|
||||
}
|
||||
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
|
||||
std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
|
||||
interfaces::Ipc* ipc() override { return m_ipc.get(); }
|
||||
bool canListenIpc() override { return true; }
|
||||
node::NodeContext& m_node;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <interfaces/echo.h>
|
||||
#include <interfaces/mining.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/rpc.h>
|
||||
#include <interfaces/wallet.h>
|
||||
|
||||
#include <memory>
|
||||
|
@ -36,6 +37,7 @@ public:
|
|||
virtual std::unique_ptr<Mining> makeMining() { return nullptr; }
|
||||
virtual std::unique_ptr<WalletLoader> makeWalletLoader(Chain& chain) { return nullptr; }
|
||||
virtual std::unique_ptr<Echo> makeEcho() { return nullptr; }
|
||||
virtual std::unique_ptr<Rpc> makeRpc() { return nullptr; }
|
||||
virtual Ipc* ipc() { return nullptr; }
|
||||
virtual bool canListenIpc() { return false; }
|
||||
};
|
||||
|
@ -53,6 +55,25 @@ std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status);
|
|||
|
||||
//! Return implementation of Init interface for the gui process.
|
||||
std::unique_ptr<Init> MakeGuiInit(int argc, char* argv[]);
|
||||
|
||||
//! Return implementation of Init interface for a basic IPC client that doesn't
|
||||
//! provide any IPC services itself.
|
||||
//!
|
||||
//! When an IPC client connects to a socket or spawns a process, it gets a pointer
|
||||
//! to an Init object allowing it to create objects and threads on the remote
|
||||
//! side of the IPC connection. But the client also needs to provide a local Init
|
||||
//! object to allow the remote side of the connection to create objects and
|
||||
//! threads on this side. This function just returns a basic Init object
|
||||
//! allowing remote connections to only create local threads, not other objects
|
||||
//! (because its Init::make* methods return null.)
|
||||
//!
|
||||
//! @param exe_name Current executable name, which is just passed to the IPC
|
||||
//! system and used for logging.
|
||||
//!
|
||||
//! @param process_argv0 Optional string containing argv[0] value passed to
|
||||
//! main(). This is passed to the IPC system and used to locate binaries by
|
||||
//! relative path if subprocesses are spawned.
|
||||
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0="");
|
||||
} // namespace interfaces
|
||||
|
||||
#endif // BITCOIN_INTERFACES_INIT_H
|
||||
|
|
31
src/interfaces/rpc.h
Normal file
31
src/interfaces/rpc.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2025 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_INTERFACES_RPC_H
|
||||
#define BITCOIN_INTERFACES_RPC_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class UniValue;
|
||||
|
||||
namespace node {
|
||||
struct NodeContext;
|
||||
} // namespace node
|
||||
|
||||
namespace interfaces {
|
||||
//! Interface giving clients ability to emulate RPC calls.
|
||||
class Rpc
|
||||
{
|
||||
public:
|
||||
virtual ~Rpc() = default;
|
||||
virtual UniValue executeRpc(UniValue request, std::string url, std::string user) = 0;
|
||||
};
|
||||
|
||||
//! Return implementation of Rpc interface.
|
||||
std::unique_ptr<Rpc> MakeRpc(node::NodeContext& node);
|
||||
|
||||
} // namespace interfaces
|
||||
|
||||
#endif // BITCOIN_INTERFACES_RPC_H
|
|
@ -14,6 +14,7 @@ target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR}
|
|||
capnp/echo.capnp
|
||||
capnp/init.capnp
|
||||
capnp/mining.capnp
|
||||
capnp/rpc.capnp
|
||||
)
|
||||
|
||||
target_link_libraries(bitcoin_ipc
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
|
||||
#include <ipc/capnp/echo.capnp.proxy-types.h>
|
||||
#include <ipc/capnp/mining.capnp.proxy-types.h>
|
||||
#include <ipc/capnp/rpc.capnp.proxy-types.h>
|
||||
|
||||
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H
|
||||
|
|
|
@ -15,9 +15,11 @@ $Proxy.includeTypes("ipc/capnp/init-types.h");
|
|||
|
||||
using Echo = import "echo.capnp";
|
||||
using Mining = import "mining.capnp";
|
||||
using Rpc = import "rpc.capnp";
|
||||
|
||||
interface Init $Proxy.wrap("interfaces::Init") {
|
||||
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
|
||||
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
|
||||
makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining);
|
||||
makeRpc @3 (context :Proxy.Context) -> (result :Rpc.Rpc);
|
||||
}
|
||||
|
|
12
src/ipc/capnp/rpc-types.h
Normal file
12
src/ipc/capnp/rpc-types.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2025 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_IPC_CAPNP_RPC_TYPES_H
|
||||
#define BITCOIN_IPC_CAPNP_RPC_TYPES_H
|
||||
|
||||
#include <ipc/capnp/common.capnp.proxy-types.h>
|
||||
#include <ipc/capnp/common-types.h>
|
||||
#include <ipc/capnp/rpc.capnp.proxy.h>
|
||||
|
||||
#endif // BITCOIN_IPC_CAPNP_RPC_TYPES_H
|
17
src/ipc/capnp/rpc.capnp
Normal file
17
src/ipc/capnp/rpc.capnp
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (c) 2025 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@0x9c3505dc45e146ac;
|
||||
|
||||
using Cxx = import "/capnp/c++.capnp";
|
||||
$Cxx.namespace("ipc::capnp::messages");
|
||||
|
||||
using Common = import "common.capnp";
|
||||
using Proxy = import "/mp/proxy.capnp";
|
||||
$Proxy.include("interfaces/rpc.h");
|
||||
$Proxy.includeTypes("ipc/capnp/rpc-types.h");
|
||||
|
||||
interface Rpc $Proxy.wrap("interfaces::Rpc") {
|
||||
executeRpc @0 (context :Proxy.Context, request :Text, uri :Text, user :Text) -> (result :Text);
|
||||
}
|
|
@ -71,10 +71,13 @@ public:
|
|||
fd = m_process->connect(gArgs.GetDataDirNet(), "bitcoin-node", address);
|
||||
} catch (const std::system_error& e) {
|
||||
// If connection type is auto and socket path isn't accepting connections, or doesn't exist, catch the error and return null;
|
||||
if (e.code() == std::errc::connection_refused || e.code() == std::errc::no_such_file_or_directory) {
|
||||
if (e.code() == std::errc::connection_refused || e.code() == std::errc::no_such_file_or_directory || e.code() == std::errc::not_a_directory) {
|
||||
return nullptr;
|
||||
}
|
||||
throw;
|
||||
} catch (const std::invalid_argument&) {
|
||||
// Catch 'Unix address path "..." exceeded maximum socket path length' error
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
fd = m_process->connect(gArgs.GetDataDirNet(), "bitcoin-node", address);
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
#include <consensus/validation.h>
|
||||
#include <deploymentstatus.h>
|
||||
#include <external_signer.h>
|
||||
#include <httprpc.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <interfaces/mining.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/rpc.h>
|
||||
#include <interfaces/types.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <kernel/chain.h>
|
||||
|
@ -80,6 +82,7 @@ using interfaces::Handler;
|
|||
using interfaces::MakeSignalHandler;
|
||||
using interfaces::Mining;
|
||||
using interfaces::Node;
|
||||
using interfaces::Rpc;
|
||||
using interfaces::WalletLoader;
|
||||
using node::BlockAssembler;
|
||||
using node::BlockWaitOptions;
|
||||
|
@ -1115,6 +1118,24 @@ public:
|
|||
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
|
||||
NodeContext& m_node;
|
||||
};
|
||||
|
||||
class RpcImpl : public Rpc
|
||||
{
|
||||
public:
|
||||
explicit RpcImpl(NodeContext& node) : m_node(node) {}
|
||||
|
||||
UniValue executeRpc(UniValue request, std::string uri, std::string user) override
|
||||
{
|
||||
JSONRPCRequest req;
|
||||
req.context = &m_node;
|
||||
req.URI = std::move(uri);
|
||||
req.authUser = std::move(user);
|
||||
HTTPStatusCode status;
|
||||
return ExecuteHTTPRPC(request, req, status);
|
||||
}
|
||||
|
||||
NodeContext& m_node;
|
||||
};
|
||||
} // namespace
|
||||
} // namespace node
|
||||
|
||||
|
@ -1122,4 +1143,5 @@ namespace interfaces {
|
|||
std::unique_ptr<Node> MakeNode(node::NodeContext& context) { return std::make_unique<node::NodeImpl>(context); }
|
||||
std::unique_ptr<Chain> MakeChain(node::NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
|
||||
std::unique_ptr<Mining> MakeMining(node::NodeContext& context) { return std::make_unique<node::MinerImpl>(context); }
|
||||
std::unique_ptr<Rpc> MakeRpc(node::NodeContext& context) { return std::make_unique<node::RpcImpl>(context); }
|
||||
} // namespace interfaces
|
||||
|
|
Loading…
Add table
Reference in a new issue