Skip to content

Commit df40f46

Browse files
committed
bitcoin-cli: Add -ipcconnect option
This implements an idea from Pieter Wuille <[email protected]> thttps://github.com//issues/28722#issuecomment-2807026958 to allow the bitcoin-cli client 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 also commit changes bitcoin-cli to prefer IPC over HTTP by default.
1 parent 2fe5c3c commit df40f46

File tree

15 files changed

+198
-15
lines changed

15 files changed

+198
-15
lines changed

src/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ target_link_libraries(bitcoin_cli
391391
# Bitcoin Core RPC client
392392
if(BUILD_CLI)
393393
add_executable(bitcoin-cli bitcoin-cli.cpp)
394+
if(ENABLE_IPC)
395+
target_sources(bitcoin-cli PRIVATE init/basic-ipc.cpp)
396+
target_link_libraries(bitcoin-cli bitcoin_ipc)
397+
else()
398+
target_sources(bitcoin-cli PRIVATE init/basic.cpp)
399+
endif()
394400
add_windows_resources(bitcoin-cli bitcoin-cli-res.rc)
395401
target_link_libraries(bitcoin-cli
396402
core_interface

src/bitcoin-cli.cpp

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include <common/system.h>
1212
#include <compat/compat.h>
1313
#include <compat/stdin.h>
14+
#include <interfaces/init.h>
15+
#include <interfaces/ipc.h>
16+
#include <interfaces/rpc.h>
1417
#include <policy/feerate.h>
1518
#include <rpc/client.h>
1619
#include <rpc/mining.h>
@@ -108,6 +111,7 @@ static void SetupCliArgs(ArgsManager& argsman)
108111
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);
109112
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);
110113
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);
114+
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 unix' 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 not try to use IPC. Default value: auto", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
111115
}
112116

113117
std::optional<std::string> RpcWalletName(const ArgsManager& args)
@@ -775,7 +779,34 @@ struct DefaultRequestHandler : BaseRequestHandler {
775779
}
776780
};
777781

778-
static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
782+
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)
783+
{
784+
std::string address{gArgs.GetArg("-ipcconnect", "auto")};
785+
std::unique_ptr<interfaces::Init> cli_init{interfaces::MakeBasicInit("bitcoin-cli")};
786+
if (!cli_init) {
787+
if (address == "auto") return {};
788+
throw std::runtime_error("bitcoin-cli was not built with IPC support");
789+
}
790+
791+
std::unique_ptr<interfaces::Init> node_init;
792+
try {
793+
node_init = cli_init->ipc()->connectAddress(address);
794+
if (!node_init) return {};
795+
} catch (const std::exception& exception) {
796+
tfm::format(std::cerr, "Error: %s\n", exception.what());
797+
tfm::format(std::cerr, "Probably bitcoin-node is not running or not listening on a unix socket. Can be started with:\n\n");
798+
tfm::format(std::cerr, " bitcoin-node -chain=%s -ipcbind=unix\n", gArgs.GetChainTypeString());
799+
throw;
800+
}
801+
802+
std::unique_ptr<interfaces::Rpc> rpc{node_init->makeRpc()};
803+
assert(rpc);
804+
HTTPStatusCode status{HTTP_OK};
805+
UniValue valReply{rpc->executeRpc(rh->PrepareRequest(strMethod, args), endpoint, username, status)};
806+
return rh->ProcessReply(valReply);
807+
}
808+
809+
static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::string& endpoint, const std::string& username)
779810
{
780811
std::string host;
781812
// In preference order, we choose the following for the port:
@@ -856,7 +887,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
856887
failedToGetAuthCookie = true;
857888
}
858889
} else {
859-
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
890+
strRPCUserColonPass = username + ":" + gArgs.GetArg("-rpcpassword", "");
860891
}
861892

862893
struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
@@ -872,17 +903,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
872903
assert(output_buffer);
873904
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
874905

875-
// check if we should use a special wallet endpoint
876-
std::string endpoint = "/";
877-
if (rpcwallet) {
878-
char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
879-
if (encodedURI) {
880-
endpoint = "/wallet/" + std::string(encodedURI);
881-
free(encodedURI);
882-
} else {
883-
throw CConnectionFailed("uri-encode failed");
884-
}
885-
}
906+
886907
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str());
887908
req.release(); // ownership moved to evcon in above call
888909
if (r != 0) {
@@ -943,9 +964,26 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
943964
const int timeout = gArgs.GetIntArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT);
944965
const auto deadline{std::chrono::steady_clock::now() + 1s * timeout};
945966

