diff --git a/build_msvc/test_bitcoin/test_bitcoin.vcxproj b/build_msvc/test_bitcoin/test_bitcoin.vcxproj index 2a78f6f2a1e93..0ae3819e50d42 100644 --- a/build_msvc/test_bitcoin/test_bitcoin.vcxproj +++ b/build_msvc/test_bitcoin/test_bitcoin.vcxproj @@ -10,7 +10,7 @@ - + diff --git a/contrib/devtools/circular-dependencies.py b/contrib/devtools/circular-dependencies.py index b742a8cea6718..1f69bf4b21b01 100755 --- a/contrib/devtools/circular-dependencies.py +++ b/contrib/devtools/circular-dependencies.py @@ -15,7 +15,7 @@ # define functions and variables declared in corresponding .h files is # incorrect. HEADER_MODULE_PATHS = [ - 'interfaces/' + 'ipc/' ] def module_name(path): diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk index 946e885354aac..8ed18529d9ca9 100644 --- a/depends/packages/native_libmultiprocess.mk +++ b/depends/packages/native_libmultiprocess.mk @@ -1,8 +1,8 @@ package=native_libmultiprocess -$(package)_version=414542f81e0997354b45b8ade13ca144a3e35ff1 +$(package)_version=2cbbd09d8b9972a8f5c68b510c0dae9a9f7a22da $(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive $(package)_file_name=$($(package)_version).tar.gz -$(package)_sha256_hash=8542dbaf8c4fce8fd7af6929f5dc9b34dffa51c43e9ee360e93ee0f34b180bc2 +$(package)_sha256_hash=a86416908cc4d27a41c58d846ca854730b1c56bd7a58d823348969e300a10014 $(package)_dependencies=native_capnp define $(package)_config_cmds diff --git a/doc/build-osx.md b/doc/build-osx.md index 5c3dc1ac7fa0f..dd93c4f912ca4 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -48,7 +48,7 @@ See [dependencies.md](dependencies.md) for a complete overview. To install, run the following from your terminal: ``` bash -brew install automake libtool boost pkg-config libevent +brew install automake libtool boost pkg-config libevent capnp ``` ### 4. Clone Bitcoin repository diff --git a/doc/build-unix.md b/doc/build-unix.md index bf367fc4216b1..6ce5fe45b9e5b 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -81,7 +81,7 @@ To build without GUI pass `--without-gui`. To build with Qt 5 you need the following: - sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools + sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libcapnp-dev capnproto Additionally, to support Wayland protocol for modern desktop environments: diff --git a/doc/dependencies.md b/doc/dependencies.md index ba8643be79dd7..c051b359d7b02 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -48,3 +48,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | --- | --- | --- | --- | --- | | [Berkeley DB](../depends/packages/bdb.mk) (legacy wallet) | [link](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.30 | 4.8.x | No | | [SQLite](../depends/packages/sqlite.mk) | [link](https://sqlite.org) | [3.38.5](https://github.com/bitcoin/bitcoin/pull/25378) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | No | + +### Multiprocess support +| Dependency | Releases | Version used | Minimum required | Runtime | +| [Cap'n Proto](../depends/packages/capnp.mk) | [link](https://capnproto.org) | [0.6.1](https://capnproto.org/install.html) | 0.5.3 | Yes | diff --git a/doc/design/multiprocess.md b/doc/design/multiprocess.md index 636d78d905c45..304b86387b4b6 100644 --- a/doc/design/multiprocess.md +++ b/doc/design/multiprocess.md @@ -81,7 +81,7 @@ This section describes the major components of the Inter-Process Communication ( - In the generated code, we have C++ client subclasses that inherit from the abstract classes in [`src/interfaces/`](../../src/interfaces/). These subclasses are the workhorses of the IPC mechanism. - They implement all the methods of the interface, marshalling arguments into a structured format, sending them as requests to the IPC server via a UNIX socket, and handling the responses. - These subclasses effectively mask the complexity of IPC, presenting a familiar C++ interface to developers. -- Internally, the client subclasses generated by the `mpgen` tool wrap [client classes generated by Cap'n Proto](https://capnproto.org/cxxrpc.html#clients), and use them to send IPC requests. +- Internally, the client subclasses generated by the `mpgen` tool wrap [client classes generated by Cap'n Proto](https://capnproto.org/cxxrpc.html#clients), and use them to send IPC requests. The Cap'n Proto client classes are low-level, with non-blocking methods that use asynchronous I/O and pass request and response objects, while mpgen client subclasses provide normal C++ methods that block while executing and convert between request/response objects and arguments/return values. ### C++ Server Classes in Generated Code - On the server side, corresponding generated C++ classes receive IPC requests. These server classes are responsible for unmarshalling method arguments, invoking the corresponding methods in the local [`src/interfaces/`](../../src/interfaces/) objects, and creating the IPC response. @@ -142,7 +142,7 @@ In the current design, class names, method names, and parameter names are duplic An alternate approach could use custom [C++ Attributes](https://en.cppreference.com/w/cpp/language/attributes) embedded in interface declarations to automatically generate `.capnp` files from C++ headers. This has not been pursued because parsing C++ headers is more complicated than parsing Cap’n Proto interface definitions, especially portably on multiple platforms. -In the meantime, the developer guide [Internal interface guidelines](developer-notes.md#internal-interface-guidelines) can provide guidance on keeping interfaces consistent and functional and avoiding compile errors. +In the meantime, the developer guide [Internal interface guidelines](../developer-notes.md#internal-interface-guidelines) can provide guidance on keeping interfaces consistent and functional and avoiding compile errors. ### Interface Stability @@ -197,7 +197,7 @@ sequenceDiagram - Upon receiving the request, the Cap'n Proto dispatching code in the `bitcoin-node` process calls the `getBlockHash` method of the `Chain` [server class](#c-server-classes-in-generated-code). - The server class is automatically generated by the `mpgen` tool from the [`chain.capnp`](https://github.com/ryanofsky/bitcoin/blob/pr/ipc/src/ipc/capnp/chain.capnp) file in [`src/ipc/capnp/`](../../src/ipc/capnp/). - The `getBlockHash` method of the generated `Chain` server subclass in `bitcoin-wallet` receives a Cap’n Proto request object with the `height` parameter, and calls the `getBlockHash` method on its local `Chain` object with the provided `height`. - - When the call returns, it encapsulates the return value in a Cap’n Proto response, which it sends back to the `bitcoin-wallet` process, + - When the call returns, it encapsulates the return value in a Cap’n Proto response, which it sends back to the `bitcoin-wallet` process. 5. **Response and Return** - The `getBlockHash` method of the generated `Chain` client subclass in `bitcoin-wallet` which sent the request now receives the response. @@ -232,7 +232,7 @@ This modularization represents an advancement in Bitcoin Core's architecture, of - **Cap’n Proto struct**: A structured data format used in Cap’n Proto, similar to structs in C++, for organizing and transporting data across different processes. -- **client class (in generated code)**: A C++ class generated from a Cap’n Proto interface which inherits from a Bitcoin core abstract class, and implements each virtual method to send IPC requests to another process. (see also [components section](#c-client-subclasses-in-generated-code)) +- **client class (in generated code)**: A C++ class generated from a Cap’n Proto interface which inherits from a Bitcoin Core abstract class, and implements each virtual method to send IPC requests to another process. (see also [components section](#c-client-subclasses-in-generated-code)) - **IPC (inter-process communication)**: Mechanisms that enable processes to exchange requests and data. diff --git a/doc/multiprocess.md b/doc/multiprocess.md index 7ba89b3ff56a2..3e96082875f3e 100644 --- a/doc/multiprocess.md +++ b/doc/multiprocess.md @@ -17,7 +17,9 @@ The multiprocess feature requires [Cap'n Proto](https://capnproto.org/) and [lib ``` cd make -C depends NO_QT=1 MULTIPROCESS=1 -CONFIG_SITE=$PWD/depends/x86_64-pc-linux-gnu/share/config.site ./configure +# Set host platform to output of gcc -dumpmachine or clang -dumpmachine or check the depends/ directory for the generated subdirectory name +HOST_PLATFORM="x86_64-pc-linux-gnu" +CONFIG_SITE="$PWD/depends/$HOST_PLATFORM/share/config.site" ./configure make src/bitcoin-node -regtest -printtoconsole -debug=ipc BITCOIND=bitcoin-node test/functional/test_runner.py @@ -32,3 +34,9 @@ Alternately, you can install [Cap'n Proto](https://capnproto.org/) and [libmulti `bitcoin-node` is a drop-in replacement for `bitcoind`, and `bitcoin-gui` is a drop-in replacement for `bitcoin-qt`, and there are no differences in use or external behavior between the new and old executables. But internally after [#10102](https://github.com/bitcoin/bitcoin/pull/10102), `bitcoin-gui` will spawn a `bitcoin-node` process to run P2P and RPC code, communicating with it across a socket pair, and `bitcoin-node` will spawn `bitcoin-wallet` to run wallet code, also communicating over a socket pair. This will let node, wallet, and GUI code run in separate address spaces for better isolation, and allow future improvements like being able to start and stop components independently on different machines and environments. [#19460](https://github.com/bitcoin/bitcoin/pull/19460) also adds a new `bitcoin-node` `-ipcbind` option and an `bitcoind-wallet` `-ipcconnect` option to allow new wallet processes to connect to an existing node process. And [#19461](https://github.com/bitcoin/bitcoin/pull/19461) adds a new `bitcoin-gui` `-ipcconnect` option to allow new GUI processes to connect to an existing node process. + +## Known issues + +- Unexpected socket disconnects aren't handled cleanly many places. Interface calls that used to never throw can now throw exceptions if a socket is disconnected (typically because a process on the other side of the connection has crashed or been killed), leading to errors. + +- Internally spawned bitcoin-node and bitcoin-wallet processes don't currently install signal handlers and so won't shut down cleanly if terminated with [CTRL-C](https://github.com/bitcoin/bitcoin/pull/10102#issuecomment-595353238). Shutting down with `bitcoin-cli stop` should still shut down cleanly, and is a suggested alternative. diff --git a/src/Makefile.am b/src/Makefile.am index b6f0daaabadbd..8e84892c91ad3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -422,7 +422,6 @@ libbitcoin_node_a_SOURCES = \ node/context.cpp \ node/database_args.cpp \ node/eviction.cpp \ - node/interface_ui.cpp \ node/interfaces.cpp \ node/kernel_notifications.cpp \ node/mempool_args.cpp \ @@ -690,6 +689,7 @@ libbitcoin_common_a_SOURCES = \ netaddress.cpp \ netbase.cpp \ net_permissions.cpp \ + node/interface_ui.cpp \ outputtype.cpp \ policy/feerate.cpp \ policy/policy.cpp \ @@ -804,7 +804,7 @@ 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) -bitcoin_node_LDADD = $(LIBBITCOIN_NODE) $(bitcoin_bin_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS) +bitcoin_node_LDADD = $(LIBBITCOIN_NODE) $(bitcoin_bin_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS) $(LIBBITCOIN_WALLET) # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp @@ -847,21 +847,24 @@ bitcoin_tx_LDADD = \ # bitcoin-wallet binary # bitcoin_wallet_SOURCES = bitcoin-wallet.cpp +if BUILD_MULTIPROCESS +bitcoin_wallet_SOURCES += init/bitcoin-wallet-ipc.cpp +# FIX: Dependency on kernel should be dropped. See BitcoinWalletInit constructor comment. +bitcoin_wallet_SOURCES += kernel/context.cpp +else bitcoin_wallet_SOURCES += init/bitcoin-wallet.cpp +endif bitcoin_wallet_CPPFLAGS = $(bitcoin_bin_cppflags) bitcoin_wallet_CXXFLAGS = $(bitcoin_bin_cxxflags) bitcoin_wallet_LDFLAGS = $(bitcoin_bin_ldflags) bitcoin_wallet_LDADD = \ $(LIBBITCOIN_WALLET_TOOL) \ - $(LIBBITCOIN_WALLET) \ + $(bitcoin_bin_ldadd) \ + $(LIBBITCOIN_IPC) \ $(LIBBITCOIN_COMMON) \ - $(LIBBITCOIN_UTIL) \ - $(LIBUNIVALUE) \ - $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ - $(LIBSECP256K1) \ - $(BDB_LIBS) \ - $(SQLITE_LIBS) + $(LIBBITCOIN_UTIL) \ + $(LIBMULTIPROCESS_LIBS) if TARGET_WINDOWS bitcoin_wallet_SOURCES += bitcoin-wallet-res.rc @@ -1078,29 +1081,48 @@ if HARDEN endif libbitcoin_ipc_mpgen_input = \ + ipc/capnp/chain.capnp \ + ipc/capnp/common.capnp \ ipc/capnp/echo.capnp \ - ipc/capnp/init.capnp + ipc/capnp/handler.capnp \ + ipc/capnp/init.capnp \ + ipc/capnp/node.capnp \ + ipc/capnp/wallet.capnp EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) %.capnp: # Explicitly list dependencies on generated headers as described in # https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually +ipc/capnp/libbitcoin_ipc_a-chain.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) +ipc/capnp/libbitcoin_ipc_a-common.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) if BUILD_MULTIPROCESS LIBBITCOIN_IPC=libbitcoin_ipc.a libbitcoin_ipc_a_SOURCES = \ + ipc/capnp/chain.cpp \ + ipc/capnp/chain-types.h \ + ipc/capnp/common.cpp \ + ipc/capnp/common.h \ + ipc/capnp/common-types.h \ ipc/capnp/context.h \ ipc/capnp/init-types.h \ + ipc/capnp/init.cpp \ + ipc/capnp/node-types.h \ + ipc/capnp/node.cpp \ + ipc/capnp/node.h \ ipc/capnp/protocol.cpp \ ipc/capnp/protocol.h \ + ipc/capnp/wallet-types.h \ + ipc/capnp/wallet.cpp \ + ipc/capnp/wallet.h \ ipc/context.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_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DLIBMULTIPROCESS_IPC libbitcoin_ipc_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS) include $(MPGEN_PREFIX)/include/mpgen.mk diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 416a11b0c0112..b8afc47bd4e51 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -216,6 +216,39 @@ BITCOIN_TEST_SUITE += \ wallet/test/init_test_fixture.h endif # ENABLE_WALLET +if BUILD_MULTIPROCESS +# Add boost ipc_tests definition to BITCOIN_TESTS +BITCOIN_TESTS += test/ipc_tests.cpp + +# Build ipc_test code in a separate library so it can be compiled with custom +# LIBMULTIPROCESS_CFLAGS without those flags affecting other tests +LIBBITCOIN_IPC_TEST=libbitcoin_ipc_test.a +EXTRA_LIBRARIES += $(LIBBITCOIN_IPC_TEST) +libbitcoin_ipc_test_a_SOURCES = \ + test/ipc_test.cpp \ + test/ipc_test.h +libbitcoin_ipc_test_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_ipc_test_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS) + +# Generate various .c++/.h files from the ipc_test.capnp file +include $(MPGEN_PREFIX)/include/mpgen.mk +EXTRA_DIST += test/ipc_test.capnp +libbitcoin_ipc_test_mpgen_output = \ + test/ipc_test.capnp.c++ \ + test/ipc_test.capnp.h \ + test/ipc_test.capnp.proxy-client.c++ \ + test/ipc_test.capnp.proxy-server.c++ \ + test/ipc_test.capnp.proxy-types.c++ \ + test/ipc_test.capnp.proxy-types.h \ + test/ipc_test.capnp.proxy.h +nodist_libbitcoin_ipc_test_a_SOURCES = $(libbitcoin_ipc_test_mpgen_output) +CLEANFILES += $(libbitcoin_ipc_test_mpgen_output) +endif + +# Explicitly list dependencies on generated headers as described in +# https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually +test/libbitcoin_ipc_test_a-ipc_test.$(OBJEXT): test/ipc_test.capnp.h + test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) test_test_bitcoin_LDADD = $(LIBTEST_UTIL) @@ -223,6 +256,9 @@ if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) test_test_bitcoin_CPPFLAGS += $(BDB_CPPFLAGS) endif +if BUILD_MULTIPROCESS +test_test_bitcoin_LDADD += $(LIBBITCOIN_IPC_TEST) $(LIBMULTIPROCESS_LIBS) +endif test_test_bitcoin_LDADD += $(LIBBITCOIN_NODE) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ $(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS) diff --git a/src/addrman.cpp b/src/addrman.cpp index a8206de6ee291..b649950eced3a 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -171,7 +171,7 @@ void AddrManImpl::Serialize(Stream& s_) const */ // Always serialize in the latest version (FILE_FORMAT). - ParamsStream s{CAddress::V2_DISK, s_}; + ParamsStream s{s_, CAddress::V2_DISK}; s << static_cast(FILE_FORMAT); @@ -236,7 +236,7 @@ void AddrManImpl::Unserialize(Stream& s_) s_ >> Using>(format); const auto ser_params = (format >= Format::V3_BIP155 ? CAddress::V2_DISK : CAddress::V1_DISK); - ParamsStream s{ser_params, s_}; + ParamsStream s{s_, ser_params}; uint8_t compat; s >> compat; diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index d5dfbbec2713a..59c6840558593 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -28,7 +28,7 @@ #include const std::function G_TRANSLATION_FUN = nullptr; -UrlDecodeFn* const URL_DECODE = nullptr; +UrlDecodeFn* const URL_DECODE = urlDecode; static void SetupWalletToolArgs(ArgsManager& argsman) { diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 4f0a816388e7f..86d8b8497b7a5 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -172,7 +173,8 @@ static bool AppInit(NodeContext& node) // -server defaults to true for bitcoind but not for the GUI so do this here args.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console - InitLogging(args); + interfaces::Ipc* ipc = node.init->ipc(); + InitLogging(args, ipc ? ipc->logSuffix() : nullptr); InitParameterInteraction(args); if (!AppInitBasicSetup(args, node.exit_status)) { // InitError will have been called with detailed error, which ends up on console diff --git a/src/init.cpp b/src/init.cpp index b825c8ce21798..9769be38fa5da 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -811,9 +811,9 @@ void InitParameterInteraction(ArgsManager& args) * Note that this is called very early in the process lifetime, so you should be * careful about what global state you rely on here. */ -void InitLogging(const ArgsManager& args) +void InitLogging(const ArgsManager& args, const char* log_suffix) { - init::SetLoggingOptions(args); + init::SetLoggingOptions(args, log_suffix); init::LogPackageVersion(); } diff --git a/src/init.h b/src/init.h index ead5f5e0d2195..de47b7d44f550 100644 --- a/src/init.h +++ b/src/init.h @@ -35,7 +35,7 @@ bool ShutdownRequested(node::NodeContext& node); void Interrupt(node::NodeContext& node); void Shutdown(node::NodeContext& node); //!Initialize the logging infrastructure -void InitLogging(const ArgsManager& args); +void InitLogging(const ArgsManager& args, const char* log_suffix); //!Parameter interaction: change current parameters depending on various rules void InitParameterInteraction(ArgsManager& args); diff --git a/src/init/bitcoin-gui.cpp b/src/init/bitcoin-gui.cpp index aceff1e40f0bc..56fc2cb2924a3 100644 --- a/src/init/bitcoin-gui.cpp +++ b/src/init/bitcoin-gui.cpp @@ -3,17 +3,18 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include -#include #include #include -#include -#include -#include #include #include +namespace ipc { +namespace capnp { +void SetupNodeClient(ipc::Context& context); +} // namespace capnp +} // namespace ipc + namespace init { namespace { const char* EXE_NAME = "bitcoin-gui"; @@ -21,20 +22,11 @@ const char* EXE_NAME = "bitcoin-gui"; class BitcoinGuiInit : public interfaces::Init { public: - BitcoinGuiInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) - { - InitContext(m_node); - m_node.init = this; - } - std::unique_ptr makeNode() override { return interfaces::MakeNode(m_node); } - std::unique_ptr makeChain() override { return interfaces::MakeChain(m_node); } - std::unique_ptr makeWalletLoader(interfaces::Chain& chain) override + BitcoinGuiInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, ".gui", arg0, *this)) { - return MakeWalletLoader(chain, *Assert(m_node.args)); + ipc::capnp::SetupNodeClient(m_ipc->context()); } - std::unique_ptr makeEcho() override { return interfaces::MakeEcho(); } interfaces::Ipc* ipc() override { return m_ipc.get(); } - node::NodeContext m_node; std::unique_ptr m_ipc; }; } // namespace diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 97b8dc1161442..56e927e6559b9 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 @@ -9,10 +10,21 @@ #include #include #include +#include #include #include +#include #include +#include +#include + +namespace ipc { +namespace capnp { +void SetupNodeServer(ipc::Context& context); +std::string GlobalArgsNetwork(); +} // namespace capnp +} // namespace ipc namespace init { namespace { @@ -22,18 +34,21 @@ class BitcoinNodeInit : public interfaces::Init { public: BitcoinNodeInit(node::NodeContext& node, const char* arg0) - : m_node(node), - m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) + : m_node(node), m_ipc(interfaces::MakeIpc(EXE_NAME, "", arg0, *this)) { InitContext(m_node); m_node.init = this; + // Extra initialization code that runs when a bitcoin-node process is + // spawned by a bitcoin-gui process, after the ArgsManager configuration + // is transferred from the parent process to the child process. + m_ipc->context().init_process = [this] { + InitLogging(*Assert(m_node.args), m_ipc->logSuffix()); + InitParameterInteraction(*Assert(m_node.args)); + }; + ipc::capnp::SetupNodeServer(m_ipc->context()); } std::unique_ptr makeNode() override { return interfaces::MakeNode(m_node); } std::unique_ptr makeChain() override { return interfaces::MakeChain(m_node); } - std::unique_ptr makeWalletLoader(interfaces::Chain& chain) override - { - return MakeWalletLoader(chain, *Assert(m_node.args)); - } std::unique_ptr makeEcho() override { return interfaces::MakeEcho(); } interfaces::Ipc* ipc() override { return m_ipc.get(); } node::NodeContext& m_node; @@ -46,9 +61,9 @@ namespace interfaces { std::unique_ptr MakeNodeInit(node::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. + // Check if bitcoin-node is being invoked as an IPC server by the gui. 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; } diff --git a/src/init/bitcoin-wallet-ipc.cpp b/src/init/bitcoin-wallet-ipc.cpp new file mode 100644 index 0000000000000..28839b66c5269 --- /dev/null +++ b/src/init/bitcoin-wallet-ipc.cpp @@ -0,0 +1,91 @@ +// 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 +#include + +namespace interfaces { +class Chain; +} // namespace interfaces + +namespace ipc { +namespace capnp { +std::string GlobalArgsNetwork(); +} // namespace capnp +} // namespace ipc + +namespace init { +namespace { +const char* EXE_NAME = "bitcoin-wallet"; + +class BitcoinWalletInit : public interfaces::Init +{ +public: + BitcoinWalletInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, ".wallet", arg0, *this)) + { + // Extra initialization code that runs when a bitcoin-wallet process is + // spawned by a bitcoin-node process, after the ArgsManager + // configuration is transferred from the parent process to the child + // process. + m_ipc->context().init_process = [this] { + // Fix: get rid of kernel dependency. Wallet process should not be + // linked against kernel code. Everything in kernel constructor + // should be moved to a util::SetGlobals function, everything in + // destructor should be moved to a util::UnsetGlobals function and + // that should be called instead. Alternately there can be a + // util::Globals class that becomes a memberof kernel::Context + m_kernel.emplace(); + init::SetLoggingOptions(gArgs, m_ipc->logSuffix()); + if (auto result = init::SetLoggingCategories(gArgs); !result) { + throw std::runtime_error(util::ErrorString(result).original); + } + if (!init::StartLogging(gArgs)) { + throw std::runtime_error("Logging start failure"); + } + }; + } + std::unique_ptr makeWalletLoader(interfaces::Chain& chain) override + { + return MakeWalletLoader(chain, gArgs); + } + interfaces::Ipc* ipc() override { return m_ipc.get(); } + std::unique_ptr m_ipc; + std::optional m_kernel; +}; +} // namespace +} // namespace init + +namespace interfaces { +std::unique_ptr MakeWalletInit(int argc, char* argv[], int& exit_status) +{ + auto init = std::make_unique(argc > 0 ? argv[0] : ""); + // Check if bitcoin-wallet is being invoked as an IPC server. If so, then + // bypass normal execution and just respond to requests over the IPC + // channel and finally return null. + if (init->m_ipc->startSpawnedProcess(argc, argv, exit_status)) { + return nullptr; + } + return init; +} +} // namespace interfaces diff --git a/src/init/common.cpp b/src/init/common.cpp index 6560258ef50b7..d0dd0f8088321 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -45,10 +45,11 @@ void AddLoggingArgs(ArgsManager& argsman) argsman.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); } -void SetLoggingOptions(const ArgsManager& args) +void SetLoggingOptions(const ArgsManager& args, const char* log_suffix) { LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile"); LogInstance().m_file_path = AbsPathForConfigVal(args, args.GetPathArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)); + if (log_suffix && LogInstance().m_file_path != "/dev/null") LogInstance().m_file_path += log_suffix; LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false)); LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); diff --git a/src/init/common.h b/src/init/common.h index b61a77c6d4643..69e7694d665e1 100644 --- a/src/init/common.h +++ b/src/init/common.h @@ -14,7 +14,7 @@ class ArgsManager; namespace init { void AddLoggingArgs(ArgsManager& args); -void SetLoggingOptions(const ArgsManager& args); +void SetLoggingOptions(const ArgsManager& args, const char* log_suffix); [[nodiscard]] util::Result SetLoggingCategories(const ArgsManager& args); [[nodiscard]] util::Result SetLoggingLevel(const ArgsManager& args); bool StartLogging(const ArgsManager& args); diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 9da5cb96373f3..10609af2601c3 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -382,7 +382,8 @@ class ChainClient //! Load saved state. virtual bool load() = 0; - //! Start client execution and provide a scheduler. + //! Start client execution and provide a scheduler. (Scheduler is + //! ignored if client is out-of-process). virtual void start(CScheduler& scheduler) = 0; //! Save state to disk. diff --git a/src/interfaces/ipc.h b/src/interfaces/ipc.h index 963649fc9ab2e..f8638f5cb667e 100644 --- a/src/interfaces/ipc.h +++ b/src/interfaces/ipc.h @@ -65,6 +65,9 @@ class Ipc //! IPC context struct accessor (see struct definition for more description). virtual ipc::Context& context() = 0; + //! Suffix for debug.log to avoid output clashes from different processes. + virtual const char* logSuffix() = 0; + protected: //! Internal implementation of public addCleanup method (above) as a //! type-erased virtual function, since template functions can't be virtual. @@ -72,7 +75,7 @@ class Ipc }; //! Return implementation of Ipc interface. -std::unique_ptr MakeIpc(const char* exe_name, const char* process_argv0, Init& init); +std::unique_ptr MakeIpc(const char* exe_name, const char* log_suffix, const char* process_argv0, Init& init); } // namespace interfaces #endif // BITCOIN_INTERFACES_IPC_H diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 61142366234cc..15ae587b87a3f 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -361,6 +361,8 @@ struct WalletAddress wallet::AddressPurpose purpose; std::string name; + WalletAddress() = default; + WalletAddress(CTxDestination dest, wallet::isminetype is_mine, wallet::AddressPurpose purpose, std::string name) : dest(std::move(dest)), is_mine(is_mine), purpose(std::move(purpose)), name(std::move(name)) { diff --git a/src/ipc/capnp/chain-types.h b/src/ipc/capnp/chain-types.h new file mode 100644 index 0000000000000..dff52d1e3dea4 --- /dev/null +++ b/src/ipc/capnp/chain-types.h @@ -0,0 +1,85 @@ +// 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_CHAIN_TYPES_H +#define BITCOIN_IPC_CAPNP_CHAIN_TYPES_H + +#include +#include +#include +#include +#include + +#include + +//! Specialization of handleRpc needed because it takes a CRPCCommand& reference +//! argument, so a manual cleanup callback is needed to free the passed +//! CRPCCommand struct and proxy ActorCallback object. +template <> +struct mp::ProxyServerMethodTraits +{ + using Context = ServerContext; + static ::capnp::Void invoke(Context& context); +}; + +//! Specialization of start method needed to provide CScheduler& reference +//! argument. +template <> +struct mp::ProxyServerMethodTraits +{ + using ChainContext = ServerContext; + static void invoke(ChainContext& context); + using WalletContext = ServerContext; + static void invoke(WalletContext& context); +}; + +namespace mp { +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::FoundBlock& dest, + ipc::capnp::messages::FoundBlockParam::Builder&& builder); +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + std::function&& fn); +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockResult::Reader& reader, + const interfaces::FoundBlock& dest); + +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::BlockInfo& block, + ipc::capnp::messages::BlockInfo::Builder&& builder); +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::BlockInfo::Reader& reader, + ::capnp::Void builder, + std::function&& fn); + +//! CScheduler& server-side argument handling. Skips argument so it can +//! be handled by ProxyServerCustom code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} + +//! CRPCCommand& server-side argument handling. Skips argument so it can +//! be handled by ProxyServerCustom code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} + +//! Override to avoid assert failures that would happen trying to serialize +//! spent coins. Probably it would be best for Coin serialization code not +//! to assert, but avoiding serialization in this case is harmless. +bool CustomHasValue(InvokeContext& invoke_context, const Coin& coin); +} // namespace mp + +#endif // BITCOIN_IPC_CAPNP_CHAIN_TYPES_H diff --git a/src/ipc/capnp/chain.capnp b/src/ipc/capnp/chain.capnp new file mode 100644 index 0000000000000..d557934f97fb2 --- /dev/null +++ b/src/ipc/capnp/chain.capnp @@ -0,0 +1,184 @@ +# 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. + +@0x94f21a4864bd2c65; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/chain.h"); +$Proxy.include("rpc/server.h"); +$Proxy.includeTypes("ipc/capnp/chain-types.h"); + +using Common = import "common.capnp"; +using Handler = import "handler.capnp"; + +interface Chain $Proxy.wrap("interfaces::Chain") { + destroy @0 (context :Proxy.Context) -> (); + getHeight @1 (context :Proxy.Context) -> (result :Int32, hasResult :Bool); + getBlockHash @2 (context :Proxy.Context, height :Int32) -> (result :Data); + haveBlockOnDisk @3 (context :Proxy.Context, height :Int32) -> (result :Bool); + getTipLocator @4 (context :Proxy.Context) -> (result :Data); + getActiveChainLocator @5 (context :Proxy.Context, blockHash :Data) -> (result :Data); + findLocatorFork @6 (context :Proxy.Context, locator :Data) -> (result :Int32, hasResult :Bool); + hasBlockFilterIndex @7 (context :Proxy.Context, filterType :UInt8) -> (result :Bool); + blockFilterMatchesAny @8 (context :Proxy.Context, filterType :UInt8, blockHash :Data, filterSet :List(Data)) -> (result :Bool, hasResult :Bool); + findBlock @9 (context :Proxy.Context, hash :Data, block :FoundBlockParam) -> (block :FoundBlockResult, result :Bool); + findFirstBlockWithTimeAndHeight @10 (context :Proxy.Context, minTime :Int64, minHeight :Int32, block :FoundBlockParam) -> (block :FoundBlockResult, result :Bool); + findAncestorByHeight @11 (context :Proxy.Context, blockHash :Data, ancestorHeight :Int32, ancestor :FoundBlockParam) -> (ancestor :FoundBlockResult, result :Bool); + findAncestorByHash @12 (context :Proxy.Context, blockHash :Data, ancestorHash :Data, ancestor :FoundBlockParam) -> (ancestor :FoundBlockResult, result :Bool); + findCommonAncestor @13 (context :Proxy.Context, blockHash1 :Data, blockHash2 :Data, ancestor :FoundBlockParam, block1 :FoundBlockParam, block2 :FoundBlockParam) -> (ancestor :FoundBlockResult, block1 :FoundBlockResult, block2 :FoundBlockResult, result :Bool); + findCoins @14 (context :Proxy.Context, coins :List(Common.Pair(Data, Data))) -> (coins :List(Common.Pair(Data, Data))); + guessVerificationProgress @15 (context :Proxy.Context, blockHash :Data) -> (result :Float64); + hasBlocks @16 (context :Proxy.Context, blockHash :Data, minHeight :Int32, maxHeight: Int32, hasMaxHeight :Bool) -> (result :Bool); + isRBFOptIn @17 (context :Proxy.Context, tx :Data) -> (result :Int32); + isInMempool @18 (context :Proxy.Context, tx :Data) -> (result :Bool); + hasDescendantsInMempool @19 (context :Proxy.Context, txid :Data) -> (result :Bool); + broadcastTransaction @20 (context :Proxy.Context, tx: Data, maxTxFee :Int64, relay :Bool) -> (error: Text, result :Bool); + getTransactionAncestry @21 (context :Proxy.Context, txid :Data) -> (ancestors :UInt64, descendants :UInt64, ancestorsize :UInt64, ancestorfees :Int64); + calculateIndividualBumpFees @22 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result: List(Common.PairInt64(Data))); + calculateCombinedBumpFee @23 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result :Int64, hasResult :Bool); + getPackageLimits @24 (context :Proxy.Context) -> (ancestors :UInt64, descendants :UInt64); + checkChainLimits @25 (context :Proxy.Context, tx :Data) -> (result :Common.ResultVoid); + estimateSmartFee @26 (context :Proxy.Context, numBlocks :Int32, conservative :Bool, wantCalc :Bool) -> (calc :FeeCalculation, result :Data); + estimateMaxBlocks @27 (context :Proxy.Context) -> (result :UInt32); + mempoolMinFee @28 (context :Proxy.Context) -> (result :Data); + relayMinFee @29 (context :Proxy.Context) -> (result :Data); + relayIncrementalFee @30 (context :Proxy.Context) -> (result :Data); + relayDustFee @31 (context :Proxy.Context) -> (result :Data); + havePruned @32 (context :Proxy.Context) -> (result :Bool); + isReadyToBroadcast @33 (context :Proxy.Context) -> (result :Bool); + isInitialBlockDownload @34 (context :Proxy.Context) -> (result :Bool); + shutdownRequested @35 (context :Proxy.Context) -> (result :Bool); + initMessage @36 (context :Proxy.Context, message :Text) -> (); + initWarning @37 (context :Proxy.Context, message :Common.BilingualStr) -> (); + initError @38 (context :Proxy.Context, message :Common.BilingualStr) -> (); + showProgress @39 (context :Proxy.Context, title :Text, progress :Int32, resumePossible :Bool) -> (); + handleNotifications @40 (context :Proxy.Context, notifications :ChainNotifications) -> (result :Handler.Handler); + waitForNotificationsIfTipChanged @41 (context :Proxy.Context, oldTip :Data) -> (); + handleRpc @42 (context :Proxy.Context, command :RPCCommand) -> (result :Handler.Handler); + rpcEnableDeprecated @43 (context :Proxy.Context, method :Text) -> (result :Bool); + rpcRunLater @44 (context :Proxy.Context, name :Text, fn: RunLaterCallback, seconds: Int64) -> (); + getSetting @45 (context :Proxy.Context, name :Text) -> (result :Text); + getSettingsList @46 (context :Proxy.Context, name :Text) -> (result :List(Text)); + getRwSetting @47 (context :Proxy.Context, name :Text) -> (result :Text); + updateRwSetting @48 (context :Proxy.Context, name :Text, value :Text, write :Bool) -> (result :Bool); + requestMempoolTransactions @49 (context :Proxy.Context, notifications :ChainNotifications) -> (); + hasAssumedValidChain @50 (context :Proxy.Context) -> (result :Bool); +} + +interface ChainNotifications $Proxy.wrap("interfaces::Chain::Notifications") { + destroy @0 (context :Proxy.Context) -> (); + transactionAddedToMempool @1 (context :Proxy.Context, tx :Data) -> (); + transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :Int32) -> (); + blockConnected @3 (context :Proxy.Context, role: UInt32, block :BlockInfo) -> (); + blockDisconnected @4 (context :Proxy.Context, block :BlockInfo) -> (); + updatedBlockTip @5 (context :Proxy.Context) -> (); + chainStateFlushed @6 (context :Proxy.Context, role: UInt32, locator :Data) -> (); +} + +interface ChainClient $Proxy.wrap("interfaces::ChainClient") { + destroy @0 (context :Proxy.Context) -> (); + registerRpcs @1 (context :Proxy.Context) -> (); + verify @2 (context :Proxy.Context) -> (result :Bool); + load @3 (context :Proxy.Context) -> (result :Bool); + start @4 (context :Proxy.Context, scheduler :Void) -> (); + flush @5 (context :Proxy.Context) -> (); + stop @6 (context :Proxy.Context) -> (); + setMockTime @7 (context :Proxy.Context, time :Int64) -> (); + schedulerMockForward @8 (context :Proxy.Context, time :Int64) -> (); +} + +struct FeeCalculation $Proxy.wrap("FeeCalculation") { + est @0 :EstimationResult; + reason @1 :Int32; + desiredTarget @2 :Int32; + returnedTarget @3 :Int32; +} + +struct EstimationResult $Proxy.wrap("EstimationResult") +{ + pass @0 :EstimatorBucket; + fail @1 :EstimatorBucket; + decay @2 :Float64; + scale @3 :UInt32; +} + +struct EstimatorBucket $Proxy.wrap("EstimatorBucket") +{ + start @0 :Float64; + end @1 :Float64; + withinTarget @2 :Float64; + totalConfirmed @3 :Float64; + inMempool @4 :Float64; + leftMempool @5 :Float64; +} + +struct RPCCommand $Proxy.wrap("CRPCCommand") { + category @0 :Text; + name @1 :Text; + actor @2 :ActorCallback; + argNames @3 :List(RPCArg); + uniqueId @4 :Int64 $Proxy.name("unique_id"); +} + +struct RPCArg { + name @0 :Text; + namedOnly @1: Bool; +} + +interface ActorCallback $Proxy.wrap("ProxyCallback") { + call @0 (context :Proxy.Context, request :JSONRPCRequest, response :Text, lastCallback :Bool) -> (error :Text $Proxy.exception("std::exception"), rpcError :Text $Proxy.exception("UniValue"), typeError :Text $Proxy.exception("UniValue::type_error"), response :Text, result: Bool); +} + +struct JSONRPCRequest $Proxy.wrap("JSONRPCRequest") { + id @0 :Text; + method @1 :Text $Proxy.name("strMethod"); + params @2 :Text; + mode @3 :Int32; + uri @4 :Text $Proxy.name("URI"); + authUser @5 :Text; +} + +interface RunLaterCallback $Proxy.wrap("ProxyCallback>") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +struct FoundBlockParam { + wantHash @0 :Bool; + wantHeight @1 :Bool; + wantTime @2 :Bool; + wantMaxTime @3 :Bool; + wantMtpTime @4 :Bool; + wantInActiveChain @5 :Bool; + nextBlock @6: FoundBlockParam; + wantData @7 :Bool; +} + +struct FoundBlockResult { + hash @0 :Data; + height @1 :Int32; + time @2 :Int64; + maxTime @3 :Int64; + mtpTime @4 :Int64; + inActiveChain @5 :Int64; + nextBlock @6: FoundBlockResult; + data @7 :Data; + found @8 :Bool; +} + +struct BlockInfo $Proxy.wrap("interfaces::BlockInfo") { + # Fields skipped below with Proxy.skip are pointer fields manually handled + # by CustomBuildMessage / CustomPassMessage overloads. + hash @0 :Data $Proxy.skip; + prevHash @1 :Data $Proxy.skip; + height @2 :Int32 = -1; + fileNumber @3 :Int32 = -1 $Proxy.name("file_number"); + dataPos @4 :UInt32 = 0 $Proxy.name("data_pos"); + data @5 :Data $Proxy.skip; + undoData @6 :Data $Proxy.skip; + chainTimeMax @7 :UInt32 = 0 $Proxy.name("chain_time_max"); +} diff --git a/src/ipc/capnp/chain.cpp b/src/ipc/capnp/chain.cpp new file mode 100644 index 0000000000000..fb1ba5a0720d0 --- /dev/null +++ b/src/ipc/capnp/chain.cpp @@ -0,0 +1,200 @@ +// 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mp { +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::FoundBlock& dest, + ipc::capnp::messages::FoundBlockParam::Builder&& builder) +{ + if (dest.m_hash) builder.setWantHash(true); + if (dest.m_height) builder.setWantHeight(true); + if (dest.m_time) builder.setWantTime(true); + if (dest.m_max_time) builder.setWantMaxTime(true); + if (dest.m_mtp_time) builder.setWantMtpTime(true); + if (dest.m_in_active_chain) builder.setWantInActiveChain(true); + if (dest.m_next_block) CustomBuildMessage(invoke_context, *dest.m_next_block, builder.initNextBlock()); + if (dest.m_data) builder.setWantData(true); +} + +void FindBlock(const std::function& find, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + interfaces::FoundBlock& found_block) +{ + uint256 hash; + int height = -1; + int64_t time = -1; + int64_t max_time = -1; + int64_t mtp_time = -1; + bool in_active_chain = -1; + CBlock data; + if (reader.getWantHash()) found_block.hash(hash); + if (reader.getWantHeight()) found_block.height(height); + if (reader.getWantTime()) found_block.time(time); + if (reader.getWantMaxTime()) found_block.maxTime(max_time); + if (reader.getWantMtpTime()) found_block.mtpTime(mtp_time); + if (reader.getWantInActiveChain()) found_block.inActiveChain(in_active_chain); + if (reader.getWantData()) found_block.data(data); + if (reader.hasNextBlock()) { + interfaces::FoundBlock next_block; + found_block.nextBlock(next_block); + FindBlock(find, reader.getNextBlock(), builder.initNextBlock(), next_block); + } else { + find(); + } + if (!found_block.found) return; + if (reader.getWantHash()) builder.setHash(ipc::capnp::ToArray(ipc::capnp::Serialize(hash))); + if (reader.getWantHeight()) builder.setHeight(height); + if (reader.getWantTime()) builder.setTime(time); + if (reader.getWantMaxTime()) builder.setMaxTime(max_time); + if (reader.getWantMtpTime()) builder.setMtpTime(mtp_time); + if (reader.getWantInActiveChain()) builder.setInActiveChain(in_active_chain); + if (reader.getWantData()) builder.setData(ipc::capnp::ToArray(ipc::capnp::Serialize(data))); + builder.setFound(true); +} + +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + std::function&& fn) +{ + interfaces::FoundBlock found_block; + FindBlock([&] { fn(found_block); }, reader, std::move(builder), found_block); +} + +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockResult::Reader& reader, + const interfaces::FoundBlock& dest) +{ + if (!reader.getFound()) return; + if (dest.m_hash) *dest.m_hash = ipc::capnp::Unserialize(reader.getHash()); + if (dest.m_height) *dest.m_height = reader.getHeight(); + if (dest.m_time) *dest.m_time = reader.getTime(); + if (dest.m_max_time) *dest.m_max_time = reader.getMaxTime(); + if (dest.m_mtp_time) *dest.m_mtp_time = reader.getMtpTime(); + if (dest.m_in_active_chain) *dest.m_in_active_chain = reader.getInActiveChain(); + if (dest.m_next_block) CustomReadMessage(invoke_context, reader.getNextBlock(), *dest.m_next_block); + if (dest.m_data) *dest.m_data = ipc::capnp::Unserialize(reader.getData()); + dest.found = true; +} + +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::BlockInfo& block, + ipc::capnp::messages::BlockInfo::Builder&& builder) +{ + // Pointer fields are annotated with Proxy.skip so need to be filled + // manually. Generated code would actually work correctly, though, so this + // could be dropped if there were a Proxy.skip(read_only=True) half-skip + // option. + builder.setHash(ipc::capnp::ToArray(block.hash)); + if (block.prev_hash) builder.setPrevHash(ipc::capnp::ToArray(*block.prev_hash)); + if (block.data) builder.setData(ipc::capnp::ToArray(ipc::capnp::Serialize(*block.data))); + if (block.undo_data) builder.setUndoData(ipc::capnp::ToArray(ipc::capnp::Serialize(*block.undo_data))); + // Copy the remaining fields using the code generated by Proxy.wrap. + mp::BuildOne<0>(mp::TypeList(), invoke_context, builder, block); +} + +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::BlockInfo::Reader& reader, + ::capnp::Void builder, + std::function&& fn) +{ + // Pointer fields are annotated with Proxy.skip because code generator can't + // figure out pointer lifetimes to be able to implementat a ReadField + // implementation for the BlockInfo struct, and the default PassField + // function calls readfield. In theory though, code generator could create a + // PassField specialization for the struct which could allocate pointers for + // the lifetime of the call, like the code below is doing manually. This + // would take care of all pointer fields, though the BlockInfo.hash + // reference field would still need to be handled specially. Or even + // BlockInfo.hash field could be handled automatically if the generated code + // used C++20 designated member initialization. + const uint256 hash = ipc::capnp::ToBlob(reader.getHash()); + std::optional prev_hash; + std::optional data; + std::optional undo_data; + interfaces::BlockInfo block{hash}; + if (reader.hasPrevHash()) { + prev_hash.emplace(ipc::capnp::ToBlob(reader.getPrevHash())); + block.prev_hash = &*prev_hash; + } + if (reader.hasData()) { + data.emplace(ipc::capnp::Unserialize(reader.getData())); + block.data = &*data; + } + if (reader.hasUndoData()) { + undo_data.emplace(ipc::capnp::Unserialize(reader.getUndoData())); + block.undo_data = &*undo_data; + } + mp::ReadField(mp::TypeList(), invoke_context, + mp::Make(reader), mp::ReadDestValue(block)); + fn(block); +} + +::capnp::Void ProxyServerMethodTraits::invoke( + Context& context) +{ + auto params = context.call_context.getParams(); + auto command = params.getCommand(); + + CRPCCommand::Actor actor; + ReadField(TypeList(), context, Make(command.getActor()), ReadDestValue(actor)); + std::vector> args; + ReadField(TypeList(), context, Make(command.getArgNames()), ReadDestValue(args)); + + auto rpc_command = std::make_unique(command.getCategory(), command.getName(), std::move(actor), + std::move(args), command.getUniqueId()); + auto handler = context.proxy_server.m_impl->handleRpc(*rpc_command); + auto results = context.call_context.getResults(); + auto result = kj::heap>(std::shared_ptr(handler.release()), *context.proxy_server.m_context.connection); + result->m_context.cleanup.emplace_back([rpc_command = rpc_command.release()] { delete rpc_command; }); + results.setResult(kj::mv(result)); + return {}; +} + +void ProxyServerMethodTraits::invoke(ChainContext& context) +{ + // This method is never called because ChainClient::Start is overridden by + // WalletLoader::Start. The custom implementation is needed just because + // the CScheduler& argument this is supposed to pass is not serializable. + assert(0); +} + +bool CustomHasValue(InvokeContext& invoke_context, const Coin& coin) +{ + // Spent coins cannot be serialized due to an assert in Coin::Serialize. + return !coin.IsSpent(); +} +} // namespace mp diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h new file mode 100644 index 0000000000000..d7414a1f3eba4 --- /dev/null +++ b/src/ipc/capnp/common-types.h @@ -0,0 +1,446 @@ +// Copyright (c) 2023 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_COMMON_TYPES_H +#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace ipc { +namespace capnp { +//! Convert kj::StringPtr to std::string. +inline std::string ToString(const kj::StringPtr& str) { return {str.cStr(), str.size()}; } + +//! Convert kj::ArrayPtr to std::string. +inline std::string ToString(const kj::ArrayPtr& array) +{ + return {reinterpret_cast(array.begin()), array.size()}; +} + +//! Convert kj::ArrayPtr to base_blob. +template +inline T ToBlob(const kj::ArrayPtr& array) +{ + return T({array.begin(), array.begin() + array.size()}); +} + +//! Convert base_blob to kj::ArrayPtr. +template +inline kj::ArrayPtr ToArray(const T& blob) +{ + return {reinterpret_cast(blob.data()), blob.size()}; +} + +template +auto Wrap(S& s) +{ + return ParamsStream{s, TX_WITH_WITNESS, CAddress::V2_NETWORK}; +} + +//! Serialize bitcoin value. +template +DataStream Serialize(const T& value) +{ + DataStream stream; + auto wrapper{Wrap(stream)}; + value.Serialize(wrapper); + return stream; +} + +//! Deserialize bitcoin value. +template +T Unserialize(const kj::ArrayPtr& data) +{ + SpanReader stream{{data.begin(), data.end()}}; + T value; + auto wrapper{Wrap(stream)}; + value.Unserialize(wrapper); + return value; +} + +//! Use SFINAE to define Serializeable trait which is true if type T has a +//! Serialize(stream) method, false otherwise. +template +struct Serializable { +private: + template + static std::true_type test(decltype(std::declval().Serialize(std::declval()))*); + template + static std::false_type test(...); + +public: + static constexpr bool value = decltype(test(nullptr))::value; +}; + +//! Use SFINAE to define Unserializeable trait which is true if type T has +//! an Unserialize(stream) method, false otherwise. +template +struct Unserializable { +private: + template + static std::true_type test(decltype(std::declval().Unserialize(std::declval()))*); + template + static std::false_type test(...); + +public: + static constexpr bool value = decltype(test(nullptr))::value; +}; + +template +using Deserializable = std::is_constructible; +} // namespace capnp +} // namespace ipc + +//! Functions to serialize / deserialize common bitcoin types. +namespace mp { +//! Overload multiprocess library's CustomBuildField hook to allow any +//! serializable object to be stored in a capnproto Data field or passed to a +//! canproto interface. Use Priority<1> so this hook has medium priority, and +//! higher priority hooks could take precedence over this one. +template +void CustomBuildField( + TypeList, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output, + // Enable if serializeable and if LocalType is not cv or reference + // qualified. If LocalType is cv or reference qualified, it is important to + // fall back to lower-priority Priority<0> implementation of this function + // that strips cv references, to prevent this CustomBuildField overload from + // taking precedence over more narrow overloads for specific LocalTypes. + std::enable_if_t::value && + std::is_same_v>>>* enable = nullptr) +{ + DataStream stream; + auto wrapper{ipc::capnp::Wrap(stream)}; + value.Serialize(wrapper); + auto result = output.init(stream.size()); + memcpy(result.begin(), stream.data(), stream.size()); +} + +//! Overload multiprocess library's CustomReadField hook to allow any object +//! with an Unserialize method to be read from a capnproto Data field or +//! returned from canproto interface. Use Priority<1> so this hook has medium +//! priority, and higher priority hooks could take precedence over this one. +template +decltype(auto) +CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest, + std::enable_if_t::value && + !ipc::capnp::Deserializable::value>* enable = nullptr) +{ + return read_dest.update([&](auto& value) { + if (!input.has()) return; + auto data = input.get(); + SpanReader stream({data.begin(), data.end()}); + auto wrapper{ipc::capnp::Wrap(stream)}; + value.Unserialize(wrapper); + }); +} + +template +decltype(auto) +CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest, + std::enable_if_t::value>* enable = nullptr) +{ + assert(input.has()); + auto data = input.get(); + SpanReader stream({data.begin(), data.end()}); + // TODO: instead of always preferring Deserialize implementation over + // Unserialize should prefer Deserializing when emplacing, unserialize when + // updating. Can implement by adding read_dest.alreadyConstructed() + // constexpr bool method in libmultiprocess. + auto wrapper{ipc::capnp::Wrap(stream)}; + return read_dest.construct(deserialize, wrapper); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& value, + Output&& output) +{ + output.set(value.count()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, + Input&& input, ReadDest&& read_dest) +{ + return read_dest.construct(input.get()); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& value, + Output&& output) +{ + output.set(value.count()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, + Input&& input, ReadDest&& read_dest) +{ + return read_dest.construct(input.get()); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& path, Output&& output) +{ + std::string str = fs::PathToString(path); + auto result = output.init(str.size()); + memcpy(result.begin(), str.data(), str.size()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, + ReadDest&& read_dest) +{ + auto data = input.get(); + return read_dest.construct(fs::PathFromString({CharCast(data.begin()), data.size()})); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& str, Output&& output) +{ + auto result = output.init(str.size()); + // Copy SecureString into output. Caller needs to be responsible for calling + // memory_cleanse later on the output after it is sent. + memcpy(result.begin(), str.data(), str.size()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, + ReadDest&& read_dest) +{ + auto data = input.get(); + // Copy input into SecureString. Caller needs to be responsible for calling + // memory_cleanse on the input. + return read_dest.construct(CharCast(data.begin()), data.size()); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output) +{ + std::string str = value.write(); + auto result = output.init(str.size()); + memcpy(result.begin(), str.data(), str.size()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.read(std::string_view{data.begin(), data.size()}); + }); +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, + Value&& value, Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::string(value.what())); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, + Input&& input, ReadDest&& read_dest) +{ + read_dest.construct(ReadField(TypeList(), invoke_context, input, mp::ReadDestTemp())); +} + +template +void CustomBuildField( + TypeList<>, Priority<1>, InvokeContext& invoke_context, Output&& output, + typename std::enable_if::value>::type* enable = nullptr) +{ + ipc::capnp::BuildGlobalArgs(invoke_context, output.init()); +} + +template +auto CustomPassField(TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) -> + typename std::enable_if::value>::type +{ + ipc::capnp::ReadGlobalArgs(server_context, Accessor::get(server_context.call_context.getParams())); + return fn.invoke(server_context, std::forward(args)...); +} + +//! util::Result builder. +template +void CustomBuildField(TypeList>, Priority<1>, InvokeContext& invoke_context, Value&& value, + Output&& output) +{ + auto result = output.init(); + if (value) { + if constexpr (!std::is_same_v) { + using ValueAccessor = typename ProxyStruct::ValueAccessor; + BuildField(TypeList(), invoke_context, Make(result), *value); + } + } else { + BuildField(TypeList(), invoke_context, Make(result.initError()), + util::ErrorString(value)); + } +} + +//! util::Result reader. +template +decltype(auto) CustomReadField(TypeList>, Priority<1>, InvokeContext& invoke_context, + Input&& input, ReadDest&& read_dest) +{ + auto result = input.get(); + return read_dest.update([&](auto& value) { + if (result.hasError()) { + bilingual_str error; + ReadField(mp::TypeList(), invoke_context, mp::Make(result.getError()), + mp::ReadDestValue(error)); + value = util::Error{std::move(error)}; + } else { + if constexpr (!std::is_same_v) { + assert (result.hasValue()); + ReadField(mp::TypeList(), invoke_context, mp::Make(result.getValue()), + mp::ReadDestEmplace( + mp::TypeList(), [&](auto&&... args) -> auto& { + value = LocalType{std::forward(args)...}; + return *value; + })); + } + } + }); +} + +//! Forwarder to CustomBuildMessage. +template +void CustomBuildField(TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output, + decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr) +{ + CustomBuildMessage(invoke_context, value, std::move(output.init())); +} + +//! Forwarder to CustomReadMessage. +template +decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Reader&& reader, + ReadDest&& read_dest, + decltype(CustomReadMessage(invoke_context, reader.get(), + std::declval()))* enable = nullptr) +{ + return read_dest.update([&](auto& value) { CustomReadMessage(invoke_context, reader.get(), value); }); +} + +template +decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr) +{ + return Accessor::get(message); +} + +template +::capnp::Void MaybeGet(...) +{ + return {}; +} + +template +decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr) +{ + return Accessor::init(message); +} + +template +::capnp::Void MaybeInit(...) +{ + return {}; +} + +//! Forwarder to CustomPassMessage. +template +auto CustomPassField(TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) + -> decltype(CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), + MaybeGet(server_context.call_context.getResults()), nullptr)) +{ + CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), + MaybeInit(server_context.call_context.getResults()), + [&](LocalTypes... param) { fn.invoke(server_context, std::forward(args)..., param...); }); +} + +//! Generic ::capnp::Data field builder for any class that a Span can be +//! constructed from, particularly BaseHash and base_blob classes and +//! subclasses. It's also used to serialize vector +//! GCSFilter::ElementSet set elements. +//! +//! There is currently no corresponding ::capnp::Data CustomReadField function +//! that works using Spans, because the bitcoin classes in the codebase like +//! BaseHash and blob_blob that can converted /to/ Span don't currently have +//! Span constructors that allow them to be constructed /from/ Span. For example +//! CustomReadField function could be written that would allow dropping +//! specialized CustomReadField functions for types like PKHash. +//! +//! For the LocalType = vector case, it's also not necessary to +//! have ::capnp::Data CustomReadField function corresponding to this +//! CustomBuildField function because ::capnp::Data inherits from +//! ::capnp::ArrayPtr, and libmultiprocess already provides a generic +//! CustomReadField function that can read from ::capnp::ArrayPtr into +//! std::vector. +template +void CustomBuildField( + TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output, + typename std::enable_if_t::type>::type>::value>* enable_not_serializable = nullptr, + typename std::enable_if_t>* enable_output = + nullptr, + decltype(Span{value})* enable_value = nullptr) +{ + auto data = Span{value}; + auto result = output.init(data.size()); + memcpy(result.begin(), data.data(), data.size()); +} + +// libmultiprocess only provides read/build functions for std::set, not +// std::unordered_set, so copy and paste those functions here. +// TODO: Move these to libmultiprocess and dedup std::set, std::unordered_set, +// and std::vector implementations. +template +decltype(auto) CustomReadField(TypeList>, Priority<1>, + InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + for (auto item : data) { + ReadField(TypeList(), invoke_context, Make(item), + ReadDestEmplace( + TypeList(), [&](auto&&... args) -> auto& { + return *value.emplace(std::forward(args)...).first; + })); + } + }); +} + +template +void CustomBuildField(TypeList>, Priority<1>, InvokeContext& invoke_context, + Value&& value, Output&& output) +{ + auto list = output.init(value.size()); + size_t i = 0; + for (const auto& elem : value) { + BuildField(TypeList(), invoke_context, ListOutput(list, i), elem); + ++i; + } +} +} // namespace mp + +#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H diff --git a/src/ipc/capnp/common.capnp b/src/ipc/capnp/common.capnp new file mode 100644 index 0000000000000..22934c8e3c6d0 --- /dev/null +++ b/src/ipc/capnp/common.capnp @@ -0,0 +1,54 @@ +# 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. + +@0xcd2c6232cb484a28; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("ipc/capnp/common.h"); +$Proxy.includeTypes("ipc/capnp/common-types.h"); + +struct Settings $Proxy.wrap("common::Settings") { + forcedSettings @0 :List(Pair(Text, Text)) $Proxy.name("forced_settings"); + commandLineOptions @1 :List(Pair(Text, List(Text))) $Proxy.name("command_line_options"); + rwSettings @2 :List(Pair(Text, Text)) $Proxy.name("rw_settings"); + roConfig @3 :List(Pair(Text, List(Pair(Text, List(Text))))) $Proxy.name("ro_config"); +} + +struct GlobalArgs $Proxy.count(0) { + settings @0 :Settings; + configPath @1 : Text; +} + +struct BilingualStr $Proxy.wrap("bilingual_str") { + original @0 :Text; + translated @1 :Text; +} + +struct Result(Value) { + value @0 :Value; + error @1: BilingualStr; +} + +# Wrapper for util::Result +struct ResultVoid(Value) { + error @0: BilingualStr; +} + +struct Pair(Key, Value) { + key @0 :Key; + value @1 :Value; +} + +struct PairInt64(Key) { + key @0 :Key; + value @1 :Int64; +} + +struct PairUInt64(Key) { + key @0 :Key; + value @1 :UInt64; +} diff --git a/src/ipc/capnp/common.cpp b/src/ipc/capnp/common.cpp new file mode 100644 index 0000000000000..ca818ae0302f9 --- /dev/null +++ b/src/ipc/capnp/common.cpp @@ -0,0 +1,51 @@ +// 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 +#include + +namespace ipc { +namespace capnp { +void BuildGlobalArgs(mp::InvokeContext& invoke_context, messages::GlobalArgs::Builder&& builder) +{ + gArgs.LockSettings([&](const common::Settings& settings) { + mp::BuildField(mp::TypeList(), invoke_context, + mp::Make(builder.initSettings()), settings); + }); + builder.setConfigPath(fs::PathToString(gArgs.GetConfigFilePath())); +} + +void ReadGlobalArgs(mp::InvokeContext& invoke_context, const messages::GlobalArgs::Reader& reader) +{ + gArgs.LockSettings([&](common::Settings& settings) { + mp::ReadField(mp::TypeList(), invoke_context, mp::Make(reader.getSettings()), + mp::ReadDestValue(settings)); + }); + gArgs.SetConfigFilePath(fs::PathFromString(ipc::capnp::ToString(reader.getConfigPath()))); + SelectParams(gArgs.GetChainType()); + Context& ipc_context = *static_cast(invoke_context.connection.m_loop.m_context); + ipc_context.init_process(); +} +} // namespace capnp +} // namespace ipc diff --git a/src/ipc/capnp/common.h b/src/ipc/capnp/common.h new file mode 100644 index 0000000000000..6cdbc18ac434d --- /dev/null +++ b/src/ipc/capnp/common.h @@ -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. + +#ifndef BITCOIN_IPC_CAPNP_COMMON_H +#define BITCOIN_IPC_CAPNP_COMMON_H + +#include +#include + +#include + +class RPCTimerInterface; + +namespace mp { +struct InvokeContext; +} // namespace mp + +namespace ipc { +namespace capnp { +//! GlobalArgs client-side argument handling. Builds message from ::gArgs variable. +void BuildGlobalArgs(mp::InvokeContext& invoke_context, messages::GlobalArgs::Builder&& builder); + +//! GlobalArgs server-side argument handling. Reads message into ::gArgs variable. +void ReadGlobalArgs(mp::InvokeContext& invoke_context, const messages::GlobalArgs::Reader& reader); +} // namespace capnp +} // namespace ipc + +#endif // BITCOIN_IPC_CAPNP_COMMON_H diff --git a/src/ipc/capnp/context.h b/src/ipc/capnp/context.h index 06e16144941ba..2676b1eef7e3e 100644 --- a/src/ipc/capnp/context.h +++ b/src/ipc/capnp/context.h @@ -5,8 +5,16 @@ #ifndef BITCOIN_IPC_CAPNP_CONTEXT_H #define BITCOIN_IPC_CAPNP_CONTEXT_H +#include #include +namespace interfaces { +class Node; +} // namespace interfaces +namespace mp { +struct InvokeContext; +} // namespace mp + namespace ipc { namespace capnp { //! Cap'n Proto context struct. Generally the parent ipc::Context struct should @@ -16,6 +24,12 @@ namespace capnp { //! function and object types to capnp hooks. struct Context : ipc::Context { + using MakeNodeClient = std::unique_ptr(mp::InvokeContext& context, + messages::Node::Client&& client); + using MakeNodeServer = kj::Own(mp::InvokeContext& context, + std::shared_ptr impl); + MakeNodeClient* make_node_client = nullptr; + MakeNodeServer* make_node_server = nullptr; }; } // namespace capnp } // namespace ipc diff --git a/src/ipc/capnp/handler.capnp b/src/ipc/capnp/handler.capnp new file mode 100644 index 0000000000000..3c1fadbac894e --- /dev/null +++ b/src/ipc/capnp/handler.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. + +@0xebd8f46e2f369076; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/handler.h"); + +interface Handler $Proxy.wrap("interfaces::Handler") { + destroy @0 (context :Proxy.Context) -> (); + disconnect @1 (context :Proxy.Context) -> (); +} diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h index 42031441b597c..55301db20e567 100644 --- a/src/ipc/capnp/init-types.h +++ b/src/ipc/capnp/init-types.h @@ -5,6 +5,31 @@ #ifndef BITCOIN_IPC_CAPNP_INIT_TYPES_H #define BITCOIN_IPC_CAPNP_INIT_TYPES_H +#include #include +#include +#include + +namespace mp { +//! Specialization of makeWalletLoader needed because it takes a Chain& reference +//! argument, not a unique_ptr argument, so a manual cleanup +//! callback is needed to clean up the ProxyServer proxy object. +template <> +struct ProxyServerMethodTraits +{ + using Context = ServerContext; + static capnp::Void invoke(Context& context); +}; + +//! Chain& server-side argument handling. Skips argument so it can +//! be handled by ProxyServerCustom code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} +} // namespace mp #endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp index e6d358c66556e..21e81aef02261 100644 --- a/src/ipc/capnp/init.capnp +++ b/src/ipc/capnp/init.capnp @@ -8,13 +8,22 @@ using Cxx = import "/capnp/c++.capnp"; $Cxx.namespace("ipc::capnp::messages"); using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/chain.h"); $Proxy.include("interfaces/echo.h"); $Proxy.include("interfaces/init.h"); +$Proxy.include("interfaces/node.h"); $Proxy.includeTypes("ipc/capnp/init-types.h"); +using Chain = import "chain.capnp"; +using Common = import "common.capnp"; using Echo = import "echo.capnp"; +using Node = import "node.capnp"; +using Wallet = import "wallet.capnp"; interface Init $Proxy.wrap("interfaces::Init") { construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo); + makeNode @2 (context :Proxy.Context) -> (result :Node.Node); + makeChain @3 (context :Proxy.Context) -> (result :Chain.Chain); + makeWalletLoader @4 (context :Proxy.Context, globalArgs :Common.GlobalArgs, chain :Chain.Chain) -> (result :Wallet.WalletLoader); } diff --git a/src/ipc/capnp/init.cpp b/src/ipc/capnp/init.cpp new file mode 100644 index 0000000000000..c8d8bb3a8221d --- /dev/null +++ b/src/ipc/capnp/init.cpp @@ -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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mp { +template struct ProxyClient; + +::capnp::Void ProxyServerMethodTraits::invoke(Context& context) +{ + auto params = context.call_context.getParams(); + auto chain = std::make_unique>( + params.getChain(), context.proxy_server.m_context.connection, /* destroy_connection= */ false); + auto wallet_loader = context.proxy_server.m_impl->makeWalletLoader(*chain); + auto results = context.call_context.getResults(); + auto result = kj::heap>(std::shared_ptr(wallet_loader.release()), *context.proxy_server.m_context.connection); + result->m_context.cleanup.emplace_back([chain = chain.release()] { delete chain; }); + results.setResult(kj::mv(result)); + return {}; +} +} // namespace mp diff --git a/src/ipc/capnp/node-types.h b/src/ipc/capnp/node-types.h new file mode 100644 index 0000000000000..948d0f3a3b88c --- /dev/null +++ b/src/ipc/capnp/node-types.h @@ -0,0 +1,114 @@ +// 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_NODE_TYPES_H +#define BITCOIN_IPC_CAPNP_NODE_TYPES_H + +#include +#include +#include +#include + +class CNodeStats; +struct CNodeStateStats; + +//! Specialization of rpcSetTimerInterfaceIfUnset needed because it takes a +//! RPCTimerInterface* argument, which requires custom code to provide a +//! compatible timer. +template <> +struct mp::ProxyServerMethodTraits +{ + using Context = ServerContext; + static void invoke(Context& context); +}; + +//! Specialization of rpcUnsetTimerInterface needed because it takes a +//! RPCTimerInterface* argument, which requires custom code to provide a +//! compatible timer. +template <> +struct mp::ProxyServerMethodTraits +{ + using Context = ServerContext; + static void invoke(Context& context); +}; + +namespace mp { +//! Specialization of MakeProxyClient for Node to that constructs a client +//! object through a function pointer so client object code relying on +//! net_processing types doesn't need to get linked into the bitcoin-wallet +//! executable. +template <> +inline std::unique_ptr CustomMakeProxyClient( + InvokeContext& context, ipc::capnp::messages::Node::Client&& client) +{ + ipc::capnp::Context& ipc_context = *static_cast(context.connection.m_loop.m_context); + return ipc_context.make_node_client(context, kj::mv(client)); +} + +//! Specialization of MakeProxyServer for Node to that constructs a server +//! object through a function pointer so server object code relying on +//! net_processing types doesn't need to get linked into the bitcoin-wallet +//! executable. +template <> +inline kj::Own CustomMakeProxyServer( + InvokeContext& context, std::shared_ptr&& impl) +{ + ipc::capnp::Context& ipc_context = *static_cast(context.connection.m_loop.m_context); + return ipc_context.make_node_server(context, std::move(impl)); +} + +//! RPCTimerInterface* server-side argument handling. Skips argument so it can +//! be handled by ProxyServerCustom code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} + +void CustomBuildMessage(InvokeContext& invoke_context, + const banmap_t& banmap, + ipc::capnp::messages::Banmap::Builder&& builder); + +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::Banmap::Reader& reader, + banmap_t& banmap); + +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& stats, + Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::get<0>(stats)); + if (std::get<1>(stats)) { + auto message_builder = output.get(); + using Accessor = ProxyStruct::StateStatsAccessor; + StructField field_output{message_builder}; + BuildField(TypeList(), invoke_context, field_output, std::get<2>(stats)); + } +} + +void CustomReadMessage(InvokeContext& invoke_context, + ipc::capnp::messages::NodeStats::Reader const& reader, + std::tuple& node_stats); + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, Value&& subnet, Output&& output) +{ + std::string subnet_str = subnet.ToString(); + auto result = output.init(subnet_str.size()); + memcpy(result.begin(), subnet_str.data(), subnet_str.size()); +} + +void CustomReadMessage(InvokeContext& invoke_context, + const capnp::Data::Reader& reader, + CSubNet& subnet); +} // namespace mp + +#endif // BITCOIN_IPC_CAPNP_NODE_TYPES_H diff --git a/src/ipc/capnp/node.capnp b/src/ipc/capnp/node.capnp new file mode 100644 index 0000000000000..b21e02e231ae9 --- /dev/null +++ b/src/ipc/capnp/node.capnp @@ -0,0 +1,206 @@ +# 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. + +@0x92546c47dc734b2e; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("ipc/capnp/node.h"); +$Proxy.includeTypes("ipc/capnp/node-types.h"); + +using Common = import "common.capnp"; +using Handler = import "handler.capnp"; +using Wallet = import "wallet.capnp"; + +interface Node $Proxy.wrap("interfaces::Node") { + destroy @0 (context :Proxy.Context) -> (); + initLogging @1 (context :Proxy.Context) -> (); + initParameterInteraction @2 (context :Proxy.Context) -> (); + getWarnings @3 (context :Proxy.Context) -> (result :Common.BilingualStr); + getLogCategories @4 (context :Proxy.Context) -> (result :UInt32); + getExitStatus @5 (context :Proxy.Context) -> (result :Int32); + baseInitialize @6 (context :Proxy.Context, globalArgs :Common.GlobalArgs) -> (error :Text $Proxy.exception("std::exception"), result :Bool); + appInitMain @7 (context :Proxy.Context) -> (tipInfo :BlockAndHeaderTipInfo, error :Text $Proxy.exception("std::exception"), result :Bool); + appShutdown @8 (context :Proxy.Context) -> (); + startShutdown @9 (context :Proxy.Context) -> (); + shutdownRequested @10 (context :Proxy.Context) -> (result :Bool); + isSettingIgnored @11 (name :Text) -> (result: Bool); + getPersistentSetting @12 (name :Text) -> (result: Text); + updateRwSetting @13 (name :Text, value :Text) -> (); + forceSetting @14 (name :Text, value :Text) -> (); + resetSettings @15 () -> (); + mapPort @16 (context :Proxy.Context, useUPnP :Bool, useNatPnP :Bool) -> (); + getProxy @17 (context :Proxy.Context, net :Int32) -> (proxyInfo :ProxyInfo, result :Bool); + getNodeCount @18 (context :Proxy.Context, flags :Int32) -> (result :UInt64); + getNodesStats @19 (context :Proxy.Context) -> (stats :List(NodeStats), result :Bool); + getBanned @20 (context :Proxy.Context) -> (banmap :Banmap, result :Bool); + ban @21 (context :Proxy.Context, netAddr :Data, banTimeOffset :Int64) -> (result :Bool); + unban @22 (context :Proxy.Context, ip :Data) -> (result :Bool); + disconnectByAddress @23 (context :Proxy.Context, address :Data) -> (result :Bool); + disconnectById @24 (context :Proxy.Context, id :Int64) -> (result :Bool); + listExternalSigners @25 (context :Proxy.Context) -> (result :List(ExternalSigner)); + getTotalBytesRecv @26 (context :Proxy.Context) -> (result :Int64); + getTotalBytesSent @27 (context :Proxy.Context) -> (result :Int64); + getMempoolSize @28 (context :Proxy.Context) -> (result :UInt64); + getMempoolDynamicUsage @29 (context :Proxy.Context) -> (result :UInt64); + getHeaderTip @30 (context :Proxy.Context) -> (height :Int32, blockTime :Int64, result :Bool); + getNumBlocks @31 (context :Proxy.Context) -> (result :Int32); + getBestBlockHash @32 (context :Proxy.Context) -> (result :Data); + getLastBlockTime @33 (context :Proxy.Context) -> (result :Int64); + getVerificationProgress @34 (context :Proxy.Context) -> (result :Float64); + isInitialBlockDownload @35 (context :Proxy.Context) -> (result :Bool); + isLoadingBlocks @36 (context :Proxy.Context) -> (result :Bool); + setNetworkActive @37 (context :Proxy.Context, active :Bool) -> (); + getNetworkActive @38 (context :Proxy.Context) -> (result :Bool); + getDustRelayFee @39 (context :Proxy.Context) -> (result :Data); + executeRpc @40 (context :Proxy.Context, command :Text, params :Text, uri :Text) -> (error :Text $Proxy.exception("std::exception"), rpcError :Text $Proxy.exception("UniValue"), result :Text); + listRpcCommands @41 (context :Proxy.Context) -> (result :List(Text)); + rpcSetTimerInterfaceIfUnset @42 (context :Proxy.Context, iface :Void) -> (); + rpcUnsetTimerInterface @43 (context :Proxy.Context, iface :Void) -> (); + getUnspentOutput @44 (context :Proxy.Context, output :Data) -> (result :Data); + broadcastTransaction @45 (context :Proxy.Context, tx: Data, maxTxFee :Int64) -> (error: Text, result :Int32); + customWalletLoader @46 (context :Proxy.Context) -> (result :Wallet.WalletLoader) $Proxy.name("walletLoader"); + handleInitMessage @47 (context :Proxy.Context, callback :InitMessageCallback) -> (result :Handler.Handler); + handleMessageBox @48 (context :Proxy.Context, callback :MessageBoxCallback) -> (result :Handler.Handler); + handleQuestion @49 (context :Proxy.Context, callback :QuestionCallback) -> (result :Handler.Handler); + handleShowProgress @50 (context :Proxy.Context, callback :ShowNodeProgressCallback) -> (result :Handler.Handler); + handleInitWallet @51 (context :Proxy.Context, callback :InitWalletCallback) -> (result :Handler.Handler); + handleNotifyNumConnectionsChanged @52 (context :Proxy.Context, callback :NotifyNumConnectionsChangedCallback) -> (result :Handler.Handler); + handleNotifyNetworkActiveChanged @53 (context :Proxy.Context, callback :NotifyNetworkActiveChangedCallback) -> (result :Handler.Handler); + handleNotifyAlertChanged @54 (context :Proxy.Context, callback :NotifyAlertChangedCallback) -> (result :Handler.Handler); + handleBannedListChanged @55 (context :Proxy.Context, callback :BannedListChangedCallback) -> (result :Handler.Handler); + handleNotifyBlockTip @56 (context :Proxy.Context, callback :NotifyBlockTipCallback) -> (result :Handler.Handler); + handleNotifyHeaderTip @57 (context :Proxy.Context, callback :NotifyHeaderTipCallback) -> (result :Handler.Handler); +} + +interface ExternalSigner $Proxy.wrap("interfaces::ExternalSigner") { + destroy @0 (context :Proxy.Context) -> (); + getName @1 (context :Proxy.Context) -> (result :Text); +} + +interface InitMessageCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, message :Text) -> (); +} + +interface MessageBoxCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, message :Common.BilingualStr, caption :Text, style :UInt32) -> (result :Bool); +} + +interface QuestionCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, message :Common.BilingualStr, nonInteractiveMessage :Text, caption :Text, style :UInt32) -> (result :Bool); +} + +interface ShowNodeProgressCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, title :Text, progress :Int32, resumePossible :Bool) -> (); +} + +interface InitWalletCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface NotifyNumConnectionsChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, newNumConnections :Int32) -> (); +} + +interface NotifyNetworkActiveChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, networkActive :Bool) -> (); +} + +interface NotifyAlertChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface BannedListChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface NotifyBlockTipCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, syncState: Int32, tip: BlockTip, verificationProgress :Float64) -> (); +} + +interface NotifyHeaderTipCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, syncState: Int32, tip: BlockTip, verificationProgress :Float64) -> (); +} + +struct ProxyInfo $Proxy.wrap("::Proxy") { + proxy @0 :Data; + randomizeCredentials @1 :Bool $Proxy.name("randomize_credentials"); +} + +struct NodeStats $Proxy.wrap("CNodeStats") { + nodeid @0 :Int64 $Proxy.name("nodeid"); + lastSend @1 :Int64 $Proxy.name("m_last_send"); + lastRecv @2 :Int64 $Proxy.name("m_last_recv"); + lastTXTime @3 :Int64 $Proxy.name("m_last_tx_time"); + lastBlockTime @4 :Int64 $Proxy.name("m_last_block_time"); + timeConnected @5 :Int64 $Proxy.name("m_connected"); + timeOffset @6 :Int64 $Proxy.name("nTimeOffset"); + addrName @7 :Text $Proxy.name("m_addr_name"); + version @8 :Int32 $Proxy.name("nVersion"); + cleanSubVer @9 :Text $Proxy.name("cleanSubVer"); + inbound @10 :Bool $Proxy.name("fInbound"); + bip152HighbandwidthTo @11 :Bool $Proxy.name("m_bip152_highbandwidth_to"); + bip152HighbandwidthFrom @12 :Bool $Proxy.name("m_bip152_highbandwidth_from"); + startingHeight @13 :Int32 $Proxy.name("m_starting_height"); + sendBytes @14 :UInt64 $Proxy.name("nSendBytes"); + sendBytesPerMsgType @15 :List(Common.PairUInt64(Text)) $Proxy.name("mapSendBytesPerMsgType"); + recvBytes @16 :UInt64 $Proxy.name("nRecvBytes"); + recvBytesPerMsgType @17 :List(Common.PairUInt64(Text)) $Proxy.name("mapRecvBytesPerMsgType"); + permissionFlags @18 :Int32 $Proxy.name("m_permission_flags"); + pingTime @19 :Int64 $Proxy.name("m_last_ping_time"); + minPingTime @20 :Int64 $Proxy.name("m_min_ping_time"); + addrLocal @21 :Text $Proxy.name("addrLocal"); + addr @22 :Data $Proxy.name("addr"); + addrBind @23 :Data $Proxy.name("addrBind"); + network @24 :Int32 $Proxy.name("m_network"); + mappedAs @25 :UInt32 $Proxy.name("m_mapped_as"); + connType @26 :Int32 $Proxy.name("m_conn_type"); + transportType @27 :UInt8 $Proxy.name("m_transport_type;"); + sessionId @28 :Text $Proxy.name("m_session_id"); + stateStats @29 :NodeStateStats $Proxy.skip; +} + +struct NodeStateStats $Proxy.wrap("CNodeStateStats") { + syncHeight @0 :Int32 $Proxy.name("nSyncHeight"); + commonHeight @1 :Int32 $Proxy.name("nCommonHeight"); + startingHeight @2 :Int32 $Proxy.name("m_starting_height"); + pingWait @3 :Int64 $Proxy.name("m_ping_wait"); + heightInFlight @4 :List(Int32) $Proxy.name("vHeightInFlight"); + addressesProcessed @5 :UInt64 $Proxy.name("m_addr_processed"); + addressesRateLimited @6 :UInt64 $Proxy.name("m_addr_rate_limited"); + addressRelayEnabled @7 :Bool $Proxy.name("m_addr_relay_enabled"); + theirServices @8 :UInt64 $Proxy.name("their_services"); + presyncHeight @9 :Int64 $Proxy.name("presync_height"); +} + +struct Banmap { + json @0 :Text; +} + +struct BlockTip $Proxy.wrap("interfaces::BlockTip") { + blockHeight @0 :Int32 $Proxy.name("block_height"); + blockTime @1 :Int64 $Proxy.name("block_time"); + blockHash @2 :Data $Proxy.name("block_hash"); +} + +struct BlockAndHeaderTipInfo $Proxy.wrap("interfaces::BlockAndHeaderTipInfo") { + blockHeight @0 :Int32 $Proxy.name("block_height"); + blockTime @1 :Int64 $Proxy.name("block_time"); + headerHeight @2 :Int32 $Proxy.name("header_height"); + headerTime @3 :Int64 $Proxy.name("header_time"); + verificationProgress @4 :Float64 $Proxy.name("verification_progress"); +} diff --git a/src/ipc/capnp/node.cpp b/src/ipc/capnp/node.cpp new file mode 100644 index 0000000000000..0f544c4748642 --- /dev/null +++ b/src/ipc/capnp/node.cpp @@ -0,0 +1,142 @@ +// 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 +#include + +#include +#include +#include +#include +#include + +class CNodeStats; +struct CNodeStateStats; + +namespace ipc { +namespace capnp { +void SetupNodeClient(ipc::Context& context) +{ + static_cast(context).make_node_client = mp::MakeProxyClient; +} + +void SetupNodeServer(ipc::Context& context) +{ + static_cast(context).make_node_server = mp::MakeProxyServer; +} + +class RpcTimer : public ::RPCTimerBase +{ +public: + RpcTimer(mp::EventLoop& loop, std::function& fn, int64_t millis) + : m_fn(fn), m_promise(loop.m_io_context.provider->getTimer() + .afterDelay(millis * kj::MILLISECONDS) + .then([this]() { m_fn(); }) + .eagerlyEvaluate(nullptr)) + { + } + ~RpcTimer() noexcept override {} + + std::function m_fn; + kj::Promise m_promise; +}; + +class RpcTimerInterface : public ::RPCTimerInterface +{ +public: + RpcTimerInterface(mp::EventLoop& loop) : m_loop(loop) {} + const char* Name() override { return "Cap'n Proto"; } + RPCTimerBase* NewTimer(std::function& fn, int64_t millis) override + { + RPCTimerBase* result; + m_loop.sync([&] { result = new RpcTimer(m_loop, fn, millis); }); + return result; + } + mp::EventLoop& m_loop; +}; +} // namespace capnp +} // namespace ipc + +namespace mp { +void ProxyServerMethodTraits::invoke(Context& context) +{ + if (!context.proxy_server.m_timer_interface) { + auto timer = std::make_unique(context.proxy_server.m_context.connection->m_loop); + context.proxy_server.m_timer_interface = std::move(timer); + } + context.proxy_server.m_impl->rpcSetTimerInterfaceIfUnset(context.proxy_server.m_timer_interface.get()); +} + +void ProxyServerMethodTraits::invoke(Context& context) +{ + context.proxy_server.m_impl->rpcUnsetTimerInterface(context.proxy_server.m_timer_interface.get()); + context.proxy_server.m_timer_interface.reset(); +} + +void CustomReadMessage(InvokeContext& invoke_context, + ipc::capnp::messages::NodeStats::Reader const& reader, + std::tuple& node_stats) +{ + CNodeStats& node = std::get<0>(node_stats); + ReadField(TypeList(), invoke_context, Make(reader), ReadDestValue(node)); + if ((std::get<1>(node_stats) = reader.hasStateStats())) { + CNodeStateStats& state = std::get<2>(node_stats); + ReadField(TypeList(), invoke_context, Make(reader.getStateStats()), + ReadDestValue(state)); + } +} + +void CustomReadMessage(InvokeContext& invoke_context, + const capnp::Data::Reader& reader, + CSubNet& subnet) +{ + std::string subnet_str = ipc::capnp::ToString(reader); + subnet = LookupSubNet(subnet_str); +} + +void CustomBuildMessage(InvokeContext& invoke_context, + const banmap_t& banmap, + ipc::capnp::messages::Banmap::Builder&& builder) +{ + builder.setJson(BanMapToJson(banmap).write()); +} + +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::Banmap::Reader& reader, + banmap_t& banmap) +{ + UniValue banmap_json; + if (!banmap_json.read(ipc::capnp::ToString(reader.getJson()))) { + throw std::runtime_error("Could not parse banmap json"); + } + BanMapFromJson(banmap_json, banmap); +} + +interfaces::WalletLoader& ProxyClientCustom::walletLoader() +{ + if (!m_wallet_loader) { + m_wallet_loader = self().customWalletLoader(); + } + return *m_wallet_loader; +} +} // namespace mp diff --git a/src/ipc/capnp/node.h b/src/ipc/capnp/node.h new file mode 100644 index 0000000000000..53e3fe96da57a --- /dev/null +++ b/src/ipc/capnp/node.h @@ -0,0 +1,54 @@ +// 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_NODE_H +#define BITCOIN_IPC_CAPNP_NODE_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +class RPCTimerInterface; + +//! Specialization of Node proxy server needed to add m_timer_interface +//! member used by rpcSetTimerInterfaceIfUnset and rpcUnsetTimerInterface +//! methods. +template <> +struct mp::ProxyServerCustom + : public mp::ProxyServerBase +{ +public: + using ProxyServerBase::ProxyServerBase; + std::unique_ptr m_timer_interface; +}; + +//! Specialization of Node client to manage memory of WalletLoader& reference +//! returned by walletLoader(). +template <> +class mp::ProxyClientCustom + : public mp::ProxyClientBase +{ +public: + using ProxyClientBase::ProxyClientBase; + interfaces::WalletLoader& walletLoader() override; + +private: + std::unique_ptr m_wallet_loader; +}; + +//! Specialization of Node::walletLoader client code to manage memory of +//! WalletLoader& reference returned by walletLoader(). +template <> +struct mp::ProxyClientMethodTraits + : public FunctionTraits (interfaces::Node::*const)()> +{ +}; + +#endif // BITCOIN_IPC_CAPNP_NODE_H diff --git a/src/ipc/capnp/wallet-types.h b/src/ipc/capnp/wallet-types.h new file mode 100644 index 0000000000000..bbda504eb85dd --- /dev/null +++ b/src/ipc/capnp/wallet-types.h @@ -0,0 +1,55 @@ +// 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_WALLET_TYPES_H +#define BITCOIN_IPC_CAPNP_WALLET_TYPES_H + +#include +#include +#include +#include +#include + +class CKey; +namespace wallet { +class CCoinControl; +} // namespace wallet + +namespace mp { +void CustomBuildMessage(InvokeContext& invoke_context, + const CTxDestination& dest, + ipc::capnp::messages::TxDestination::Builder&& builder); +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::TxDestination::Reader& reader, + CTxDestination& dest); +void CustomBuildMessage(InvokeContext& invoke_context, + const WitnessUnknown& dest, + ipc::capnp::messages::WitnessUnknown::Builder&& builder); +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::WitnessUnknown::Reader& reader, + WitnessUnknown& dest); +void CustomBuildMessage(InvokeContext& invoke_context, const CKey& key, ipc::capnp::messages::Key::Builder&& builder); +void CustomReadMessage(InvokeContext& invoke_context, const ipc::capnp::messages::Key::Reader& reader, CKey& key); +void CustomBuildMessage(InvokeContext& invoke_context, + const wallet::CCoinControl& coin_control, + ipc::capnp::messages::CoinControl::Builder&& builder); +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::CoinControl::Reader& reader, + wallet::CCoinControl& coin_control); + +//! CustomReadField implementation needed for converting ::capnp::Data messages +//! to PKHash objects because PKHash doesn't currently have a Span constructor. +//! This could be dropped and replaced with a more generic ::capnp::Data +//! CustomReadField function as described in common-types.h near the generic +//! CustomBuildField function which is already used to build ::capnp::Data +//! messages from PKHash objects. +template +decltype(auto) CustomReadField( + TypeList, Priority<1>, InvokeContext& invoke_context, Reader&& reader, ReadDest&& read_dest) +{ + return read_dest.construct(ipc::capnp::ToBlob(reader.get())); +} +} // namespace mp + +#endif // BITCOIN_IPC_CAPNP_WALLET_TYPES_H diff --git a/src/ipc/capnp/wallet.capnp b/src/ipc/capnp/wallet.capnp new file mode 100644 index 0000000000000..55b50353ef6e4 --- /dev/null +++ b/src/ipc/capnp/wallet.capnp @@ -0,0 +1,257 @@ +# 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. + +@0xe234cce74feea00c; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("ipc/capnp/wallet.h"); +$Proxy.includeTypes("ipc/capnp/wallet-types.h"); + +using Chain = import "chain.capnp"; +using Common = import "common.capnp"; +using Handler = import "handler.capnp"; + +interface Wallet $Proxy.wrap("interfaces::Wallet") { + destroy @0 (context :Proxy.Context) -> (); + encryptWallet @1 (context :Proxy.Context, walletPassphrase :Data) -> (result :Bool); + isCrypted @2 (context :Proxy.Context) -> (result :Bool); + lock @3 (context :Proxy.Context) -> (result :Bool); + unlock @4 (context :Proxy.Context, walletPassphrase :Data) -> (result :Bool); + isLocked @5 (context :Proxy.Context) -> (result :Bool); + changeWalletPassphrase @6 (context :Proxy.Context, oldWalletPassphrase :Data, newWalletPassphrase :Data) -> (result :Bool); + abortRescan @7 (context :Proxy.Context) -> (); + backupWallet @8 (context :Proxy.Context, filename :Text) -> (result :Bool); + getWalletName @9 (context :Proxy.Context) -> (result :Text); + getNewDestination @10 (context :Proxy.Context, outputType :Int32, label :Text) -> (result :Common.Result(TxDestination)); + getPubKey @11 (context :Proxy.Context, script :Data, address :Data) -> (pubKey :Data, result :Bool); + signMessage @12 (context :Proxy.Context, message :Text, pkhash :Data) -> (signature :Text, result :Int32); + isSpendable @13 (context :Proxy.Context, dest :TxDestination) -> (result :Bool); + haveWatchOnly @14 (context :Proxy.Context) -> (result :Bool); + setAddressBook @15 (context :Proxy.Context, dest :TxDestination, name :Text, purpose :Int32) -> (result :Bool); + delAddressBook @16 (context :Proxy.Context, dest :TxDestination) -> (result :Bool); + getAddress @17 (context :Proxy.Context, dest :TxDestination, wantName :Bool, wantIsMine :Bool, wantPurpose :Bool) -> (name :Text, isMine :Int32, purpose :Int32, result :Bool); + getAddresses @18 (context :Proxy.Context) -> (result :List(WalletAddress)); + getAddressReceiveRequests @19 (context :Proxy.Context) -> (result :List(Data)); + setAddressReceiveRequest @20 (context :Proxy.Context, dest :TxDestination, id :Data, value :Data) -> (result :Bool); + displayAddress @21 (context :Proxy.Context, dest :TxDestination) -> (result :Bool); + lockCoin @22 (context :Proxy.Context, output :Data, writeToDb :Bool) -> (result :Bool); + unlockCoin @23 (context :Proxy.Context, output :Data) -> (result :Bool); + isLockedCoin @24 (context :Proxy.Context, output :Data) -> (result :Bool); + listLockedCoins @25 (context :Proxy.Context) -> (outputs :List(Data)); + createTransaction @26 (context :Proxy.Context, recipients :List(Recipient), coinControl :CoinControl, sign :Bool, changePos :Int32) -> (changePos :Int32, fee :Int64, result :Common.Result(Data)); + commitTransaction @27 (context :Proxy.Context, tx :Data, valueMap :List(Common.Pair(Text, Text)), orderForm :List(Common.Pair(Text, Text))) -> (); + transactionCanBeAbandoned @28 (context :Proxy.Context, txid :Data) -> (result :Bool); + abandonTransaction @29 (context :Proxy.Context, txid :Data) -> (result :Bool); + transactionCanBeBumped @30 (context :Proxy.Context, txid :Data) -> (result :Bool); + createBumpTransaction @31 (context :Proxy.Context, txid :Data, coinControl :CoinControl) -> (errors :List(Common.BilingualStr), oldFee :Int64, newFee :Int64, mtx :Data, result :Bool); + signBumpTransaction @32 (context :Proxy.Context, mtx :Data) -> (mtx :Data, result :Bool); + commitBumpTransaction @33 (context :Proxy.Context, txid :Data, mtx :Data) -> (errors :List(Common.BilingualStr), bumpedTxid :Data, result :Bool); + getTx @34 (context :Proxy.Context, txid :Data) -> (result :Data); + getWalletTx @35 (context :Proxy.Context, txid :Data) -> (result :WalletTx); + getWalletTxs @36 (context :Proxy.Context) -> (result :List(WalletTx)); + tryGetTxStatus @37 (context :Proxy.Context, txid :Data) -> (txStatus :WalletTxStatus, numBlocks :Int32, blockTime :Int64, result :Bool); + getWalletTxDetails @38 (context :Proxy.Context, txid :Data) -> (txStatus :WalletTxStatus, orderForm :List(Common.Pair(Text, Text)), inMempool :Bool, numBlocks :Int32, result :WalletTx); + getBalances @39 (context :Proxy.Context) -> (result :WalletBalances); + fillPSBT @40 (context :Proxy.Context, sighashType :Int32, sign :Bool, bip32derivs :Bool, wantNSigned :Bool) -> (nSigned: UInt64, psbt :Data, complete :Bool, result :Int32); + tryGetBalances @41 (context :Proxy.Context) -> (balances :WalletBalances, blockHash :Data, result :Bool); + getBalance @42 (context :Proxy.Context) -> (result :Int64); + getAvailableBalance @43 (context :Proxy.Context, coinControl :CoinControl) -> (result :Int64); + txinIsMine @44 (context :Proxy.Context, txin :Data) -> (result :Int32); + txoutIsMine @45 (context :Proxy.Context, txout :Data) -> (result :Int32); + getDebit @46 (context :Proxy.Context, txin :Data, filter :UInt32) -> (result :Int64); + getCredit @47 (context :Proxy.Context, txout :Data, filter :UInt32) -> (result :Int64); + listCoins @48 (context :Proxy.Context) -> (result :List(Common.Pair(TxDestination, List(Common.Pair(Data, WalletTxOut))))); + getCoins @49 (context :Proxy.Context, outputs :List(Data)) -> (result :List(WalletTxOut)); + getRequiredFee @50 (context :Proxy.Context, txBytes :UInt32) -> (result :Int64); + getMinimumFee @51 (context :Proxy.Context, txBytes :UInt32, coinControl :CoinControl, wantReturnedTarget :Bool, wantReason :Bool) -> (returnedTarget :Int32, reason :Int32, result :Int64); + getConfirmTarget @52 (context :Proxy.Context) -> (result :UInt32); + hdEnabled @53 (context :Proxy.Context) -> (result :Bool); + canGetAddresses @54 (context :Proxy.Context) -> (result :Bool); + privateKeysDisabled @55 (context :Proxy.Context) -> (result :Bool); + taprootEnabled @56 (context :Proxy.Context) -> (result :Bool); + hasExternalSigner @57 (context :Proxy.Context) -> (result :Bool); + getDefaultAddressType @58 (context :Proxy.Context) -> (result :Int32); + getDefaultMaxTxFee @59 (context :Proxy.Context) -> (result :Int64); + remove @60 (context :Proxy.Context) -> (); + isLegacy @61 (context :Proxy.Context) -> (result :Bool); + handleUnload @62 (context :Proxy.Context, callback :UnloadWalletCallback) -> (result :Handler.Handler); + handleShowProgress @63 (context :Proxy.Context, callback :ShowWalletProgressCallback) -> (result :Handler.Handler); + handleStatusChanged @64 (context :Proxy.Context, callback :StatusChangedCallback) -> (result :Handler.Handler); + handleAddressBookChanged @65 (context :Proxy.Context, callback :AddressBookChangedCallback) -> (result :Handler.Handler); + handleTransactionChanged @66 (context :Proxy.Context, callback :TransactionChangedCallback) -> (result :Handler.Handler); + handleWatchOnlyChanged @67 (context :Proxy.Context, callback :WatchOnlyChangedCallback) -> (result :Handler.Handler); + handleCanGetAddressesChanged @68 (context :Proxy.Context, callback :CanGetAddressesChangedCallback) -> (result :Handler.Handler); +} + +interface UnloadWalletCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface ShowWalletProgressCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, title :Text, progress :Int32) -> (); +} + +interface StatusChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface AddressBookChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, address :TxDestination, label :Text, isMine :Bool, purpose :Int32, status :Int32) -> (); +} + +interface TransactionChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, txid :Data, status :Int32) -> (); +} + +interface WatchOnlyChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, haveWatchOnly :Bool) -> (); +} + +interface CanGetAddressesChangedCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +interface WalletLoader extends(Chain.ChainClient) $Proxy.wrap("interfaces::WalletLoader") { + createWallet @0 (context :Proxy.Context, name :Text, passphrase :Text, flags :UInt64) -> (warning :List(Common.BilingualStr), result :Common.Result(Wallet)); + loadWallet @1 (context :Proxy.Context, name :Text) -> (warning :List(Common.BilingualStr), result :Common.Result(Wallet)); + getWalletDir @2 (context :Proxy.Context) -> (result :Text); + restoreWallet @3 (context :Proxy.Context, backupFile :Data, name :Text) -> (warning :List(Common.BilingualStr), result :Common.Result(Wallet)); + migrateWallet @4 (context :Proxy.Context, name :Text, passphrase :Text) -> (result :Common.Result(WalletMigrationResult)); + listWalletDir @5 (context :Proxy.Context) -> (result :List(Text)); + getWallets @6 (context :Proxy.Context) -> (result :List(Wallet)); + handleLoadWallet @7 (context :Proxy.Context, callback :LoadWalletCallback) -> (result :Handler.Handler); +} + +interface LoadWalletCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, wallet :Wallet) -> (); +} + +struct Key { + secret @0 :Data; + isCompressed @1 :Bool; +} + +struct TxDestination { + pubKey @0 :Data; + pkHash @1 :Data; + scriptHash @2 :Data; + witnessV0ScriptHash @3 :Data; + witnessV0KeyHash @4 :Data; + witnessV1Taproot @5 :Data; + witnessUnknown @6 :WitnessUnknown; +} + +struct WitnessUnknown +{ + version @0 :UInt32; + program @1 :Data; +} + +struct WalletAddress $Proxy.wrap("interfaces::WalletAddress") { + dest @0 :TxDestination; + isMine @1 :Int32 $Proxy.name("is_mine"); + name @2 :Text; + purpose @3 :Int32; +} + +struct Recipient $Proxy.wrap("wallet::CRecipient") { + dest @0 :TxDestination; + amount @1 :Int64 $Proxy.name("nAmount"); + subtractFeeFromAmount @2 :Bool $Proxy.name("fSubtractFeeFromAmount"); +} + +struct CoinControl { + destChange @0 :TxDestination; + hasChangeType @1 :Bool; + changeType @2 :Int32; + includeUnsafeInputs @3 :Bool; + allowOtherInputs @4 :Bool; + allowWatchOnly @5 :Bool; + overrideFeeRate @6 :Bool; + hasFeeRate @7 :Bool; + feeRate @8 :Data; + hasConfirmTarget @9 :Bool; + confirmTarget @10 :Int32; + hasSignalRbf @11 :Bool; + signalRbf @12 :Bool; + avoidPartialSpends @13 :Bool; + avoidAddressReuse @14 :Bool; + feeMode @15 :Int32; + minDepth @16 :Int32; + maxDepth @17 :Int32; + # Note: The corresponding CCoinControl class has a FlatSigningProvider + # m_external_provider member here, but it is intentionally not included in + # this capnp struct because it is not needed by the GUI. Probably the + # CCoinControl class should be split up to separate options which are + # intended to be set by GUI and RPC interfaces from members containing + # internal wallet data that just happens to be stored in the CCoinControl + # object because it is convenient to pass around. + hasLockTime @18 :UInt32; + lockTime @19 :UInt32; + hasVersion @20 :UInt32; + version @21 :UInt32; + setSelected @22 :List(Data); +} + +struct WalletTx $Proxy.wrap("interfaces::WalletTx") { + tx @0 :Data; + txinIsMine @1 :List(Int32) $Proxy.name("txin_is_mine"); + txoutIsMine @2 :List(Int32) $Proxy.name("txout_is_mine"); + txoutIsChange @3 :List(Bool) $Proxy.name("txout_is_change"); + txoutAddress @4 :List(TxDestination) $Proxy.name("txout_address"); + txoutAddressIsMine @5 :List(Int32) $Proxy.name("txout_address_is_mine"); + credit @6 :Int64; + debit @7 :Int64; + change @8 :Int64; + time @9 :Int64; + valueMap @10 :List(Common.Pair(Text, Text)) $Proxy.name("value_map"); + isCoinbase @11 :Bool $Proxy.name("is_coinbase"); +} + +struct WalletTxOut $Proxy.wrap("interfaces::WalletTxOut") { + txout @0 :Data; + time @1 :Int64; + depthInMainChain @2 :Int32 $Proxy.name("depth_in_main_chain"); + isSpent @3 :Bool $Proxy.name("is_spent"); +} + +struct WalletTxStatus $Proxy.wrap("interfaces::WalletTxStatus") { + blockHeight @0 :Int32 $Proxy.name("block_height"); + blocksToMaturity @1 :Int32 $Proxy.name("blocks_to_maturity"); + depthInMainChain @2 :Int32 $Proxy.name("depth_in_main_chain"); + timeReceived @3 :UInt32 $Proxy.name("time_received"); + lockTime @4 :UInt32 $Proxy.name("lock_time"); + isTrusted @5 :Bool $Proxy.name("is_trusted"); + isAbandoned @6 :Bool $Proxy.name("is_abandoned"); + isCoinbase @7 :Bool $Proxy.name("is_coinbase"); + isInMainChain @8 :Bool $Proxy.name("is_in_main_chain"); +} + +struct WalletBalances $Proxy.wrap("interfaces::WalletBalances") { + balance @0 :Int64; + unconfirmedBalance @1 :Int64 $Proxy.name("unconfirmed_balance"); + immatureBalance @2 :Int64 $Proxy.name("immature_balance"); + haveWatchOnly @3 :Bool $Proxy.name("have_watch_only"); + watchOnlyBalance @4 :Int64 $Proxy.name("watch_only_balance"); + unconfirmedWatchOnlyBalance @5 :Int64 $Proxy.name("unconfirmed_watch_only_balance"); + immatureWatchOnlyBalance @6 :Int64 $Proxy.name("immature_watch_only_balance"); +} + +struct WalletMigrationResult $Proxy.wrap("interfaces::WalletMigrationResult") { + wallet @0 :Wallet; + watchonlyWalletName @1 :Text $Proxy.name("watchonly_wallet_name"); + solvablesWalletName @2 :Text $Proxy.name("solvables_wallet_name"); + backupPath @3 :Text $Proxy.name("backup_path"); +} diff --git a/src/ipc/capnp/wallet.cpp b/src/ipc/capnp/wallet.cpp new file mode 100644 index 0000000000000..40556da36650e --- /dev/null +++ b/src/ipc/capnp/wallet.cpp @@ -0,0 +1,222 @@ +// 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 +#include +#include