Skip to content

Commit df3f63c

Browse files
committed
Merge bitcoin/bitcoin#30509: multiprocess: Add -ipcbind option to bitcoin-node
30073e6 multiprocess: Add -ipcbind option to bitcoin-node (Russell Yanofsky) 73fe7d7 multiprocess: Add unit tests for connect, serve, and listen functions (Ryan Ofsky) 955d407 multiprocess: Add IPC connectAddress and listenAddress methods (Russell Yanofsky) 4da2043 depends: Update libmultiprocess library for CustomMessage function and ThreadContext bugfix (Ryan Ofsky) Pull request description: Add `-ipcbind` option to `bitcoin-node` to make it listen on a unix socket and accept connections from other processes. The default socket path is `<datadir>/node.sock`, but this can be customized. This option lets potential wallet, gui, index, and mining processes connect to the node and control it. See examples in #19460, #19461, and #30437. Motivation for this PR, in combination with #30510, is be able to release a bitcoin core node binary that can generate block templates for a separate Stratum v2 mining service, like the one being implemented in Sjors/bitcoin#48, that connects over IPC. Other things to know about this PR: - While the `-ipcbind` option lets other processes to connect to the `bitcoin-node` process, the only thing they can actually do after connecting is call methods on the [`Init`](https://github.com/bitcoin/bitcoin/blob/master/src/ipc/capnp/init.capnp#L17-L20) interface which is currently very limited and doesn't do much. But PRs [#30510](bitcoin/bitcoin#30510), [#29409](bitcoin/bitcoin#29409), and [#10102](bitcoin/bitcoin#10102) expand the `Init` interface to expose mining, wallet, and gui functionality respectively. - This PR is not needed for [#10102](bitcoin/bitcoin#10102), which runs GUI, node, and wallet code in different processes, because [#10102](bitcoin/bitcoin#10102) does not use unix sockets or allow outside processes to connect to existing processes. [#10102](bitcoin/bitcoin#10102) lets parent and child processes communicate over internal socketpairs, not externally accessible sockets. --- This PR is part of the [process separation project](bitcoin/bitcoin#28722). ACKs for top commit: achow101: ACK 30073e6 TheCharlatan: Re-ACK 30073e6 itornaza: Code review ACK 30073e6 Tree-SHA512: 2b766e60535f57352e8afda9c3748a32acb5a57b2827371b48ba865fa9aa1df00f340732654f2e300c6823dbc6f3e14377fca87e4e959e613fe85a6d2312d9c8
2 parents 712a2b5 + 30073e6 commit df3f63c

21 files changed

+359
-18
lines changed