967+
// check if we should use a special wallet endpoint
968+
std::string endpoint = "/";
969+
if (rpcwallet) {
970+
char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
971+
if (encodedURI) {
972+
endpoint = "/wallet/" + std::string(encodedURI);
973+
free(encodedURI);
974+
} else {
975+
throw CConnectionFailed("uri-encode failed");
976+
}
977+
}
978+
979+
std::string username{gArgs.GetArg("-rpcuser", "")};
980+
if (auto response{CallIPC(rh, strMethod, args, endpoint, username)}) {
981+
return *response;
982+
}
983+
946984
do {
947985
try {
948-
response = CallRPC(rh, strMethod, args, rpcwallet);
986+
response = CallRPC(rh, strMethod, args, endpoint, username);
949987
if (fWait) {
950988
const UniValue& error = response.find_value("error");
951989
if (!error.isNull() && error["code"].getInt<int>() == RPC_IN_WARMUP) {

src/init/basic-ipc.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <interfaces/init.h>
6+
#include <interfaces/ipc.h>
7+
8+
namespace init {
9+
namespace {
10+
class BitcoinBasicInit : public interfaces::Init
11+
{
12+
public:
13+
BitcoinBasicInit(const char* exe_name, const char* process_argv0) : m_ipc(interfaces::MakeIpc(exe_name, process_argv0, *this))
14+
{
15+
}
16+
interfaces::Ipc* ipc() override { return m_ipc.get(); }
17+
std::unique_ptr<interfaces::Ipc> m_ipc;
18+
};
19+
} // namespace
20+
} // namespace init
21+
22+
namespace interfaces {
23+
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0)
24+
{
25+
return std::make_unique<init::BitcoinBasicInit>(exe_name, process_argv0);
26+
}
27+
} // namespace interfaces

src/init/basic.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <interfaces/init.h>
6+
7+
namespace interfaces {
8+
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0)
9+
{
10+
return std::make_unique<Init>();
11+
}
12+
} // namespace interfaces

src/init/bitcoin-gui.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <interfaces/init.h>
99
#include <interfaces/ipc.h>
1010
#include <interfaces/node.h>
11+
#include <interfaces/rpc.h>
1112
#include <interfaces/wallet.h>
1213
#include <node/context.h>
1314
#include <util/check.h>
@@ -33,6 +34,7 @@ class BitcoinGuiInit : public interfaces::Init
3334
return MakeWalletLoader(chain, *Assert(m_node.args));
3435
}
3536
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
37+
std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
3638
interfaces::Ipc* ipc() override { return m_ipc.get(); }
3739
// bitcoin-gui accepts -ipcbind option even though it does not use it
3840
// directly. It just returns true here to accept the option because

src/init/bitcoin-node.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <interfaces/init.h>
99
#include <interfaces/ipc.h>
1010
#include <interfaces/node.h>
11+
#include <interfaces/rpc.h>
1112
#include <interfaces/wallet.h>
1213
#include <node/context.h>
1314
#include <util/check.h>
@@ -36,6 +37,7 @@ class BitcoinNodeInit : public interfaces::Init
3637
return MakeWalletLoader(chain, *Assert(m_node.args));
3738
}
3839
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
40+
std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
3941
interfaces::Ipc* ipc() override { return m_ipc.get(); }
4042
bool canListenIpc() override { return true; }
4143
node::NodeContext& m_node;

src/interfaces/init.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <interfaces/echo.h>
1010
#include <interfaces/mining.h>
1111
#include <interfaces/node.h>
12+
#include <interfaces/rpc.h>
1213
#include <interfaces/wallet.h>
1314

1415
#include <memory>
@@ -36,6 +37,7 @@ class Init
3637
virtual std::unique_ptr<Mining> makeMining() { return nullptr; }
3738
virtual std::unique_ptr<WalletLoader> makeWalletLoader(Chain& chain) { return nullptr; }
3839
virtual std::unique_ptr<Echo> makeEcho() { return nullptr; }
40+
virtual std::unique_ptr<Rpc> makeRpc() { return nullptr; }
3941
virtual Ipc* ipc() { return nullptr; }
4042
virtual bool canListenIpc() { return false; }
4143
};
@@ -53,6 +55,14 @@ std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status);
5355

5456
//! Return implementation of Init interface for the gui process.
5557
std::unique_ptr<Init> MakeGuiInit(int argc, char* argv[]);
58+
59+
//! Return implementation of Init interface for a basic IPC client that doesn't
60+
//! provide any IPC services itself. The exe_name parameter is used for
61+
//! logging, and the process_argv0 parameter is optional and only needed if the
62+
//! IPC client will spawn subprocesses. If so, it should point to the argv[0]
63+
//! value which the current executable was started with, so other executables
64+
//! can be found relative to the current one.
65+
std::unique_ptr<Init> MakeBasicInit(const char* exe_name, const char* process_argv0="");
5666
} // namespace interfaces
5767

5868
#endif // BITCOIN_INTERFACES_INIT_H

src/interfaces/rpc.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_INTERFACES_RPC_H
6+
#define BITCOIN_INTERFACES_RPC_H
7+
8+
#include <memory>
9+
#include <string>
10+
11+
class UniValue;
12+
enum HTTPStatusCode : int;
13+
14+
namespace node {
15+
struct NodeContext;
16+
} // namespace node
17+
18+
namespace interfaces {
19+
//! Interface giving clients ability to emulate RPC calls.
20+
class Rpc
21+
{
22+
public:
23+
virtual ~Rpc() = default;
24+
virtual UniValue executeRpc(const UniValue& request, const std::string& url, const std::string& user, HTTPStatusCode& status) = 0;
25+
};
26+
27+
//! Return implementation of Rpc interface.
28+
std::unique_ptr<Rpc> MakeRpc(node::NodeContext& node);
29+
30+
} // namespace interfaces
31+
32+
#endif // BITCOIN_INTERFACES_RPC_H

src/ipc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR}
1414
capnp/echo.capnp
1515
capnp/init.capnp
1616
capnp/mining.capnp
17+
capnp/rpc.capnp
1718
)
1819

1920
target_link_libraries(bitcoin_ipc

src/ipc/capnp/init-types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77

88
#include <ipc/capnp/echo.capnp.proxy-types.h>
99
#include <ipc/capnp/mining.capnp.proxy-types.h>
10+
#include <ipc/capnp/rpc.capnp.proxy-types.h>
1011

1112
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H

0 commit comments

Comments
 (0)