From 5d62d7f6cd48bbc4e9f37ecc369f38d5e1e0036c Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 8 Dec 2020 13:37:41 -0500 Subject: Update libmultiprocess library Fix "Disable GCC suggest-override warnings for proxy clients" https://github.com/chaincodelabs/libmultiprocess/pull/40 is needed to prevent cirrus GCC failure https://cirrus-ci.com/task/6000489311502336?command=ci#L4294 This also includes other recent changes https://github.com/chaincodelabs/libmultiprocess/pull/35 Fix README.md markdown https://github.com/chaincodelabs/libmultiprocess/pull/37 Add "make check" target to build and run tests https://github.com/chaincodelabs/libmultiprocess/pull/38 Add "extends" inherited method support https://github.com/chaincodelabs/libmultiprocess/pull/41 Avoid depending on argument default constructors https://github.com/chaincodelabs/libmultiprocess/pull/42 Support attaching custom cleanup functions to proxy client and server classes https://github.com/chaincodelabs/libmultiprocess/pull/43 Drop hardcoded #include lines in generated files --- depends/packages/native_libmultiprocess.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk index c50fdc3f6b..14653ce9fb 100644 --- a/depends/packages/native_libmultiprocess.mk +++ b/depends/packages/native_libmultiprocess.mk @@ -1,8 +1,8 @@ package=native_libmultiprocess -$(package)_version=5741d750a04e644a03336090d8979c6d033e32c0 +$(package)_version=d576d975debdc9090bd2582f83f49c76c0061698 $(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive $(package)_file_name=$($(package)_version).tar.gz -$(package)_sha256_hash=ac848db49a6ed53e423c62d54bd87f1f08cbb0326254a8667e10bbfe5bf032a4 +$(package)_sha256_hash=9f8b055c8bba755dc32fe799b67c20b91e7b13e67cadafbc54c0f1def057a370 $(package)_dependencies=native_capnp define $(package)_config_cmds -- cgit v1.2.3 From 745c9cebd50fea1664efef571dc1ee1bddc96102 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: multiprocess: Add Ipc and Init interface definitions --- src/Makefile.am | 3 +++ src/interfaces/init.cpp | 15 +++++++++++++++ src/interfaces/init.h | 36 ++++++++++++++++++++++++++++++++++++ src/interfaces/ipc.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/interfaces/init.cpp create mode 100644 src/interfaces/init.h create mode 100644 src/interfaces/ipc.h diff --git a/src/Makefile.am b/src/Makefile.am index d5190206c0..8f971efa09 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -159,6 +159,8 @@ BITCOIN_CORE_H = \ init/common.h \ interfaces/chain.h \ interfaces/handler.h \ + interfaces/init.h \ + interfaces/ipc.h \ interfaces/node.h \ interfaces/wallet.h \ key.h \ @@ -559,6 +561,7 @@ libbitcoin_util_a_SOURCES = \ compat/strnlen.cpp \ fs.cpp \ interfaces/handler.cpp \ + interfaces/init.cpp \ logging.cpp \ random.cpp \ randomenv.cpp \ diff --git a/src/interfaces/init.cpp b/src/interfaces/init.cpp new file mode 100644 index 0000000000..1eeea9b5cb --- /dev/null +++ b/src/interfaces/init.cpp @@ -0,0 +1,15 @@ +// Copyright (c) 2021 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 +#include +#include +#include + +namespace interfaces { +std::unique_ptr Init::makeNode() { return {}; } +std::unique_ptr Init::makeChain() { return {}; } +std::unique_ptr Init::makeWalletClient(Chain& chain) { return {}; } +Ipc* Init::ipc() { return nullptr; } +} // namespace interfaces diff --git a/src/interfaces/init.h b/src/interfaces/init.h new file mode 100644 index 0000000000..8ffd017656 --- /dev/null +++ b/src/interfaces/init.h @@ -0,0 +1,36 @@ +// Copyright (c) 2021 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_INIT_H +#define BITCOIN_INTERFACES_INIT_H + +#include + +struct NodeContext; + +namespace interfaces { +class Chain; +class Ipc; +class Node; +class WalletClient; + +//! Initial interface created when a process is first started, and used to give +//! and get access to other interfaces (Node, Chain, Wallet, etc). +//! +//! There is a different Init interface implementation for each process +//! (bitcoin-gui, bitcoin-node, bitcoin-wallet, bitcoind, bitcoin-qt) and each +//! implementation can implement the make methods for interfaces it supports. +//! The default make methods all return null. +class Init +{ +public: + virtual ~Init() = default; + virtual std::unique_ptr makeNode(); + virtual std::unique_ptr makeChain(); + virtual std::unique_ptr makeWalletClient(Chain& chain); + virtual Ipc* ipc(); +}; +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_INIT_H diff --git a/src/interfaces/ipc.h b/src/interfaces/ipc.h new file mode 100644 index 0000000000..65df575da8 --- /dev/null +++ b/src/interfaces/ipc.h @@ -0,0 +1,48 @@ +// Copyright (c) 2021 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_IPC_H +#define BITCOIN_INTERFACES_IPC_H + +#include +#include +#include + +namespace interfaces { +class Init; + +//! Interface providing access to interprocess-communication (IPC) +//! functionality. +class Ipc +{ +public: + virtual ~Ipc() = default; + + //! Spawn a child process returning pointer to its Init interface. + virtual std::unique_ptr spawnProcess(const char* exe_name) = 0; + + //! If this is a spawned process, block and handle requests from the parent + //! process by forwarding them to this process's Init interface, then return + //! true. If this is not a spawned child process, return false. + virtual bool startSpawnedProcess(int argc, char* argv[], int& exit_status) = 0; + + //! Add cleanup callback to remote interface that will run when the + //! interface is deleted. + template + void addCleanup(Interface& iface, std::function cleanup) + { + addCleanup(typeid(Interface), &iface, std::move(cleanup)); + } + +protected: + //! Internal implementation of public addCleanup method (above) as a + //! type-erased virtual function, since template functions can't be virtual. + virtual void addCleanup(std::type_index type, void* iface, std::function cleanup) = 0; +}; + +//! Return implementation of Ipc interface. +std::unique_ptr MakeIpc(const char* exe_name, const char* process_argv0, Init& init); +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_IPC_H -- cgit v1.2.3 From 10afdf0280fa93bfffb0a7665c60dc155cd84514 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: multiprocess: Add Ipc interface implementation --- src/Makefile.am | 37 ++++++++++++++++- src/ipc/capnp/.gitignore | 2 + src/ipc/capnp/init-types.h | 7 ++++ src/ipc/capnp/init.capnp | 16 +++++++ src/ipc/capnp/protocol.cpp | 90 ++++++++++++++++++++++++++++++++++++++++ src/ipc/capnp/protocol.h | 17 ++++++++ src/ipc/exception.h | 20 +++++++++ src/ipc/interfaces.cpp | 77 ++++++++++++++++++++++++++++++++++ src/ipc/process.cpp | 61 +++++++++++++++++++++++++++ src/ipc/process.h | 42 +++++++++++++++++++ src/ipc/protocol.h | 39 +++++++++++++++++ src/logging.cpp | 1 + src/logging.h | 1 + test/lint/lint-include-guards.sh | 2 +- 14 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 src/ipc/capnp/.gitignore create mode 100644 src/ipc/capnp/init-types.h create mode 100644 src/ipc/capnp/init.capnp create mode 100644 src/ipc/capnp/protocol.cpp create mode 100644 src/ipc/capnp/protocol.h create mode 100644 src/ipc/exception.h create mode 100644 src/ipc/interfaces.cpp create mode 100644 src/ipc/process.cpp create mode 100644 src/ipc/process.h create mode 100644 src/ipc/protocol.h diff --git a/src/Makefile.am b/src/Makefile.am index 8f971efa09..8d17338938 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -74,6 +74,7 @@ EXTRA_LIBRARIES += \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_CLI) \ + $(LIBBITCOIN_IPC) \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_WALLET_TOOL) \ $(LIBBITCOIN_ZMQ) @@ -301,6 +302,8 @@ obj/build.h: FORCE "$(abs_top_srcdir)" libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h +ipc/capnp/libbitcoin_ipc_a-ipc.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) + # server: shared between bitcoind and bitcoin-qt # Contains code accessing mempool and chain state that is meant to be separated # from wallet and gui code (see node/README.md). Shared code should go in @@ -647,7 +650,7 @@ bitcoin_node_SOURCES = $(bitcoin_daemon_sources) bitcoin_node_CPPFLAGS = $(bitcoin_bin_cppflags) bitcoin_node_CXXFLAGS = $(bitcoin_bin_cxxflags) bitcoin_node_LDFLAGS = $(bitcoin_bin_ldflags) -bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd) +bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS) # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp @@ -811,6 +814,38 @@ if HARDEN $(AM_V_at) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS) endif +libbitcoin_ipc_mpgen_input = \ + ipc/capnp/init.capnp +EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) +%.capnp: + +if BUILD_MULTIPROCESS +LIBBITCOIN_IPC=libbitcoin_ipc.a +libbitcoin_ipc_a_SOURCES = \ + ipc/capnp/init-types.h \ + ipc/capnp/protocol.cpp \ + ipc/capnp/protocol.h \ + ipc/exception.h \ + ipc/interfaces.cpp \ + ipc/process.cpp \ + ipc/process.h \ + ipc/protocol.h +libbitcoin_ipc_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_ipc_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS) + +include $(MPGEN_PREFIX)/include/mpgen.mk +libbitcoin_ipc_mpgen_output = \ + $(libbitcoin_ipc_mpgen_input:=.c++) \ + $(libbitcoin_ipc_mpgen_input:=.h) \ + $(libbitcoin_ipc_mpgen_input:=.proxy-client.c++) \ + $(libbitcoin_ipc_mpgen_input:=.proxy-server.c++) \ + $(libbitcoin_ipc_mpgen_input:=.proxy-types.c++) \ + $(libbitcoin_ipc_mpgen_input:=.proxy-types.h) \ + $(libbitcoin_ipc_mpgen_input:=.proxy.h) +nodist_libbitcoin_ipc_a_SOURCES = $(libbitcoin_ipc_mpgen_output) +CLEANFILES += $(libbitcoin_ipc_mpgen_output) +endif + if EMBEDDED_LEVELDB include Makefile.crc32c.include include Makefile.leveldb.include diff --git a/src/ipc/capnp/.gitignore b/src/ipc/capnp/.gitignore new file mode 100644 index 0000000000..036df1430c --- /dev/null +++ b/src/ipc/capnp/.gitignore @@ -0,0 +1,2 @@ +# capnp generated files +*.capnp.* diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h new file mode 100644 index 0000000000..c84b94802a --- /dev/null +++ b/src/ipc/capnp/init-types.h @@ -0,0 +1,7 @@ +// Copyright (c) 2021 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_INIT_TYPES_H +#define BITCOIN_IPC_CAPNP_INIT_TYPES_H +#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp new file mode 100644 index 0000000000..be6eecb4b9 --- /dev/null +++ b/src/ipc/capnp/init.capnp @@ -0,0 +1,16 @@ +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +@0xf2c5cfa319406aa6; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/init.h"); +$Proxy.includeTypes("ipc/capnp/init-types.h"); + +interface Init $Proxy.wrap("interfaces::Init") { + construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); +} diff --git a/src/ipc/capnp/protocol.cpp b/src/ipc/capnp/protocol.cpp new file mode 100644 index 0000000000..74c66c899a --- /dev/null +++ b/src/ipc/capnp/protocol.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2021 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ipc { +namespace capnp { +namespace { +void IpcLogFn(bool raise, std::string message) +{ + LogPrint(BCLog::IPC, "%s\n", message); + if (raise) throw Exception(message); +} + +class CapnpProtocol : public Protocol +{ +public: + ~CapnpProtocol() noexcept(true) + { + if (m_loop) { + std::unique_lock lock(m_loop->m_mutex); + m_loop->removeClient(lock); + } + if (m_loop_thread.joinable()) m_loop_thread.join(); + assert(!m_loop); + }; + std::unique_ptr connect(int fd, const char* exe_name) override + { + startLoop(exe_name); + return mp::ConnectStream(*m_loop, fd); + } + void serve(int fd, const char* exe_name, interfaces::Init& init) override + { + assert(!m_loop); + mp::g_thread_context.thread_name = mp::ThreadName(exe_name); + m_loop.emplace(exe_name, &IpcLogFn, nullptr); + mp::ServeStream(*m_loop, fd, init); + m_loop->loop(); + m_loop.reset(); + } + void addCleanup(std::type_index type, void* iface, std::function cleanup) override + { + mp::ProxyTypeRegister::types().at(type)(iface).cleanup.emplace_back(std::move(cleanup)); + } + void startLoop(const char* exe_name) + { + if (m_loop) return; + std::promise promise; + m_loop_thread = std::thread([&] { + util::ThreadRename("capnp-loop"); + m_loop.emplace(exe_name, &IpcLogFn, nullptr); + { + std::unique_lock lock(m_loop->m_mutex); + m_loop->addClient(lock); + } + promise.set_value(); + m_loop->loop(); + m_loop.reset(); + }); + promise.get_future().wait(); + } + std::thread m_loop_thread; + std::optional m_loop; +}; +} // namespace + +std::unique_ptr MakeCapnpProtocol() { return std::make_unique(); } +} // namespace capnp +} // namespace ipc diff --git a/src/ipc/capnp/protocol.h b/src/ipc/capnp/protocol.h new file mode 100644 index 0000000000..eb057949d2 --- /dev/null +++ b/src/ipc/capnp/protocol.h @@ -0,0 +1,17 @@ +// Copyright (c) 2021 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_PROTOCOL_H +#define BITCOIN_IPC_CAPNP_PROTOCOL_H + +#include + +namespace ipc { +class Protocol; +namespace capnp { +std::unique_ptr MakeCapnpProtocol(); +} // namespace capnp +} // namespace ipc + +#endif // BITCOIN_IPC_CAPNP_PROTOCOL_H diff --git a/src/ipc/exception.h b/src/ipc/exception.h new file mode 100644 index 0000000000..53dee8124a --- /dev/null +++ b/src/ipc/exception.h @@ -0,0 +1,20 @@ +// Copyright (c) 2021 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_EXCEPTION_H +#define BITCOIN_IPC_EXCEPTION_H + +#include + +namespace ipc { +//! Exception class thrown when a call to remote method fails due to an IPC +//! error, like a socket getting disconnected. +class Exception : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; +} // namespace ipc + +#endif // BITCOIN_IPC_EXCEPTION_H diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp new file mode 100644 index 0000000000..ad4b78ed81 --- /dev/null +++ b/src/ipc/interfaces.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2021 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ipc { +namespace { +class IpcImpl : public interfaces::Ipc +{ +public: + IpcImpl(const char* exe_name, const char* process_argv0, interfaces::Init& init) + : m_exe_name(exe_name), m_process_argv0(process_argv0), m_init(init), + m_protocol(ipc::capnp::MakeCapnpProtocol()), m_process(ipc::MakeProcess()) + { + } + std::unique_ptr spawnProcess(const char* new_exe_name) override + { + int pid; + int fd = m_process->spawn(new_exe_name, m_process_argv0, pid); + LogPrint(::BCLog::IPC, "Process %s pid %i launched\n", new_exe_name, pid); + auto init = m_protocol->connect(fd, m_exe_name); + Ipc::addCleanup(*init, [this, new_exe_name, pid] { + int status = m_process->waitSpawned(pid); + LogPrint(::BCLog::IPC, "Process %s pid %i exited with status %i\n", new_exe_name, pid, status); + }); + return init; + } + bool startSpawnedProcess(int argc, char* argv[], int& exit_status) override + { + exit_status = EXIT_FAILURE; + int32_t fd = -1; + if (!m_process->checkSpawned(argc, argv, fd)) { + return false; + } + m_protocol->serve(fd, m_exe_name, m_init); + exit_status = EXIT_SUCCESS; + return true; + } + void addCleanup(std::type_index type, void* iface, std::function cleanup) override + { + m_protocol->addCleanup(type, iface, std::move(cleanup)); + } + const char* m_exe_name; + const char* m_process_argv0; + interfaces::Init& m_init; + std::unique_ptr m_protocol; + std::unique_ptr m_process; +}; +} // namespace +} // namespace ipc + +namespace interfaces { +std::unique_ptr MakeIpc(const char* exe_name, const char* process_argv0, Init& init) +{ + return std::make_unique(exe_name, process_argv0, init); +} +} // namespace interfaces diff --git a/src/ipc/process.cpp b/src/ipc/process.cpp new file mode 100644 index 0000000000..43ed1f1bae --- /dev/null +++ b/src/ipc/process.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2021 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ipc { +namespace { +class ProcessImpl : public Process +{ +public: + int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) override + { + return mp::SpawnProcess(pid, [&](int fd) { + fs::path path = argv0_path; + path.remove_filename(); + path.append(new_exe_name); + return std::vector{path.string(), "-ipcfd", strprintf("%i", fd)}; + }); + } + int waitSpawned(int pid) override { return mp::WaitProcess(pid); } + bool checkSpawned(int argc, char* argv[], int& fd) override + { + // If this process was not started with a single -ipcfd argument, it is + // not a process spawned by the spawn() call above, so return false and + // do not try to serve requests. + if (argc != 3 || strcmp(argv[1], "-ipcfd") != 0) { + return false; + } + // If a single -ipcfd argument was provided, return true and get the + // file descriptor so Protocol::serve() can be called to handle + // requests from the parent process. The -ipcfd argument is not valid + // in combination with other arguments because the parent process + // should be able to control the child process through the IPC protocol + // without passing information out of band. + if (!ParseInt32(argv[2], &fd)) { + throw std::runtime_error(strprintf("Invalid -ipcfd number '%s'", argv[2])); + } + return true; + } +}; +} // namespace + +std::unique_ptr MakeProcess() { return std::make_unique(); } +} // namespace ipc diff --git a/src/ipc/process.h b/src/ipc/process.h new file mode 100644 index 0000000000..4bb2930d9c --- /dev/null +++ b/src/ipc/process.h @@ -0,0 +1,42 @@ +// Copyright (c) 2021 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_PROCESS_H +#define BITCOIN_IPC_PROCESS_H + +#include +#include + +namespace ipc { +class Protocol; + +//! IPC process interface for spawning bitcoin processes and serving requests +//! in processes that have been spawned. +//! +//! There will be different implementations of this interface depending on the +//! platform (e.g. unix, windows). +class Process +{ +public: + virtual ~Process() = default; + + //! Spawn process and return socket file descriptor for communicating with + //! it. + virtual int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) = 0; + + //! Wait for spawned process to exit and return its exit code. + virtual int waitSpawned(int pid) = 0; + + //! Parse command line and determine if current process is a spawned child + //! process. If so, return true and a file descriptor for communicating + //! with the parent process. + virtual bool checkSpawned(int argc, char* argv[], int& fd) = 0; +}; + +//! Constructor for Process interface. Implementation will vary depending on +//! the platform (unix or windows). +std::unique_ptr MakeProcess(); +} // namespace ipc + +#endif // BITCOIN_IPC_PROCESS_H diff --git a/src/ipc/protocol.h b/src/ipc/protocol.h new file mode 100644 index 0000000000..af955b0007 --- /dev/null +++ b/src/ipc/protocol.h @@ -0,0 +1,39 @@ +// Copyright (c) 2021 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_PROTOCOL_H +#define BITCOIN_IPC_PROTOCOL_H + +#include + +#include +#include +#include + +namespace ipc { +//! IPC protocol interface for calling IPC methods over sockets. +//! +//! There may be different implementations of this interface for different IPC +//! protocols (e.g. Cap'n Proto, gRPC, JSON-RPC, or custom protocols). +class Protocol +{ +public: + virtual ~Protocol() = default; + + //! Return Init interface that forwards requests over given socket descriptor. + //! Socket communication is handled on a background thread. + virtual std::unique_ptr connect(int fd, const char* exe_name) = 0; + + //! Handle requests on provided socket descriptor, forwarding them to the + //! provided Init interface. Socket communication is handled on the + //! current thread, and this call blocks until the socket is closed. + virtual void serve(int fd, const char* exe_name, interfaces::Init& init) = 0; + + //! Add cleanup callback to interface that will run when the interface is + //! deleted. + virtual void addCleanup(std::type_index type, void* iface, std::function cleanup) = 0; +}; +} // namespace ipc + +#endif // BITCOIN_IPC_PROTOCOL_H diff --git a/src/logging.cpp b/src/logging.cpp index 866213786e..e5187fd596 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -157,6 +157,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::LEVELDB, "leveldb"}, {BCLog::VALIDATION, "validation"}, {BCLog::I2P, "i2p"}, + {BCLog::IPC, "ipc"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index 436f0cd12e..d04bc99268 100644 --- a/src/logging.h +++ b/src/logging.h @@ -58,6 +58,7 @@ namespace BCLog { LEVELDB = (1 << 20), VALIDATION = (1 << 21), I2P = (1 << 22), + IPC = (1 << 23), ALL = ~(uint32_t)0, }; diff --git a/test/lint/lint-include-guards.sh b/test/lint/lint-include-guards.sh index 5cfa41537f..c23b903bce 100755 --- a/test/lint/lint-include-guards.sh +++ b/test/lint/lint-include-guards.sh @@ -15,7 +15,7 @@ REGEXP_EXCLUDE_FILES_WITH_PREFIX="src/(crypto/ctaes/|leveldb/|crc32c/|secp256k1/ EXIT_CODE=0 for HEADER_FILE in $(git ls-files -- "*.h" | grep -vE "^${REGEXP_EXCLUDE_FILES_WITH_PREFIX}") do - HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr "[:lower:]" "[:upper:]") + HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr - _ | tr "[:lower:]" "[:upper:]") HEADER_ID="${HEADER_ID_PREFIX}${HEADER_ID_BASE}${HEADER_ID_SUFFIX}" if [[ $(grep -cE "^#(ifndef|define) ${HEADER_ID}" "${HEADER_FILE}") != 2 ]]; then echo "${HEADER_FILE} seems to be missing the expected include guard:" -- cgit v1.2.3 From ddf7ecc8dfc64cf121099fb047e1ac871de94f4c Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: multiprocess: Add bitcoin-node process spawning support Add bitcoin-node startup code to let it spawn and be spawned by other processes --- build_msvc/bitcoind/bitcoind.vcxproj | 3 +++ src/Makefile.am | 4 ++-- src/bitcoind.cpp | 15 +++++++++---- src/init/bitcoin-node.cpp | 43 ++++++++++++++++++++++++++++++++++++ src/init/bitcoind.cpp | 29 ++++++++++++++++++++++++ src/interfaces/init.h | 14 ++++++++++++ src/node/context.h | 3 +++ 7 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 src/init/bitcoin-node.cpp create mode 100644 src/init/bitcoind.cpp diff --git a/build_msvc/bitcoind/bitcoind.vcxproj b/build_msvc/bitcoind/bitcoind.vcxproj index 48dfafaee0..c2c32af838 100644 --- a/build_msvc/bitcoind/bitcoind.vcxproj +++ b/build_msvc/bitcoind/bitcoind.vcxproj @@ -10,6 +10,9 @@ + + $(IntDir)init_bitcoind.obj + diff --git a/src/Makefile.am b/src/Makefile.am index 8d17338938..71dd7b65a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -640,13 +640,13 @@ bitcoin_bin_ldadd = \ bitcoin_bin_ldadd += $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(SQLITE_LIBS) -bitcoind_SOURCES = $(bitcoin_daemon_sources) +bitcoind_SOURCES = $(bitcoin_daemon_sources) init/bitcoind.cpp bitcoind_CPPFLAGS = $(bitcoin_bin_cppflags) bitcoind_CXXFLAGS = $(bitcoin_bin_cxxflags) bitcoind_LDFLAGS = $(bitcoin_bin_ldflags) bitcoind_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd) -bitcoin_node_SOURCES = $(bitcoin_daemon_sources) +bitcoin_node_SOURCES = $(bitcoin_daemon_sources) init/bitcoin-node.cpp bitcoin_node_CPPFLAGS = $(bitcoin_bin_cppflags) bitcoin_node_CXXFLAGS = $(bitcoin_bin_cxxflags) bitcoin_node_LDFLAGS = $(bitcoin_bin_ldflags) diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 225b8b1ec4..cf9e4fad44 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -104,10 +105,8 @@ int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint) #endif -static bool AppInit(int argc, char* argv[]) +static bool AppInit(NodeContext& node, int argc, char* argv[]) { - NodeContext node; - bool fRet = false; util::ThreadSetInternalName("init"); @@ -254,10 +253,18 @@ int main(int argc, char* argv[]) util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); #endif + + NodeContext node; + int exit_status; + std::unique_ptr init = interfaces::MakeNodeInit(node, argc, argv, exit_status); + if (!init) { + return exit_status; + } + SetupEnvironment(); // Connect bitcoind signal handlers noui_connect(); - return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); + return (AppInit(node, argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp new file mode 100644 index 0000000000..b1c8a5b561 --- /dev/null +++ b/src/init/bitcoin-node.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2021 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 +#include +#include + +#include + +namespace init { +namespace { +const char* EXE_NAME = "bitcoin-node"; + +class BitcoinNodeInit : public interfaces::Init +{ +public: + BitcoinNodeInit(NodeContext& node, const char* arg0) + : m_node(node), + m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) + { + m_node.init = this; + } + interfaces::Ipc* ipc() override { return m_ipc.get(); } + NodeContext& m_node; + std::unique_ptr m_ipc; +}; +} // namespace +} // namespace init + +namespace interfaces { +std::unique_ptr MakeNodeInit(NodeContext& node, int argc, char* argv[], int& exit_status) +{ + auto init = std::make_unique(node, argc > 0 ? argv[0] : ""); + // Check if bitcoin-node is being invoked as an IPC server. If so, then + // bypass normal execution and just respond to requests over the IPC + // channel and return null. + if (init->m_ipc->startSpawnedProcess(argc, argv, exit_status)) { + return nullptr; + } + return init; +} +} // namespace interfaces diff --git a/src/init/bitcoind.cpp b/src/init/bitcoind.cpp new file mode 100644 index 0000000000..1e17ce4d3c --- /dev/null +++ b/src/init/bitcoind.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2021 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 +#include + +#include + +namespace init { +namespace { +class BitcoindInit : public interfaces::Init +{ +public: + BitcoindInit(NodeContext& node) : m_node(node) + { + m_node.init = this; + } + NodeContext& m_node; +}; +} // namespace +} // namespace init + +namespace interfaces { +std::unique_ptr MakeNodeInit(NodeContext& node, int argc, char* argv[], int& exit_status) +{ + return std::make_unique(node); +} +} // namespace interfaces diff --git a/src/interfaces/init.h b/src/interfaces/init.h index 8ffd017656..8aea027f01 100644 --- a/src/interfaces/init.h +++ b/src/interfaces/init.h @@ -31,6 +31,20 @@ public: virtual std::unique_ptr makeWalletClient(Chain& chain); virtual Ipc* ipc(); }; + +//! Return implementation of Init interface for the node process. If the argv +//! indicates that this is a child process spawned to handle requests from a +//! parent process, this blocks and handles requests, then returns null and a +//! status code to exit with. If this returns non-null, the caller can start up +//! normally and use the Init object to spawn and connect to other processes +//! while it is running. +std::unique_ptr MakeNodeInit(NodeContext& node, int argc, char* argv[], int& exit_status); + +//! Return implementation of Init interface for the wallet process. +std::unique_ptr MakeWalletInit(int argc, char* argv[], int& exit_status); + +//! Return implementation of Init interface for the gui process. +std::unique_ptr MakeGuiInit(int argc, char* argv[]); } // namespace interfaces #endif // BITCOIN_INTERFACES_INIT_H diff --git a/src/node/context.h b/src/node/context.h index 2be9a584e6..06adb33a80 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -22,6 +22,7 @@ class PeerManager; namespace interfaces { class Chain; class ChainClient; +class Init; class WalletClient; } // namespace interfaces @@ -36,6 +37,8 @@ class WalletClient; //! any member functions. It should just be a collection of references that can //! be used without pulling in unwanted dependencies or functionality. struct NodeContext { + //! Init interface for initializing current process and connecting to other processes. + interfaces::Init* init{nullptr}; std::unique_ptr addrman; std::unique_ptr connman; std::unique_ptr mempool; -- cgit v1.2.3 From 7d76cf667eff512043a28d4407cc89f58796c42b Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: multiprocess: Add comments and documentation --- doc/multiprocess.md | 39 ++++++++++++++++++++++++++++++++++++++- src/interfaces/README.md | 6 ++++-- src/interfaces/ipc.h | 25 ++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/doc/multiprocess.md b/doc/multiprocess.md index 7a42fdd734..e3f389a6d3 100644 --- a/doc/multiprocess.md +++ b/doc/multiprocess.md @@ -15,7 +15,7 @@ Specific next steps after [#10102](https://github.com/bitcoin/bitcoin/pull/10102 ## Debugging -After [#10102](https://github.com/bitcoin/bitcoin/pull/10102), the `-debug=ipc` command line option can be used to see requests and responses between processes. +The `-debug=ipc` command line option can be used to see requests and responses between processes. ## Installation @@ -33,3 +33,40 @@ BITCOIND=bitcoin-node test/functional/test_runner.py The configure script will pick up settings and library locations from the depends directory, so there is no need to pass `--enable-multiprocess` as a separate flag when using the depends system (it's controlled by the `MULTIPROCESS=1` option). Alternately, you can install [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) packages on your system, and just run `./configure --enable-multiprocess` without using the depends system. The configure script will be able to locate the installed packages via [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/). See [Installation](https://github.com/chaincodelabs/libmultiprocess#installation) section of the libmultiprocess readme for install steps. See [build-unix.md](build-unix.md) and [build-osx.md](build-osx.md) for information about installing dependencies in general. + +## IPC implementation details + +Cross process Node, Wallet, and Chain interfaces are defined in +[`src/interfaces/`](../src/interfaces/). These are C++ classes which follow +[conventions](developer-notes.md#internal-interface-guidelines), like passing +serializable arguments so they can be called from different processes, and +making methods pure virtual so they can have proxy implementations that forward +calls between processes. + +When Wallet, Node, and Chain code is running in the same process, calling any +interface method invokes the implementation directly. When code is running in +different processes, calling an interface method invokes a proxy interface +implementation that communicates with a remote process and invokes the real +implementation in the remote process. The +[libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) code +generation tool internally generates proxy client classes and proxy server +classes for this purpose that are thin wrappers around Cap'n Proto +[client](https://capnproto.org/cxxrpc.html#clients) and +[server](https://capnproto.org/cxxrpc.html#servers) classes, which handle the +actual serialization and socket communication. + +As much as possible, calls between processes are meant to work the same as +calls within a single process without adding limitations or requiring extra +implementation effort. Processes communicate with each other by calling regular +[C++ interface methods](../src/interfaces/README.md). Method arguments and +return values are automatically serialized and sent between processes. Object +references and `std::function` arguments are automatically tracked and mapped +to allow invoked code to call back into invoking code at any time, and there is +a 1:1 threading model where any thread invoking a method in another process has +a corresponding thread in the invoked process responsible for executing all +method calls from the source thread, without blocking I/O or holding up another +call, and using the same thread local variables, locks, and callbacks between +calls. The forwarding, tracking, and threading is implemented inside the +[libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) library +which has the design goal of making calls between processes look like calls in +the same process to the extent possible. diff --git a/src/interfaces/README.md b/src/interfaces/README.md index f77d172153..97167d5298 100644 --- a/src/interfaces/README.md +++ b/src/interfaces/README.md @@ -12,6 +12,8 @@ The following interfaces are defined here: * [`Handler`](handler.h) — returned by `handleEvent` methods on interfaces above and used to manage lifetimes of event handlers. -* [`Init`](init.h) — used by multiprocess code to access interfaces above on startup. Added in [#10102](https://github.com/bitcoin/bitcoin/pull/10102). +* [`Init`](init.h) — used by multiprocess code to access interfaces above on startup. Added in [#19160](https://github.com/bitcoin/bitcoin/pull/19160). -The interfaces above define boundaries between major components of bitcoin code (node, wallet, and gui), making it possible for them to run in different processes, and be tested, developed, and understood independently. These interfaces are not currently designed to be stable or to be used externally. +* [`Ipc`](ipc.h) — used by multiprocess code to access `Init` interface across processes. Added in [#19160](https://github.com/bitcoin/bitcoin/pull/19160). + +The interfaces above define boundaries between major components of bitcoin code (node, wallet, and gui), making it possible for them to run in [different processes](../../doc/multiprocess.md), and be tested, developed, and understood independently. These interfaces are not currently designed to be stable or to be used externally. diff --git a/src/interfaces/ipc.h b/src/interfaces/ipc.h index 65df575da8..e9e6c78053 100644 --- a/src/interfaces/ipc.h +++ b/src/interfaces/ipc.h @@ -13,7 +13,30 @@ namespace interfaces { class Init; //! Interface providing access to interprocess-communication (IPC) -//! functionality. +//! functionality. The IPC implementation is responsible for establishing +//! connections between a controlling process and a process being controlled. +//! When a connection is established, the process being controlled returns an +//! interfaces::Init pointer to the controlling process, which the controlling +//! process can use to get access to other interfaces and functionality. +//! +//! When spawning a new process, the steps are: +//! +//! 1. The controlling process calls interfaces::Ipc::spawnProcess(), which +//! calls ipc::Process::spawn(), which spawns a new process and returns a +//! socketpair file descriptor for communicating with it. +//! interfaces::Ipc::spawnProcess() then calls ipc::Protocol::connect() +//! passing the socketpair descriptor, which returns a local proxy +//! interfaces::Init implementation calling remote interfaces::Init methods. +//! 2. The spawned process calls interfaces::Ipc::startSpawnProcess(), which +//! calls ipc::Process::checkSpawned() to read command line arguments and +//! determine whether it is a spawned process and what socketpair file +//! descriptor it should use. It then calls ipc::Protocol::serve() to handle +//! incoming requests from the socketpair and invoke interfaces::Init +//! interface methods, and exit when the socket is closed. +//! 3. The controlling process calls local proxy interfaces::Init object methods +//! to make other proxy objects calling other remote interfaces. It can also +//! destroy the initial interfaces::Init object to close the connection and +//! shut down the spawned process. class Ipc { public: -- cgit v1.2.3 From 84934bf70e11fe4cda1cfda60113a54895d4fdd5 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 24 Nov 2020 13:59:33 -0500 Subject: multiprocess: Add echoipc RPC method and test Add simple interfaces::Echo IPC interface with one method that just takes and returns a string, to test multiprocess framework and provide an example of how it can be used to spawn and call between processes. --- src/Makefile.am | 3 +++ src/init/bitcoin-node.cpp | 2 ++ src/interfaces/echo.cpp | 18 ++++++++++++++++++ src/interfaces/echo.h | 26 ++++++++++++++++++++++++++ src/interfaces/init.cpp | 2 ++ src/interfaces/init.h | 2 ++ src/ipc/capnp/echo.capnp | 17 +++++++++++++++++ src/ipc/capnp/init-types.h | 3 +++ src/ipc/capnp/init.capnp | 4 ++++ src/rpc/misc.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ test/functional/rpc_misc.py | 3 +++ 11 files changed, 121 insertions(+) create mode 100644 src/interfaces/echo.cpp create mode 100644 src/interfaces/echo.h create mode 100644 src/ipc/capnp/echo.capnp diff --git a/src/Makefile.am b/src/Makefile.am index 71dd7b65a0..447015fc66 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -159,6 +159,7 @@ BITCOIN_CORE_H = \ init.h \ init/common.h \ interfaces/chain.h \ + interfaces/echo.h \ interfaces/handler.h \ interfaces/init.h \ interfaces/ipc.h \ @@ -563,6 +564,7 @@ libbitcoin_util_a_SOURCES = \ compat/glibcxx_sanity.cpp \ compat/strnlen.cpp \ fs.cpp \ + interfaces/echo.cpp \ interfaces/handler.cpp \ interfaces/init.cpp \ logging.cpp \ @@ -815,6 +817,7 @@ if HARDEN endif libbitcoin_ipc_mpgen_input = \ + ipc/capnp/echo.capnp \ ipc/capnp/init.capnp EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) %.capnp: diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index b1c8a5b561..49684ede83 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include #include @@ -21,6 +22,7 @@ public: { m_node.init = this; } + std::unique_ptr makeEcho() override { return interfaces::MakeEcho(); } interfaces::Ipc* ipc() override { return m_ipc.get(); } NodeContext& m_node; std::unique_ptr m_ipc; diff --git a/src/interfaces/echo.cpp b/src/interfaces/echo.cpp new file mode 100644 index 0000000000..9bbb42217b --- /dev/null +++ b/src/interfaces/echo.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2021 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 + +#include + +namespace interfaces { +namespace { +class EchoImpl : public Echo +{ +public: + std::string echo(const std::string& echo) override { return echo; } +}; +} // namespace +std::unique_ptr MakeEcho() { return std::make_unique(); } +} // namespace interfaces diff --git a/src/interfaces/echo.h b/src/interfaces/echo.h new file mode 100644 index 0000000000..5578d9d9e6 --- /dev/null +++ b/src/interfaces/echo.h @@ -0,0 +1,26 @@ +// Copyright (c) 2021 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_ECHO_H +#define BITCOIN_INTERFACES_ECHO_H + +#include +#include + +namespace interfaces { +//! Simple string echoing interface for testing. +class Echo +{ +public: + virtual ~Echo() {} + + //! Echo provided string. + virtual std::string echo(const std::string& echo) = 0; +}; + +//! Return implementation of Echo interface. +std::unique_ptr MakeEcho(); +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_ECHO_H diff --git a/src/interfaces/init.cpp b/src/interfaces/init.cpp index 1eeea9b5cb..a3c949e616 100644 --- a/src/interfaces/init.cpp +++ b/src/interfaces/init.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -11,5 +12,6 @@ namespace interfaces { std::unique_ptr Init::makeNode() { return {}; } std::unique_ptr Init::makeChain() { return {}; } std::unique_ptr Init::makeWalletClient(Chain& chain) { return {}; } +std::unique_ptr Init::makeEcho() { return {}; } Ipc* Init::ipc() { return nullptr; } } // namespace interfaces diff --git a/src/interfaces/init.h b/src/interfaces/init.h index 8aea027f01..2a38054a17 100644 --- a/src/interfaces/init.h +++ b/src/interfaces/init.h @@ -11,6 +11,7 @@ struct NodeContext; namespace interfaces { class Chain; +class Echo; class Ipc; class Node; class WalletClient; @@ -29,6 +30,7 @@ public: virtual std::unique_ptr makeNode(); virtual std::unique_ptr makeChain(); virtual std::unique_ptr makeWalletClient(Chain& chain); + virtual std::unique_ptr makeEcho(); virtual Ipc* ipc(); }; diff --git a/src/ipc/capnp/echo.capnp b/src/ipc/capnp/echo.capnp new file mode 100644 index 0000000000..df36ee0de3 --- /dev/null +++ b/src/ipc/capnp/echo.capnp @@ -0,0 +1,17 @@ +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +@0x888b4f7f51e691f7; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/echo.h"); +$Proxy.include("ipc/capnp/echo.capnp.h"); + +interface Echo $Proxy.wrap("interfaces::Echo") { + destroy @0 (context :Proxy.Context) -> (); + echo @1 (context :Proxy.Context, echo: Text) -> (result :Text); +} diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h index c84b94802a..42031441b5 100644 --- a/src/ipc/capnp/init-types.h +++ b/src/ipc/capnp/init-types.h @@ -4,4 +4,7 @@ #ifndef BITCOIN_IPC_CAPNP_INIT_TYPES_H #define BITCOIN_IPC_CAPNP_INIT_TYPES_H + +#include + #endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp index be6eecb4b9..e6d358c665 100644 --- a/src/ipc/capnp/init.capnp +++ b/src/ipc/capnp/init.capnp @@ -8,9 +8,13 @@ using Cxx = import "/capnp/c++.capnp"; $Cxx.namespace("ipc::capnp::messages"); using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/echo.h"); $Proxy.include("interfaces/init.h"); $Proxy.includeTypes("ipc/capnp/init-types.h"); +using Echo = import "echo.capnp"; + interface Init $Proxy.wrap("interfaces::Init") { construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); + makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo); } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 00a06260ea..09b32345a2 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -644,6 +647,43 @@ static RPCHelpMan echo(const std::string& name) static RPCHelpMan echo() { return echo("echo"); } static RPCHelpMan echojson() { return echo("echojson"); } +static RPCHelpMan echoipc() +{ + return RPCHelpMan{ + "echoipc", + "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" + "This command is for testing.\n", + {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, + RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, + RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + + HelpExampleRpc("echo", "\"Hello world\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::unique_ptr echo; + if (interfaces::Ipc* ipc = Assert(EnsureAnyNodeContext(request.context).init)->ipc()) { + // Spawn a new bitcoin-node process and call makeEcho to get a + // client pointer to a interfaces::Echo instance running in + // that process. This is just for testing. A slightly more + // realistic test spawning a different executable instead of + // the same executable would add a new bitcoin-echo executable, + // and spawn bitcoin-echo below instead of bitcoin-node. But + // using bitcoin-node avoids the need to build and install a + // new executable just for this one test. + auto init = ipc->spawnProcess("bitcoin-node"); + echo = init->makeEcho(); + ipc->addCleanup(*echo, [init = init.release()] { delete init; }); + } else { + // IPC support is not available because this is a bitcoind + // process not a bitcoind-node process, so just create a local + // interfaces::Echo object and return it so the `echoipc` RPC + // method will work, and the python test calling `echoipc` + // can expect the same result. + echo = interfaces::MakeEcho(); + } + return echo->echo(request.params[0].get_str()); + }, + }; +} + static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) { UniValue ret_summary(UniValue::VOBJ); @@ -719,6 +759,7 @@ static const CRPCCommand commands[] = { "hidden", &mockscheduler, }, { "hidden", &echo, }, { "hidden", &echojson, }, + { "hidden", &echoipc, }, }; // clang-format on for (const auto& c : commands) { diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py index 1398d1237f..a80fa596cd 100755 --- a/test/functional/rpc_misc.py +++ b/test/functional/rpc_misc.py @@ -61,6 +61,9 @@ class RpcMiscTest(BitcoinTestFramework): node.logging(include=['qt']) assert_equal(node.logging()['qt'], True) + self.log.info("test echoipc (testing spawned process in multiprocess build)") + assert_equal(node.echoipc("hello"), "hello") + self.log.info("test getindexinfo") # Without any indices running the RPC returns an empty object assert_equal(node.getindexinfo(), {}) -- cgit v1.2.3