depends/packages/native_libmultiprocess.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package=native_libmultiprocess
2-
$(package)_version=6aca5f389bacf2942394b8738bbe15d6c9edfb9b
2+
$(package)_version=c1b4ab4eb897d3af09bc9b3cc30e2e6fff87f3e2
33
$(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive
44
$(package)_file_name=$($(package)_version).tar.gz
5-
$(package)_sha256_hash=2efeed53542bc1d8af3291f2b6f0e5d430d86a5e04e415ce33c136f2c226a51d
5+
$(package)_sha256_hash=6edf5ad239ca9963c78f7878486fb41411efc9927c6073928a7d6edf947cac4a
66
$(package)_dependencies=native_capnp
77

88
define $(package)_config_cmds

src/bitcoind.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,11 @@ int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
109109

110110
#endif
111111

112-
static bool ParseArgs(ArgsManager& args, int argc, char* argv[])
112+
static bool ParseArgs(NodeContext& node, int argc, char* argv[])
113113
{
114+
ArgsManager& args{*Assert(node.args)};
114115
// If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
115-
SetupServerArgs(args);
116+
SetupServerArgs(args, node.init->canListenIpc());
116117
std::string error;
117118
if (!args.ParseParameters(argc, argv, error)) {
118119
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
@@ -268,7 +269,7 @@ MAIN_FUNCTION
268269

269270
// Interpret command line arguments
270271
ArgsManager& args = *Assert(node.args);
271-
if (!ParseArgs(args, argc, argv)) return EXIT_FAILURE;
272+
if (!ParseArgs(node, argc, argv)) return EXIT_FAILURE;
272273
// Process early info return commands such as -help or -version
273274
if (ProcessInitCommands(args)) return EXIT_SUCCESS;
274275

src/common/args.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ std::string ArgsManager::GetHelpMessage() const
635635
case OptionsCategory::RPC:
636636
usage += HelpMessageGroup("RPC server options:");
637637
break;
638+
case OptionsCategory::IPC:
639+
usage += HelpMessageGroup("IPC interprocess connection options:");
640+
break;
638641
case OptionsCategory::WALLET:
639642
usage += HelpMessageGroup("Wallet options:");
640643
break;

src/common/args.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ enum class OptionsCategory {
6464
COMMANDS,
6565
REGISTER_COMMANDS,
6666
CLI_COMMANDS,
67+
IPC,
6768

6869
HIDDEN // Always the last option to avoid printing these in the help
6970
};

src/init.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <init/common.h>
3030
#include <interfaces/chain.h>
3131
#include <interfaces/init.h>
32+
#include <interfaces/ipc.h>
3233
#include <interfaces/mining.h>
3334
#include <interfaces/node.h>
3435
#include <kernel/context.h>
@@ -441,7 +442,7 @@ static void OnRPCStopped()
441442
LogDebug(BCLog::RPC, "RPC stopped.\n");
442443
}
443444

444-
void SetupServerArgs(ArgsManager& argsman)
445+
void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
445446
{
446447
SetupHelpOptions(argsman);
447448
argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now
@@ -676,6 +677,9 @@ void SetupServerArgs(ArgsManager& argsman)
676677
argsman.AddArg("-rpcwhitelistdefault", "Sets default behavior for rpc whitelisting. Unless rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc server acts as if all rpc users are subject to empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault is set to 1 and no -rpcwhitelist is set, rpc server acts as if all rpc users are subject to empty whitelists.", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
677678
argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
678679
argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
680+
if (can_listen_ipc) {
681+
argsman.AddArg("-ipcbind=<address>", "Bind to Unix socket address and listen for incoming connections. Valid address values are \"unix\" to listen on the default path, <datadir>/node.sock, or \"unix:/custom/path\" to specify a custom path. Can be specified multiple times to listen on multiple paths. Default behavior is not to listen on any path. If relative paths are specified, they are interpreted relative to the network data directory. If paths include any parent directory components and the parent directories do not exist, they will be created.", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
682+
}
679683

680684
#if HAVE_DECL_FORK
681685
argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -1200,6 +1204,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
12001204
g_wallet_init_interface.Construct(node);
12011205
uiInterface.InitWallet();
12021206

1207+
if (interfaces::Ipc* ipc = node.init->ipc()) {
1208+
for (std::string address : gArgs.GetArgs("-ipcbind")) {
1209+
try {
1210+
ipc->listenAddress(address);
1211+
} catch (const std::exception& e) {
1212+
return InitError(strprintf(Untranslated("Unable to bind to IPC address '%s'. %s"), address, e.what()));
1213+
}
1214+
LogPrintf("Listening for IPC requests on address %s\n", address);
1215+
}
1216+
}
1217+
12031218
/* Register RPC commands regardless of -server setting so they will be
12041219
* available in the GUI RPC console even if external calls are disabled.
12051220
*/

src/init.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ bool AppInitMain(node::NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip
7474
/**
7575
* Register all arguments with the ArgsManager
7676
*/
77-
void SetupServerArgs(ArgsManager& argsman);
77+
void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc=false);
7878

7979
/** Validates requirements to run the indexes and spawns each index initial sync thread */
8080
bool StartIndexBackgroundSync(node::NodeContext& node);

src/init/bitcoin-gui.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class BitcoinGuiInit : public interfaces::Init
3434
}
3535
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
3636
interfaces::Ipc* ipc() override { return m_ipc.get(); }
37+
// bitcoin-gui accepts -ipcbind option even though it does not use it
38+
// directly. It just returns true here to accept the option because
39+
// bitcoin-node accepts the option, and bitcoin-gui accepts all bitcoin-node
40+
// options and will start the node with those options.
41+
bool canListenIpc() override { return true; }
3742
node::NodeContext m_node;
3843
std::unique_ptr<interfaces::Ipc> m_ipc;
3944
};

src/init/bitcoin-node.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class BitcoinNodeInit : public interfaces::Init
3737
}
3838
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
3939
interfaces::Ipc* ipc() override { return m_ipc.get(); }
40+
bool canListenIpc() override { return true; }
4041
node::NodeContext& m_node;
4142
std::unique_ptr<interfaces::Ipc> m_ipc;
4243
};

src/interfaces/init.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Init
3737
virtual std::unique_ptr<WalletLoader> makeWalletLoader(Chain& chain) { return nullptr; }
3838
virtual std::unique_ptr<Echo> makeEcho() { return nullptr; }
3939
virtual Ipc* ipc() { return nullptr; }
40+
virtual bool canListenIpc() { return false; }
4041
};
4142

4243
//! Return implementation of Init interface for the node process. If the argv

src/interfaces/ipc.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class Init;
4141
//! to make other proxy objects calling other remote interfaces. It can also
4242
//! destroy the initial interfaces::Init object to close the connection and
4343
//! shut down the spawned process.
44+
//!
45+
//! When connecting to an existing process, the steps are similar to spawning a
46+
//! new process, except a socket is created instead of a socketpair, and
47+
//! destroying an Init interface doesn't end the process, since there can be
48+
//! multiple connections.
4449
class Ipc
4550
{
4651
public:
@@ -54,6 +59,17 @@ class Ipc
5459
//! true. If this is not a spawned child process, return false.
5560
virtual bool startSpawnedProcess(int argc, char* argv[], int& exit_status) = 0;
5661

62+
//! Connect to a socket address and make a client interface proxy object
63+
//! using provided callback. connectAddress returns an interface pointer if
64+
//! the connection was established, returns null if address is empty ("") or
65+
//! disabled ("0") or if a connection was refused but not required ("auto"),
66+
//! and throws an exception if there was an unexpected error.
67+
virtual std::unique_ptr<Init> connectAddress(std::string& address) = 0;
68+
69+
//! Connect to a socket address and make a client interface proxy object
70+
//! using provided callback. Throws an exception if there was an error.
71+
virtual void listenAddress(std::string& address) = 0;
72+
5773
//! Add cleanup callback to remote interface that will run when the
5874
//! interface is deleted.
5975
template<typename Interface>

0 commit comments

Comments
 (0)