Merge remote-tracking branch 'origin/pull/10102/head'

This commit is contained in:
Ryan Ofsky 2024-12-04 14:09:05 -05:00
commit 5a865918d6
69 changed files with 2360 additions and 89 deletions

View file

@ -15,7 +15,7 @@ MAPPING = {
# define functions and variables declared in corresponding .h files is # define functions and variables declared in corresponding .h files is
# incorrect. # incorrect.
HEADER_MODULE_PATHS = [ HEADER_MODULE_PATHS = [
'interfaces/' 'ipc/'
] ]
def module_name(path): def module_name(path):

View file

@ -48,7 +48,7 @@ See [dependencies.md](dependencies.md) for a complete overview.
To install, run the following from your terminal: To install, run the following from your terminal:
``` bash ``` bash
brew install cmake boost pkgconf libevent brew install cmake boost pkgconf libevent capnp
``` ```
### 4. Clone Bitcoin repository ### 4. Clone Bitcoin repository

View file

@ -73,7 +73,7 @@ GUI dependencies:
Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, we need to install Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, we need to install
the necessary parts of Qt, the libqrencode and pass `-DBUILD_GUI=ON`. Skip if you don't intend to use the GUI. the necessary parts of Qt, the libqrencode and pass `-DBUILD_GUI=ON`. Skip if you don't intend to use the GUI.
sudo apt-get install qtbase5-dev qttools5-dev qttools5-dev-tools sudo apt-get install qtbase5-dev qttools5-dev qttools5-dev-tools libcapnp-dev capnproto
Additionally, to support Wayland protocol for modern desktop environments: Additionally, to support Wayland protocol for modern desktop environments:

View file

@ -41,3 +41,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 | | [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 | | [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 |

View file

@ -34,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. `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. [#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. 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.

View file

@ -139,6 +139,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
net_types.cpp net_types.cpp
netaddress.cpp netaddress.cpp
netbase.cpp netbase.cpp
node/interface_ui.cpp
outputtype.cpp outputtype.cpp
policy/feerate.cpp policy/feerate.cpp
policy/policy.cpp policy/policy.cpp
@ -173,12 +174,18 @@ set(installable_targets)
if(ENABLE_WALLET) if(ENABLE_WALLET)
add_subdirectory(wallet) add_subdirectory(wallet)
if(BUILD_WALLET_TOOL) if(BUILD_WALLET_TOOL OR WITH_MULTIPROCESS)
add_executable(bitcoin-wallet add_executable(bitcoin-wallet
bitcoin-wallet.cpp bitcoin-wallet.cpp
init/bitcoin-wallet.cpp
wallet/wallettool.cpp wallet/wallettool.cpp
) )
if(WITH_MULTIPROCESS)
# FIX: Dependency on kernel should be dropped. See BitcoinWalletInit constructor comment.
target_sources(bitcoin-wallet PRIVATE init/bitcoin-wallet-ipc.cpp kernel/context.cpp)
target_link_libraries(bitcoin-wallet bitcoin_ipc)
else()
target_sources(bitcoin-wallet PRIVATE init/bitcoin-wallet.cpp)
endif()
add_windows_resources(bitcoin-wallet bitcoin-wallet-res.rc) add_windows_resources(bitcoin-wallet bitcoin-wallet-res.rc)
target_link_libraries(bitcoin-wallet target_link_libraries(bitcoin-wallet
core_interface core_interface
@ -237,7 +244,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
node/context.cpp node/context.cpp
node/database_args.cpp node/database_args.cpp
node/eviction.cpp node/eviction.cpp
node/interface_ui.cpp
node/interfaces.cpp node/interfaces.cpp
node/kernel_notifications.cpp node/kernel_notifications.cpp
node/mempool_args.cpp node/mempool_args.cpp

View file

@ -14,6 +14,7 @@
#include <init.h> #include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/init.h> #include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <kernel/context.h> #include <kernel/context.h>
#include <node/context.h> #include <node/context.h>
#include <node/interface_ui.h> #include <node/interface_ui.h>
@ -178,7 +179,8 @@ static bool AppInit(NodeContext& node)
// -server defaults to true for bitcoind but not for the GUI so do this here // -server defaults to true for bitcoind but not for the GUI so do this here
args.SoftSetBoolArg("-server", true); args.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console // 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); InitParameterInteraction(args);
if (!AppInitBasicSetup(args, node.exit_status)) { if (!AppInitBasicSetup(args, node.exit_status)) {
// InitError will have been called with detailed error, which ends up on console // InitError will have been called with detailed error, which ends up on console

View file

@ -798,9 +798,9 @@ void InitParameterInteraction(ArgsManager& args)
* Note that this is called very early in the process lifetime, so you should be * Note that this is called very early in the process lifetime, so you should be
* careful about what global state you rely on here. * 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(); init::LogPackageVersion();
} }

View file

@ -33,7 +33,7 @@ bool ShutdownRequested(node::NodeContext& node);
void Interrupt(node::NodeContext& node); void Interrupt(node::NodeContext& node);
void Shutdown(node::NodeContext& node); void Shutdown(node::NodeContext& node);
//!Initialize the logging infrastructure //!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 //!Parameter interaction: change current parameters depending on various rules
void InitParameterInteraction(ArgsManager& args); void InitParameterInteraction(ArgsManager& args);

View file

@ -3,17 +3,18 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <init.h> #include <init.h>
#include <interfaces/chain.h>
#include <interfaces/echo.h>
#include <interfaces/init.h> #include <interfaces/init.h>
#include <interfaces/ipc.h> #include <interfaces/ipc.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
#include <node/context.h>
#include <util/check.h> #include <util/check.h>
#include <memory> #include <memory>
namespace ipc {
namespace capnp {
void SetupNodeClient(ipc::Context& context);
} // namespace capnp
} // namespace ipc
namespace init { namespace init {
namespace { namespace {
const char* EXE_NAME = "bitcoin-gui"; const char* EXE_NAME = "bitcoin-gui";
@ -21,25 +22,16 @@ const char* EXE_NAME = "bitcoin-gui";
class BitcoinGuiInit : public interfaces::Init class BitcoinGuiInit : public interfaces::Init
{ {
public: public:
BitcoinGuiInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) BitcoinGuiInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, ".gui", arg0, *this))
{ {
InitContext(m_node); ipc::capnp::SetupNodeClient(m_ipc->context());
m_node.init = this;
} }
std::unique_ptr<interfaces::Node> makeNode() override { return interfaces::MakeNode(m_node); }
std::unique_ptr<interfaces::Chain> makeChain() override { return interfaces::MakeChain(m_node); }
std::unique_ptr<interfaces::WalletLoader> makeWalletLoader(interfaces::Chain& chain) override
{
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
interfaces::Ipc* ipc() override { return m_ipc.get(); } interfaces::Ipc* ipc() override { return m_ipc.get(); }
// bitcoin-gui accepts -ipcbind option even though it does not use it // bitcoin-gui accepts -ipcbind option even though it does not use it
// directly. It just returns true here to accept the option because // directly. It just returns true here to accept the option because
// bitcoin-node accepts the option, and bitcoin-gui accepts all bitcoin-node // bitcoin-node accepts the option, and bitcoin-gui accepts all bitcoin-node
// options and will start the node with those options. // options and will start the node with those options.
bool canListenIpc() override { return true; } bool canListenIpc() override { return true; }
node::NodeContext m_node;
std::unique_ptr<interfaces::Ipc> m_ipc; std::unique_ptr<interfaces::Ipc> m_ipc;
}; };
} // namespace } // namespace

View file

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <init.h> #include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/echo.h> #include <interfaces/echo.h>
@ -9,10 +10,21 @@
#include <interfaces/ipc.h> #include <interfaces/ipc.h>
#include <interfaces/node.h> #include <interfaces/node.h>
#include <interfaces/wallet.h> #include <interfaces/wallet.h>
#include <ipc/context.h>
#include <node/context.h> #include <node/context.h>
#include <util/check.h> #include <util/check.h>
#include <functional>
#include <memory> #include <memory>
#include <string>
#include <utility>
namespace ipc {
namespace capnp {
void SetupNodeServer(ipc::Context& context);
std::string GlobalArgsNetwork();
} // namespace capnp
} // namespace ipc
namespace init { namespace init {
namespace { namespace {
@ -22,19 +34,22 @@ class BitcoinNodeInit : public interfaces::Init
{ {
public: public:
BitcoinNodeInit(node::NodeContext& node, const char* arg0) BitcoinNodeInit(node::NodeContext& node, const char* arg0)
: m_node(node), : m_node(node), m_ipc(interfaces::MakeIpc(EXE_NAME, "", arg0, *this))
m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this))
{ {
InitContext(m_node); InitContext(m_node);
m_node.init = this; 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<interfaces::Node> makeNode() override { return interfaces::MakeNode(m_node); } std::unique_ptr<interfaces::Node> makeNode() override { return interfaces::MakeNode(m_node); }
std::unique_ptr<interfaces::Chain> makeChain() override { return interfaces::MakeChain(m_node); } std::unique_ptr<interfaces::Chain> makeChain() override { return interfaces::MakeChain(m_node); }
std::unique_ptr<interfaces::Mining> makeMining() override { return interfaces::MakeMining(m_node); } std::unique_ptr<interfaces::Mining> makeMining() override { return interfaces::MakeMining(m_node); }
std::unique_ptr<interfaces::WalletLoader> makeWalletLoader(interfaces::Chain& chain) override
{
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
interfaces::Ipc* ipc() override { return m_ipc.get(); } interfaces::Ipc* ipc() override { return m_ipc.get(); }
bool canListenIpc() override { return true; } bool canListenIpc() override { return true; }
@ -48,9 +63,9 @@ namespace interfaces {
std::unique_ptr<Init> MakeNodeInit(node::NodeContext& node, int argc, char* argv[], int& exit_status) std::unique_ptr<Init> MakeNodeInit(node::NodeContext& node, int argc, char* argv[], int& exit_status)
{ {
auto init = std::make_unique<init::BitcoinNodeInit>(node, argc > 0 ? argv[0] : ""); auto init = std::make_unique<init::BitcoinNodeInit>(node, argc > 0 ? argv[0] : "");
// Check if bitcoin-node is being invoked as an IPC server. If so, then // Check if bitcoin-node is being invoked as an IPC server by the gui. If
// bypass normal execution and just respond to requests over the IPC // so, then bypass normal execution and just respond to requests over the
// channel and return null. // IPC channel and return null.
if (init->m_ipc->startSpawnedProcess(argc, argv, exit_status)) { if (init->m_ipc->startSpawnedProcess(argc, argv, exit_status)) {
return nullptr; return nullptr;
} }

View file

@ -0,0 +1,94 @@
// 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 <chainparams.h>
#include <common/args.h>
#include <init/common.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/wallet.h>
#include <ipc/context.h>
#include <kernel/context.h>
#include <key.h>
#include <logging.h>
#include <pubkey.h>
#include <random.h>
#include <util/fs.h>
#include <util/translation.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
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. This is a subset of the intialization done in bitcoind
// AppInit() function, doing only initialization needed by the wallet.
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();
m_ecc_context.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<interfaces::WalletLoader> makeWalletLoader(interfaces::Chain& chain) override
{
return MakeWalletLoader(chain, gArgs);
}
interfaces::Ipc* ipc() override { return m_ipc.get(); }
std::unique_ptr<interfaces::Ipc> m_ipc;
std::optional<kernel::Context> m_kernel;
std::optional<ECC_Context> m_ecc_context;
};
} // namespace
} // namespace init
namespace interfaces {
std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status)
{
auto init = std::make_unique<init::BitcoinWalletInit>(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

View file

@ -42,10 +42,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); 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_print_to_file = !args.IsArgNegated("-debuglogfile");
LogInstance().m_file_path = AbsPathForConfigVal(args, args.GetPathArg("-debuglogfile", DEFAULT_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_print_to_console = args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false));
LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);

View file

@ -14,7 +14,7 @@ class ArgsManager;
namespace init { namespace init {
void AddLoggingArgs(ArgsManager& args); void AddLoggingArgs(ArgsManager& args);
void SetLoggingOptions(const ArgsManager& args); void SetLoggingOptions(const ArgsManager& args, const char* log_suffix);
[[nodiscard]] util::Result<void> SetLoggingCategories(const ArgsManager& args); [[nodiscard]] util::Result<void> SetLoggingCategories(const ArgsManager& args);
[[nodiscard]] util::Result<void> SetLoggingLevel(const ArgsManager& args); [[nodiscard]] util::Result<void> SetLoggingLevel(const ArgsManager& args);
bool StartLogging(const ArgsManager& args); bool StartLogging(const ArgsManager& args);

View file

@ -354,7 +354,7 @@ public:
//! support for writing null values to settings.json. //! support for writing null values to settings.json.
//! Depending on the action returned by the update function, this will either //! Depending on the action returned by the update function, this will either
//! update the setting in memory or write the updated settings to disk. //! update the setting in memory or write the updated settings to disk.
virtual bool updateRwSetting(const std::string& name, const SettingsUpdate& update_function) = 0; virtual bool updateRwSetting(const std::string& name, SettingsUpdate update_function) = 0;
//! Replace a setting in <datadir>/settings.json with a new value. //! Replace a setting in <datadir>/settings.json with a new value.
//! Null can be passed to erase the setting. //! Null can be passed to erase the setting.
@ -401,7 +401,8 @@ public:
//! Load saved state. //! Load saved state.
virtual bool load() = 0; 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; virtual void start(CScheduler& scheduler) = 0;
//! Save state to disk. //! Save state to disk.

View file

@ -81,6 +81,9 @@ public:
//! IPC context struct accessor (see struct definition for more description). //! IPC context struct accessor (see struct definition for more description).
virtual ipc::Context& context() = 0; virtual ipc::Context& context() = 0;
//! Suffix for debug.log to avoid output clashes from different processes.
virtual const char* logSuffix() = 0;
protected: protected:
//! Internal implementation of public addCleanup method (above) as a //! Internal implementation of public addCleanup method (above) as a
//! type-erased virtual function, since template functions can't be virtual. //! type-erased virtual function, since template functions can't be virtual.
@ -88,7 +91,7 @@ protected:
}; };
//! Return implementation of Ipc interface. //! Return implementation of Ipc interface.
std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* process_argv0, Init& init); std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* log_suffix, const char* process_argv0, Init& init);
} // namespace interfaces } // namespace interfaces
#endif // BITCOIN_INTERFACES_IPC_H #endif // BITCOIN_INTERFACES_IPC_H

View file

@ -369,6 +369,8 @@ struct WalletAddress
wallet::AddressPurpose purpose; wallet::AddressPurpose purpose;
std::string name; std::string name;
WalletAddress() = default;
WalletAddress(CTxDestination dest, wallet::isminetype is_mine, wallet::AddressPurpose purpose, std::string name) 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)) : dest(std::move(dest)), is_mine(is_mine), purpose(std::move(purpose)), name(std::move(name))
{ {

View file

@ -3,17 +3,26 @@
# file COPYING or https://opensource.org/license/mit/. # file COPYING or https://opensource.org/license/mit/.
add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL
capnp/chain.cpp
capnp/common.cpp
capnp/init.cpp
capnp/mining.cpp capnp/mining.cpp
capnp/node.cpp
capnp/protocol.cpp capnp/protocol.cpp
capnp/wallet.cpp
interfaces.cpp interfaces.cpp
process.cpp process.cpp
) )
target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR} target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR}
capnp/chain.capnp
capnp/common.capnp capnp/common.capnp
capnp/echo.capnp capnp/echo.capnp
capnp/handler.capnp
capnp/init.capnp capnp/init.capnp
capnp/mining.capnp capnp/mining.capnp
capnp/node.capnp
capnp/wallet.capnp
) )
target_link_libraries(bitcoin_ipc target_link_libraries(bitcoin_ipc

View file

@ -0,0 +1,96 @@
// Copyright (c) 2024 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 <ipc/capnp/chain.capnp.proxy.h>
#include <ipc/capnp/common.capnp.proxy-types.h>
#include <ipc/capnp/handler.capnp.proxy-types.h>
#include <interfaces/chain.h>
#include <ipc/capnp/wallet.capnp.proxy.h>
#include <policy/fees.h>
#include <rpc/server.h>
//! 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<ipc::capnp::messages::Chain::HandleRpcParams>
{
using Context = ServerContext<ipc::capnp::messages::Chain,
ipc::capnp::messages::Chain::HandleRpcParams,
ipc::capnp::messages::Chain::HandleRpcResults>;
static ::capnp::Void invoke(Context& context);
};
//! Specialization of start method needed to provide CScheduler& reference
//! argument.
template <>
struct mp::ProxyServerMethodTraits<ipc::capnp::messages::ChainClient::StartParams>
{
using ChainContext = ServerContext<ipc::capnp::messages::ChainClient,
ipc::capnp::messages::ChainClient::StartParams,
ipc::capnp::messages::ChainClient::StartResults>;
static void invoke(ChainContext& context);
using WalletLoaderContext = ServerContext<ipc::capnp::messages::WalletLoader,
ipc::capnp::messages::ChainClient::StartParams,
ipc::capnp::messages::ChainClient::StartResults>;
static void invoke(WalletLoaderContext& context);
};
namespace mp {
//! Overload CustomBuildMessage, CustomPassMessage, and CustomReadMessage to
//! serialize interfaces::FoundBlock parameters. Custom conversion functions are
//! needed because there is not a 1:1 correspondence between members of the C++
//! FoundBlock class and the Cap'n Proto FoundBlockParam and FoundBlockResult
//! structs. Separate param and result structs are needed because Cap'n Proto
//! only has input and output parameters, not in/out parameters like C++.
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<void(const interfaces::FoundBlock&)>&& fn);
void CustomReadMessage(InvokeContext& invoke_context,
const ipc::capnp::messages::FoundBlockResult::Reader& reader,
const interfaces::FoundBlock& dest);
//! Overload CustomBuildMessage and CustomPassMessage to serialize
//! interfaces::BlockInfo parameters. Custom conversion functions are needed
//! because BlockInfo structs contain pointer and reference members, and custom
//! code is needed to deal with their lifetimes. Specifics are described in the
//! implementation of these functions.
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<void(const interfaces::BlockInfo&)>&& fn);
//! CScheduler& server-side argument handling. Skips argument so it can
//! be handled by ProxyServerMethodTraits ChainClient::Start code.
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
void CustomPassField(TypeList<CScheduler&>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
fn.invoke(server_context, std::forward<Args>(args)...);
}
//! CRPCCommand& server-side argument handling. Skips argument so it can
//! be handled by ProxyServerMethodTraits Chain::HandleRpc code.
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
void CustomPassField(TypeList<const CRPCCommand&>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
fn.invoke(server_context, std::forward<Args>(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

195
src/ipc/capnp/chain.capnp Normal file
View file

@ -0,0 +1,195 @@
# Copyright (c) 2024 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, txid :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, update: SettingsUpdateCallback) -> (result :Bool);
overwriteRwSetting @49 (context :Proxy.Context, name :Text, value :Text, action :Int32) -> (result :Bool);
deleteRwSettings @50 (context :Proxy.Context, name :Text, action: Int32) -> (result :Bool);
requestMempoolTransactions @51 (context :Proxy.Context, notifications :ChainNotifications) -> ();
hasAssumedValidChain @52 (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<CRPCCommand::Actor>") {
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 :UInt32;
uri @4 :Text $Proxy.name("URI");
authUser @5 :Text;
peerAddr @6 :Text;
version @7: Int32 $Proxy.name("m_json_version");
}
interface RunLaterCallback $Proxy.wrap("ProxyCallback<std::function<void()>>") {
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;
wantLocator @6 :Bool;
nextBlock @7: FoundBlockParam;
wantData @8 :Bool;
}
struct FoundBlockResult {
hash @0 :Data;
height @1 :Int32;
time @2 :Int64;
maxTime @3 :Int64;
mtpTime @4 :Int64;
inActiveChain @5 :Int64;
locator @6 :Data;
nextBlock @7: FoundBlockResult;
data @8 :Data;
found @9 :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");
}
interface SettingsUpdateCallback $Proxy.wrap("ProxyCallback<interfaces::SettingsUpdate>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, value :Text) -> (value :Text, result: Int32, hasResult: Bool);
}

205
src/ipc/capnp/chain.cpp Normal file
View file

@ -0,0 +1,205 @@
// Copyright (c) 2024 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 <capnp/blob.h>
#include <capnp/capability.h>
#include <capnp/list.h>
#include <coins.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <interfaces/ipc.h>
#include <ipc/capnp/chain-types.h>
#include <ipc/capnp/chain.capnp.h>
#include <ipc/capnp/chain.capnp.proxy.h>
#include <ipc/capnp/chain.capnp.proxy-types.h>
#include <ipc/capnp/common-types.h>
#include <ipc/capnp/context.h>
#include <ipc/capnp/handler.capnp.proxy.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/util.h>
#include <primitives/block.h>
#include <rpc/server.h>
#include <streams.h>
#include <uint256.h>
#include <undo.h>
#include <assert.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
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_locator) builder.setWantLocator(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<void()>& 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;
CBlockLocator locator;
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.getWantLocator()) found_block.locator(locator);
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.getWantLocator()) builder.setLocator(ipc::capnp::ToArray(ipc::capnp::Serialize(locator)));
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<void(const interfaces::FoundBlock&)>&& 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<uint256>(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_locator) *dest.m_locator = ipc::capnp::Unserialize<CBlockLocator>(reader.getLocator());
if (dest.m_next_block) CustomReadMessage(invoke_context, reader.getNextBlock(), *dest.m_next_block);
if (dest.m_data) *dest.m_data = ipc::capnp::Unserialize<CBlock>(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<interfaces::BlockInfo>(), invoke_context, builder, block);
}
void CustomPassMessage(InvokeContext& invoke_context,
const ipc::capnp::messages::BlockInfo::Reader& reader,
::capnp::Void builder,
std::function<void(const interfaces::BlockInfo&)>&& 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<uint256>(reader.getHash());
std::optional<const uint256> prev_hash;
std::optional<const CBlock> data;
std::optional<const CBlockUndo> undo_data;
interfaces::BlockInfo block{hash};
if (reader.hasPrevHash()) {
prev_hash.emplace(ipc::capnp::ToBlob<uint256>(reader.getPrevHash()));
block.prev_hash = &*prev_hash;
}
if (reader.hasData()) {
data.emplace(ipc::capnp::Unserialize<CBlock>(reader.getData()));
block.data = &*data;
}
if (reader.hasUndoData()) {
undo_data.emplace(ipc::capnp::Unserialize<CBlockUndo>(reader.getUndoData()));
block.undo_data = &*undo_data;
}
mp::ReadField(mp::TypeList<interfaces::BlockInfo>(), invoke_context,
mp::Make<mp::ValueField>(reader), mp::ReadDestValue(block));
fn(block);
}
::capnp::Void ProxyServerMethodTraits<ipc::capnp::messages::Chain::HandleRpcParams>::invoke(
Context& context)
{
auto params = context.call_context.getParams();
auto command = params.getCommand();
CRPCCommand::Actor actor;
ReadField(TypeList<decltype(actor)>(), context, Make<ValueField>(command.getActor()), ReadDestValue(actor));
std::vector<std::pair<std::string, bool>> args;
ReadField(TypeList<decltype(args)>(), context, Make<ValueField>(command.getArgNames()), ReadDestValue(args));
auto rpc_command = std::make_unique<CRPCCommand>(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<ProxyServer<ipc::capnp::messages::Handler>>(std::shared_ptr<interfaces::Handler>(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<ipc::capnp::messages::ChainClient::StartParams>::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

View file

@ -5,22 +5,57 @@
#ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H #ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H
#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H #define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
#include <chainparams.h>
#include <clientversion.h> #include <clientversion.h>
#include <consensus/validation.h>
#include <interfaces/types.h> #include <interfaces/types.h>
#include <ipc/capnp/common.capnp.proxy.h>
#include <netbase.h>
#include <net_processing.h>
#include <net_types.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <protocol.h>
#include <serialize.h> #include <serialize.h>
#include <streams.h> #include <streams.h>
#include <univalue.h> #include <univalue.h>
#include <util/result.h>
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
#include <cstddef> #include <cstddef>
#include <mp/proxy-types.h> #include <mp/proxy-types.h>
#include <type_traits> #include <type_traits>
#include <unordered_set>
#include <utility> #include <utility>
namespace ipc { namespace ipc {
namespace capnp { namespace capnp {
//! Construct a ParamStream wrapping a data stream with serialization parameters //! Convert kj::StringPtr to std::string.
//! needed to pass transaction objects between bitcoin processes. 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<const kj::byte>& array)
{
return {reinterpret_cast<const char*>(array.begin()), array.size()};
}
//! Convert kj::ArrayPtr to base_blob.
template <typename T>
inline T ToBlob(const kj::ArrayPtr<const kj::byte>& array)
{
return T({array.begin(), array.begin() + array.size()});
}
//! Convert base_blob to kj::ArrayPtr.
template <typename T>
inline kj::ArrayPtr<const kj::byte> ToArray(const T& blob)
{
return {reinterpret_cast<const kj::byte*>(blob.data()), blob.size()};
}
//! Construct a ParamStream wrapping data stream with serialization parameters
//! needed to pass transaction and address objects between bitcoin processes.
//! In the future, more params may be added here to serialize other objects that //! In the future, more params may be added here to serialize other objects that
//! require serialization parameters. Params should just be chosen to serialize //! require serialization parameters. Params should just be chosen to serialize
//! objects completely and ensure that serializing and deserializing objects //! objects completely and ensure that serializing and deserializing objects
@ -29,7 +64,28 @@ namespace capnp {
template <typename S> template <typename S>
auto Wrap(S& s) auto Wrap(S& s)
{ {
return ParamsStream{s, TX_WITH_WITNESS}; return ParamsStream{s, TX_WITH_WITNESS, CAddress::V2_NETWORK};
}
//! Serialize bitcoin value.
template <typename T>
DataStream Serialize(const T& value)
{
DataStream stream;
auto wrapper{Wrap(stream)};
value.Serialize(wrapper);
return stream;
}
//! Deserialize bitcoin value.
template <typename T>
T Unserialize(const kj::ArrayPtr<const kj::byte>& data)
{
SpanReader stream{{data.begin(), data.end()}};
T value;
auto wrapper{Wrap(stream)};
value.Unserialize(wrapper);
return value;
} }
//! Detect if type has a deserialize_type constructor, which is //! Detect if type has a deserialize_type constructor, which is
@ -114,6 +170,41 @@ decltype(auto) CustomReadField(TypeList<std::chrono::duration<Rep, Period>>, Pri
return read_dest.construct(input.get()); return read_dest.construct(input.get());
} }
template <typename Value, typename Output>
void CustomBuildField(TypeList<fs::path>, 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 <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<fs::path>, 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 <typename Value, typename Output>
void CustomBuildField(TypeList<SecureString>, 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 <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<SecureString>, 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());
}
//! Overload CustomBuildField and CustomReadField to serialize UniValue //! Overload CustomBuildField and CustomReadField to serialize UniValue
//! parameters and return values as JSON strings. //! parameters and return values as JSON strings.
template <typename Value, typename Output> template <typename Value, typename Output>
@ -134,6 +225,82 @@ decltype(auto) CustomReadField(TypeList<UniValue>, Priority<1>, InvokeContext& i
}); });
} }
//! Overload CustomBuildField and CustomReadField to serialize
//! UniValue::type_error exceptions as text strings.
template <typename Value, typename Output>
void CustomBuildField(TypeList<UniValue::type_error>, Priority<1>, InvokeContext& invoke_context,
Value&& value, Output&& output)
{
BuildField(TypeList<std::string>(), invoke_context, output, std::string(value.what()));
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<UniValue::type_error>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
read_dest.construct(ReadField(TypeList<std::string>(), invoke_context, input, mp::ReadDestTemp<std::string>()));
}
template <typename Output>
void CustomBuildField(
TypeList<>, Priority<1>, InvokeContext& invoke_context, Output&& output,
typename std::enable_if<std::is_same<decltype(output.get()),
ipc::capnp::messages::GlobalArgs::Builder>::value>::type* enable = nullptr)
{
ipc::capnp::BuildGlobalArgs(invoke_context, output.init());
}
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
auto CustomPassField(TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) ->
typename std::enable_if<std::is_same<decltype(Accessor::get(server_context.call_context.getParams())),
ipc::capnp::messages::GlobalArgs::Reader>::value>::type
{
ipc::capnp::ReadGlobalArgs(server_context, Accessor::get(server_context.call_context.getParams()));
return fn.invoke(server_context, std::forward<Args>(args)...);
}
//! Overload CustomBuildField and CustomReadField to serialize util::Result
//! return values as common.capnp Result and ResultVoid structs
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<util::Result<LocalType>>, Priority<1>, InvokeContext& invoke_context, Value&& value,
Output&& output)
{
auto result = output.init();
if (value) {
if constexpr (!std::is_same_v<LocalType, void>) {
using ValueAccessor = typename ProxyStruct<typename decltype(result)::Builds>::ValueAccessor;
BuildField(TypeList<LocalType>(), invoke_context, Make<StructField, ValueAccessor>(result), *value);
}
} else {
BuildField(TypeList<bilingual_str>(), invoke_context, Make<ValueField>(result.initError()),
util::ErrorString(value));
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<util::Result<LocalType>>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
auto result = input.get();
if (result.hasError()) {
bilingual_str error;
ReadField(mp::TypeList<bilingual_str>(), invoke_context, mp::Make<mp::ValueField>(result.getError()),
mp::ReadDestValue(error));
read_dest.construct(util::Error{std::move(error)});
} else {
if constexpr (!std::is_same_v<LocalType, void>) {
assert (result.hasValue());
ReadField(mp::TypeList<LocalType>(), invoke_context, mp::Make<mp::ValueField>(result.getValue()),
mp::ReadDestEmplace(
mp::TypeList<LocalType>(), [&](auto&&... args) -> auto& {
return *read_dest.construct(LocalType{std::forward<decltype(args)>(args)...});
}));
} else {
read_dest.construct();
}
}
}
//! Generic ::capnp::Data field builder for any C++ type that can be converted //! Generic ::capnp::Data field builder for any C++ type that can be converted
//! to a span of bytes, like std::vector<char> or std::array<uint8_t>, or custom //! to a span of bytes, like std::vector<char> or std::array<uint8_t>, or custom
//! blob types like uint256 or PKHash with data() and size() methods pointing to //! blob types like uint256 or PKHash with data() and size() methods pointing to
@ -160,6 +327,39 @@ requires
auto result = output.init(data.size()); auto result = output.init(data.size());
memcpy(result.begin(), data.data(), 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 <typename LocalType, typename Hash, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::unordered_set<LocalType, Hash>>, 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<LocalType>(), invoke_context, Make<ValueField>(item),
ReadDestEmplace(
TypeList<const LocalType>(), [&](auto&&... args) -> auto& {
return *value.emplace(std::forward<decltype(args)>(args)...).first;
}));
}
});
}
template <typename LocalType, typename Hash, typename Value, typename Output>
void CustomBuildField(TypeList<std::unordered_set<LocalType, Hash>>, 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<LocalType>(), invoke_context, ListOutput<typename decltype(list)::Builds>(list, i), elem);
++i;
}
}
} // namespace mp } // namespace mp
#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H #endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H

View file

@ -8,9 +8,52 @@ using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("ipc::capnp::messages"); $Cxx.namespace("ipc::capnp::messages");
using Proxy = import "/mp/proxy.capnp"; using Proxy = import "/mp/proxy.capnp";
$Proxy.include("ipc/capnp/common.h");
$Proxy.includeTypes("ipc/capnp/common-types.h"); $Proxy.includeTypes("ipc/capnp/common-types.h");
struct BlockRef $Proxy.wrap("interfaces::BlockRef") { struct BlockRef $Proxy.wrap("interfaces::BlockRef") {
hash @0 :Data; hash @0 :Data;
height @1 :Int32; height @1 :Int32;
} }
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<void>
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;
}

51
src/ipc/capnp/common.cpp Normal file
View file

@ -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 <capnp/blob.h>
#include <capnp/common.h>
#include <capnp/list.h>
#include <common/args.h>
#include <common/settings.h>
#include <ipc/capnp/common-types.h>
#include <ipc/capnp/common.capnp.h>
#include <ipc/capnp/common.capnp.proxy-types.h>
#include <ipc/capnp/common.h>
#include <ipc/capnp/context.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/util.h>
#include <sync.h>
#include <univalue.h>
#include <functional>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <vector>
namespace ipc {
namespace capnp {
void BuildGlobalArgs(mp::InvokeContext& invoke_context, messages::GlobalArgs::Builder&& builder)
{
gArgs.LockSettings([&](const common::Settings& settings) {
mp::BuildField(mp::TypeList<common::Settings>(), invoke_context,
mp::Make<mp::ValueField>(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<common::Settings>(), invoke_context, mp::Make<mp::ValueField>(reader.getSettings()),
mp::ReadDestValue(settings));
});
gArgs.SetConfigFilePath(fs::PathFromString(ipc::capnp::ToString(reader.getConfigPath())));
SelectParams(gArgs.GetChainType());
Context& ipc_context = *static_cast<Context*>(invoke_context.connection.m_loop.m_context);
ipc_context.init_process();
}
} // namespace capnp
} // namespace ipc

29
src/ipc/capnp/common.h Normal file
View file

@ -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 <ipc/capnp/common.capnp.h>
#include <common/args.h>
#include <string>
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

View file

@ -5,8 +5,16 @@
#ifndef BITCOIN_IPC_CAPNP_CONTEXT_H #ifndef BITCOIN_IPC_CAPNP_CONTEXT_H
#define BITCOIN_IPC_CAPNP_CONTEXT_H #define BITCOIN_IPC_CAPNP_CONTEXT_H
#include <ipc/capnp/node.capnp.h>
#include <ipc/context.h> #include <ipc/context.h>
namespace interfaces {
class Node;
} // namespace interfaces
namespace mp {
struct InvokeContext;
} // namespace mp
namespace ipc { namespace ipc {
namespace capnp { namespace capnp {
//! Cap'n Proto context struct. Generally the parent ipc::Context struct should //! 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. //! function and object types to capnp hooks.
struct Context : ipc::Context struct Context : ipc::Context
{ {
using MakeNodeClient = std::unique_ptr<interfaces::Node>(mp::InvokeContext& context,
messages::Node::Client&& client);
using MakeNodeServer = kj::Own<messages::Node::Server>(mp::InvokeContext& context,
std::shared_ptr<interfaces::Node> impl);
MakeNodeClient* make_node_client = nullptr;
MakeNodeServer* make_node_server = nullptr;
}; };
} // namespace capnp } // namespace capnp
} // namespace ipc } // namespace ipc

View file

@ -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) -> ();
}

View file

@ -5,7 +5,32 @@
#ifndef BITCOIN_IPC_CAPNP_INIT_TYPES_H #ifndef BITCOIN_IPC_CAPNP_INIT_TYPES_H
#define BITCOIN_IPC_CAPNP_INIT_TYPES_H #define BITCOIN_IPC_CAPNP_INIT_TYPES_H
#include <ipc/capnp/chain.capnp.proxy-types.h>
#include <ipc/capnp/echo.capnp.proxy-types.h> #include <ipc/capnp/echo.capnp.proxy-types.h>
#include <ipc/capnp/init.capnp.proxy.h>
#include <ipc/capnp/mining.capnp.proxy-types.h> #include <ipc/capnp/mining.capnp.proxy-types.h>
#include <ipc/capnp/node.capnp.proxy-types.h>
namespace mp {
//! Specialization of makeWalletLoader needed because it takes a Chain& reference
//! argument, not a unique_ptr<Chain> argument, so a manual cleanup
//! callback is needed to clean up the ProxyServer<messages::Chain> proxy object.
template <>
struct ProxyServerMethodTraits<ipc::capnp::messages::Init::MakeWalletLoaderParams>
{
using Context = ServerContext<ipc::capnp::messages::Init,
ipc::capnp::messages::Init::MakeWalletLoaderParams,
ipc::capnp::messages::Init::MakeWalletLoaderResults>;
static capnp::Void invoke(Context& context);
};
//! Chain& server-side argument handling. Skips argument so it can
//! be handled by ProxyServerCustom code.
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
void CustomPassField(TypeList<interfaces::Chain&>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
fn.invoke(server_context, std::forward<Args>(args)...);
}
} // namespace mp
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H #endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H

View file

@ -8,16 +8,25 @@ using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("ipc::capnp::messages"); $Cxx.namespace("ipc::capnp::messages");
using Proxy = import "/mp/proxy.capnp"; using Proxy = import "/mp/proxy.capnp";
$Proxy.include("interfaces/chain.h");
$Proxy.include("interfaces/echo.h"); $Proxy.include("interfaces/echo.h");
$Proxy.include("interfaces/init.h"); $Proxy.include("interfaces/init.h");
$Proxy.include("interfaces/mining.h"); $Proxy.include("interfaces/mining.h");
$Proxy.include("interfaces/node.h");
$Proxy.includeTypes("ipc/capnp/init-types.h"); $Proxy.includeTypes("ipc/capnp/init-types.h");
using Chain = import "chain.capnp";
using Common = import "common.capnp";
using Echo = import "echo.capnp"; using Echo = import "echo.capnp";
using Mining = import "mining.capnp"; using Mining = import "mining.capnp";
using Node = import "node.capnp";
using Wallet = import "wallet.capnp";
interface Init $Proxy.wrap("interfaces::Init") { interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo); makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining); makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining);
makeChain @3 (context :Proxy.Context) -> (result :Chain.Chain);
makeNode @4 (context :Proxy.Context) -> (result :Node.Node);
makeWalletLoader @5 (context :Proxy.Context, globalArgs :Common.GlobalArgs, chain :Chain.Chain) -> (result :Wallet.WalletLoader);
} }

36
src/ipc/capnp/init.cpp Normal file
View file

@ -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 <capnp/capability.h>
#include <interfaces/chain.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/wallet.h>
#include <ipc/capnp/chain.capnp.h>
#include <ipc/capnp/chain.capnp.proxy.h>
#include <ipc/capnp/context.h>
#include <ipc/capnp/init-types.h>
#include <ipc/capnp/init.capnp.h>
#include <ipc/capnp/init.capnp.proxy.h>
#include <mp/proxy-io.h>
#include <memory>
#include <utility>
namespace mp {
template <typename Interface> struct ProxyClient;
::capnp::Void ProxyServerMethodTraits<ipc::capnp::messages::Init::MakeWalletLoaderParams>::invoke(Context& context)
{
auto params = context.call_context.getParams();
auto chain = std::make_unique<ProxyClient<ipc::capnp::messages::Chain>>(
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<ProxyServer<ipc::capnp::messages::WalletLoader>>(std::shared_ptr<interfaces::WalletLoader>(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

114
src/ipc/capnp/node-types.h Normal file
View file

@ -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 <ipc/capnp/common.capnp.proxy-types.h>
#include <ipc/capnp/context.h>
#include <ipc/capnp/node.capnp.proxy.h>
#include <ipc/capnp/wallet.capnp.proxy-types.h>
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<ipc::capnp::messages::Node::RpcSetTimerInterfaceIfUnsetParams>
{
using Context = ServerContext<ipc::capnp::messages::Node,
ipc::capnp::messages::Node::RpcSetTimerInterfaceIfUnsetParams,
ipc::capnp::messages::Node::RpcSetTimerInterfaceIfUnsetResults>;
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<ipc::capnp::messages::Node::RpcUnsetTimerInterfaceParams>
{
using Context = ServerContext<ipc::capnp::messages::Node,
ipc::capnp::messages::Node::RpcUnsetTimerInterfaceParams,
ipc::capnp::messages::Node::RpcUnsetTimerInterfaceResults>;
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<interfaces::Node> CustomMakeProxyClient<ipc::capnp::messages::Node, interfaces::Node>(
InvokeContext& context, ipc::capnp::messages::Node::Client&& client)
{
ipc::capnp::Context& ipc_context = *static_cast<ipc::capnp::Context*>(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<ipc::capnp::messages::Node::Server> CustomMakeProxyServer<ipc::capnp::messages::Node, interfaces::Node>(
InvokeContext& context, std::shared_ptr<interfaces::Node>&& impl)
{
ipc::capnp::Context& ipc_context = *static_cast<ipc::capnp::Context*>(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 <typename Accessor, typename ServerContext, typename Fn, typename... Args>
void CustomPassField(TypeList<RPCTimerInterface*>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
fn.invoke(server_context, std::forward<Args>(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 <typename Value, typename Output>
void CustomBuildField(TypeList<std::tuple<CNodeStats, bool, CNodeStateStats>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& stats,
Output&& output)
{
BuildField(TypeList<CNodeStats>(), invoke_context, output, std::get<0>(stats));
if (std::get<1>(stats)) {
auto message_builder = output.get();
using Accessor = ProxyStruct<ipc::capnp::messages::NodeStats>::StateStatsAccessor;
StructField<Accessor, ipc::capnp::messages::NodeStats::Builder> field_output{message_builder};
BuildField(TypeList<CNodeStateStats>(), invoke_context, field_output, std::get<2>(stats));
}
}
void CustomReadMessage(InvokeContext& invoke_context,
ipc::capnp::messages::NodeStats::Reader const& reader,
std::tuple<CNodeStats, bool, CNodeStateStats>& node_stats);
template <typename Value, typename Output>
void CustomBuildField(TypeList<CSubNet>, 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

213
src/ipc/capnp/node.capnp Normal file
View file

@ -0,0 +1,213 @@
# 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 :UInt64);
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, 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);
getMempoolMaxUsage @30 (context :Proxy.Context) -> (result :UInt64);
getHeaderTip @31 (context :Proxy.Context) -> (height :Int32, blockTime :Int64, result :Bool);
getNumBlocks @32 (context :Proxy.Context) -> (result :Int32);
getNetLocalAddresses @33 (context :Proxy.Context) -> (result :List(Common.Pair(Data, LocalServiceInfo)));
getBestBlockHash @34 (context :Proxy.Context) -> (result :Data);
getLastBlockTime @35 (context :Proxy.Context) -> (result :Int64);
getVerificationProgress @36 (context :Proxy.Context) -> (result :Float64);
isInitialBlockDownload @37 (context :Proxy.Context) -> (result :Bool);
isLoadingBlocks @38 (context :Proxy.Context) -> (result :Bool);
setNetworkActive @39 (context :Proxy.Context, active :Bool) -> ();
getNetworkActive @40 (context :Proxy.Context) -> (result :Bool);
getDustRelayFee @41 (context :Proxy.Context) -> (result :Data);
executeRpc @42 (context :Proxy.Context, command :Text, params :Text, uri :Text) -> (error :Text $Proxy.exception("std::exception"), rpcError :Text $Proxy.exception("UniValue"), result :Text);
listRpcCommands @43 (context :Proxy.Context) -> (result :List(Text));
rpcSetTimerInterfaceIfUnset @44 (context :Proxy.Context, iface :Void) -> ();
rpcUnsetTimerInterface @45 (context :Proxy.Context, iface :Void) -> ();
getUnspentOutput @46 (context :Proxy.Context, output :Data) -> (result :Data);
broadcastTransaction @47 (context :Proxy.Context, tx: Data, maxTxFee :Int64) -> (error: Text, result :Int32);
customWalletLoader @48 (context :Proxy.Context) -> (result :Wallet.WalletLoader) $Proxy.name("walletLoader");
handleInitMessage @49 (context :Proxy.Context, callback :InitMessageCallback) -> (result :Handler.Handler);
handleMessageBox @50 (context :Proxy.Context, callback :MessageBoxCallback) -> (result :Handler.Handler);
handleQuestion @51 (context :Proxy.Context, callback :QuestionCallback) -> (result :Handler.Handler);
handleShowProgress @52 (context :Proxy.Context, callback :ShowNodeProgressCallback) -> (result :Handler.Handler);
handleInitWallet @53 (context :Proxy.Context, callback :InitWalletCallback) -> (result :Handler.Handler);
handleNotifyNumConnectionsChanged @54 (context :Proxy.Context, callback :NotifyNumConnectionsChangedCallback) -> (result :Handler.Handler);
handleNotifyNetworkActiveChanged @55 (context :Proxy.Context, callback :NotifyNetworkActiveChangedCallback) -> (result :Handler.Handler);
handleNotifyAlertChanged @56 (context :Proxy.Context, callback :NotifyAlertChangedCallback) -> (result :Handler.Handler);
handleBannedListChanged @57 (context :Proxy.Context, callback :BannedListChangedCallback) -> (result :Handler.Handler);
handleNotifyBlockTip @58 (context :Proxy.Context, callback :NotifyBlockTipCallback) -> (result :Handler.Handler);
handleNotifyHeaderTip @59 (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<interfaces::Node::InitMessageFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, message :Text) -> ();
}
interface MessageBoxCallback $Proxy.wrap("ProxyCallback<interfaces::Node::MessageBoxFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, message :Common.BilingualStr, caption :Text, style :UInt32) -> (result :Bool);
}
interface QuestionCallback $Proxy.wrap("ProxyCallback<interfaces::Node::QuestionFn>") {
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<interfaces::Node::ShowProgressFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, title :Text, progress :Int32, resumePossible :Bool) -> ();
}
interface InitWalletCallback $Proxy.wrap("ProxyCallback<interfaces::Node::InitWalletFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context) -> ();
}
interface NotifyNumConnectionsChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Node::NotifyNumConnectionsChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, newNumConnections :Int32) -> ();
}
interface NotifyNetworkActiveChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Node::NotifyNetworkActiveChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, networkActive :Bool) -> ();
}
interface NotifyAlertChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Node::NotifyAlertChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context) -> ();
}
interface BannedListChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Node::BannedListChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context) -> ();
}
interface NotifyBlockTipCallback $Proxy.wrap("ProxyCallback<interfaces::Node::NotifyBlockTipFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, syncState: Int32, tip: BlockTip, verificationProgress :Float64) -> ();
}
interface NotifyHeaderTipCallback $Proxy.wrap("ProxyCallback<interfaces::Node::NotifyHeaderTipFn>") {
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("m_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");
addrName @6 :Text $Proxy.name("m_addr_name");
version @7 :Int32 $Proxy.name("nVersion");
cleanSubVer @8 :Text $Proxy.name("cleanSubVer");
inbound @9 :Bool $Proxy.name("fInbound");
bip152HighbandwidthTo @10 :Bool $Proxy.name("m_bip152_highbandwidth_to");
bip152HighbandwidthFrom @11 :Bool $Proxy.name("m_bip152_highbandwidth_from");
startingHeight @12 :Int32 $Proxy.name("m_starting_height");
sendBytes @13 :UInt64 $Proxy.name("nSendBytes");
sendBytesPerMsgType @14 :List(Common.PairUInt64(Text)) $Proxy.name("mapSendBytesPerMsgType");
recvBytes @15 :UInt64 $Proxy.name("nRecvBytes");
recvBytesPerMsgType @16 :List(Common.PairUInt64(Text)) $Proxy.name("mapRecvBytesPerMsgType");
permissionFlags @17 :Int32 $Proxy.name("m_permission_flags");
pingTime @18 :Int64 $Proxy.name("m_last_ping_time");
minPingTime @19 :Int64 $Proxy.name("m_min_ping_time");
addrLocal @20 :Text $Proxy.name("addrLocal");
addr @21 :Data $Proxy.name("addr");
addrBind @22 :Data $Proxy.name("addrBind");
network @23 :Int32 $Proxy.name("m_network");
mappedAs @24 :UInt32 $Proxy.name("m_mapped_as");
connType @25 :Int32 $Proxy.name("m_conn_type");
transportType @26 :UInt8 $Proxy.name("m_transport_type;");
sessionId @27 :Text $Proxy.name("m_session_id");
stateStats @28 :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");
timeOffset @10 :Int64 $Proxy.name("time_offset");
}
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");
}
struct LocalServiceInfo $Proxy.wrap("LocalServiceInfo") {
score @0 :Int32 $Proxy.name("nScore");
port @1 :UInt16 $Proxy.name("nPort");
}

142
src/ipc/capnp/node.cpp Normal file
View file

@ -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 <capnp/list.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
#include <ipc/capnp/context.h>
#include <ipc/capnp/node-types.h>
#include <ipc/capnp/node.capnp.h>
#include <ipc/capnp/node.capnp.proxy-types.h>
#include <ipc/capnp/node.capnp.proxy.h>
#include <ipc/capnp/node.h>
#include <kj/async-io.h>
#include <kj/async-prelude.h>
#include <kj/async.h>
#include <kj/memory.h>
#include <kj/time.h>
#include <kj/timer.h>
#include <kj/units.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/util.h>
#include <rpc/server.h>
#include <sys/types.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <tuple>
#include <utility>
class CNodeStats;
struct CNodeStateStats;
namespace ipc {
namespace capnp {
void SetupNodeClient(ipc::Context& context)
{
static_cast<Context&>(context).make_node_client = mp::MakeProxyClient<messages::Node, interfaces::Node>;
}
void SetupNodeServer(ipc::Context& context)
{
static_cast<Context&>(context).make_node_server = mp::MakeProxyServer<messages::Node, interfaces::Node>;
}
class RpcTimer : public ::RPCTimerBase
{
public:
RpcTimer(mp::EventLoop& loop, std::function<void(void)>& 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<void(void)> m_fn;
kj::Promise<void> 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<void(void)>& 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<ipc::capnp::messages::Node::RpcSetTimerInterfaceIfUnsetParams>::invoke(Context& context)
{
if (!context.proxy_server.m_timer_interface) {
auto timer = std::make_unique<ipc::capnp::RpcTimerInterface>(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<ipc::capnp::messages::Node::RpcUnsetTimerInterfaceParams>::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<CNodeStats, bool, CNodeStateStats>& node_stats)
{
CNodeStats& node = std::get<0>(node_stats);
ReadField(TypeList<CNodeStats>(), invoke_context, Make<ValueField>(reader), ReadDestValue(node));
if ((std::get<1>(node_stats) = reader.hasStateStats())) {
CNodeStateStats& state = std::get<2>(node_stats);
ReadField(TypeList<CNodeStateStats>(), invoke_context, Make<ValueField>(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<ipc::capnp::messages::Node, interfaces::Node>::walletLoader()
{
if (!m_wallet_loader) {
m_wallet_loader = self().customWalletLoader();
}
return *m_wallet_loader;
}
} // namespace mp

54
src/ipc/capnp/node.h Normal file
View file

@ -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 <interfaces/node.h>
#include <interfaces/wallet.h>
#include <ipc/capnp/node.capnp.h>
#include <mp/proxy.h>
#include <rpc/server.h>
#include <scheduler.h>
#include <memory>
#include <string>
class RPCTimerInterface;
//! Specialization of Node proxy server needed to add m_timer_interface
//! member used by rpcSetTimerInterfaceIfUnset and rpcUnsetTimerInterface
//! methods.
template <>
struct mp::ProxyServerCustom<ipc::capnp::messages::Node, interfaces::Node>
: public mp::ProxyServerBase<ipc::capnp::messages::Node, interfaces::Node>
{
public:
using ProxyServerBase::ProxyServerBase;
std::unique_ptr<RPCTimerInterface> m_timer_interface;
};
//! Specialization of Node client to manage memory of WalletLoader& reference
//! returned by walletLoader().
template <>
class mp::ProxyClientCustom<ipc::capnp::messages::Node, interfaces::Node>
: public mp::ProxyClientBase<ipc::capnp::messages::Node, interfaces::Node>
{
public:
using ProxyClientBase::ProxyClientBase;
interfaces::WalletLoader& walletLoader() override;
private:
std::unique_ptr<interfaces::WalletLoader> m_wallet_loader;
};
//! Specialization of Node::walletLoader client code to manage memory of
//! WalletLoader& reference returned by walletLoader().
template <>
struct mp::ProxyClientMethodTraits<ipc::capnp::messages::Node::CustomWalletLoaderParams>
: public FunctionTraits<std::unique_ptr<interfaces::WalletLoader> (interfaces::Node::*const)()>
{
};
#endif // BITCOIN_IPC_CAPNP_NODE_H

View file

@ -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 <ipc/capnp/chain.capnp.proxy-types.h>
#include <ipc/capnp/common.capnp.proxy-types.h>
#include <ipc/capnp/wallet.capnp.proxy.h>
#include <scheduler.h>
#include <wallet/wallet.h>
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 <typename Reader, typename ReadDest>
decltype(auto) CustomReadField(
TypeList<PKHash>, Priority<1>, InvokeContext& invoke_context, Reader&& reader, ReadDest&& read_dest)
{
return read_dest.construct(ipc::capnp::ToBlob<uint160>(reader.get()));
}
} // namespace mp
#endif // BITCOIN_IPC_CAPNP_WALLET_TYPES_H

260
src/ipc/capnp/wallet.capnp Normal file
View file

@ -0,0 +1,260 @@
# 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 :Common.ResultVoid);
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<interfaces::Wallet::UnloadFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context) -> ();
}
interface ShowWalletProgressCallback $Proxy.wrap("ProxyCallback<interfaces::Wallet::ShowProgressFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, title :Text, progress :Int32) -> ();
}
interface StatusChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Wallet::StatusChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context) -> ();
}
interface AddressBookChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Wallet::AddressBookChangedFn>") {
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<interfaces::Wallet::TransactionChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, txid :Data, status :Int32) -> ();
}
interface WatchOnlyChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Wallet::WatchOnlyChangedFn>") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, haveWatchOnly :Bool) -> ();
}
interface CanGetAddressesChangedCallback $Proxy.wrap("ProxyCallback<interfaces::Wallet::CanGetAddressesChangedFn>") {
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));
isEncrypted @5 (context :Proxy.Context, name :Text) -> (result : Bool);
listWalletDir @6 (context :Proxy.Context) -> (result :List(Common.Pair(Text, Text)));
getWallets @7 (context :Proxy.Context) -> (result :List(Wallet));
handleLoadWallet @8 (context :Proxy.Context, callback :LoadWalletCallback) -> (result :Handler.Handler);
}
interface LoadWalletCallback $Proxy.wrap("ProxyCallback<interfaces::WalletLoader::LoadWalletFn>") {
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;
hasMaxTxWeight @22 :UInt32;
maxTxWeight @23 :UInt32;
setSelected @24 :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");
}

246
src/ipc/capnp/wallet.cpp Normal file
View file

@ -0,0 +1,246 @@
// 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 <capnp/blob.h>
#include <capnp/list.h>
#include <interfaces/wallet.h>
#include <ipc/capnp/chain-types.h>
#include <ipc/capnp/chain.capnp.h>
#include <ipc/capnp/common-types.h>
#include <ipc/capnp/wallet-types.h>
#include <ipc/capnp/wallet.capnp.h>
#include <ipc/capnp/wallet.capnp.proxy-types.h>
#include <ipc/capnp/wallet.capnp.proxy.h>
#include <ipc/capnp/wallet.h>
#include <key.h>
#include <key_io.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/proxy.h>
#include <mp/util.h>
#include <outputtype.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <scheduler.h>
#include <script/solver.h>
#include <streams.h>
#include <uint256.h>
#include <util/threadnames.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <cstdint>
#include <future>
#include <memory>
#include <optional>
#include <string.h>
#include <system_error>
#include <variant>
#include <vector>
namespace ipc {
namespace capnp {
namespace messages {
struct WalletLoader;
//! Wrapper around CScheduler that creates a worker thread in its constructor
//! and shuts it down in its destructor.
class SchedulerThread
{
public:
SchedulerThread()
{
m_result = std::async([this]() {
util::ThreadRename("schedqueue");
m_scheduler.serviceQueue();
});
}
~SchedulerThread()
{
m_scheduler.stop();
m_result.get();
}
CScheduler& scheduler() { return m_scheduler; }
private:
CScheduler m_scheduler;
std::future<void> m_result;
};
} // namespace messages
} // namespace capnp
} // namespace ipc
namespace mp {
void ProxyServerMethodTraits<ipc::capnp::messages::ChainClient::StartParams>::invoke(WalletLoaderContext& context)
{
if (!context.proxy_server.m_scheduler) {
auto scheduler = std::make_unique<ipc::capnp::messages::SchedulerThread>();
context.proxy_server.m_scheduler = scheduler.get();
context.proxy_server.m_context.cleanup.emplace_back(MakeAsyncCallable([s=std::move(scheduler)]() mutable { s.reset(); }));
}
context.proxy_server.m_impl->start(context.proxy_server.m_scheduler->scheduler());
}
void CustomBuildMessage(InvokeContext& invoke_context,
const CTxDestination& dest,
ipc::capnp::messages::TxDestination::Builder&& builder)
{
if (std::get_if<CNoDestination>(&dest)) {
} else if (const PubKeyDestination* data = std::get_if<PubKeyDestination>(&dest)) {
builder.setPubKey(ipc::capnp::ToArray(data->GetPubKey()));
} else if (const PKHash* data = std::get_if<PKHash>(&dest)) {
builder.setPkHash(ipc::capnp::ToArray(*data));
} else if (const ScriptHash* data = std::get_if<ScriptHash>(&dest)) {
builder.setScriptHash(ipc::capnp::ToArray(*data));
} else if (const WitnessV0ScriptHash* data = std::get_if<WitnessV0ScriptHash>(&dest)) {
builder.setWitnessV0ScriptHash(ipc::capnp::ToArray(*data));
} else if (const WitnessV0KeyHash* data = std::get_if<WitnessV0KeyHash>(&dest)) {
builder.setWitnessV0KeyHash(ipc::capnp::ToArray(*data));
} else if (const WitnessV1Taproot* data = std::get_if<WitnessV1Taproot>(&dest)) {
builder.setWitnessV1Taproot(ipc::capnp::ToArray(*data));
} else if (const WitnessUnknown* data = std::get_if<WitnessUnknown>(&dest)) {
BuildField(TypeList<WitnessUnknown>(), invoke_context, Make<ValueField>(builder.initWitnessUnknown()), *data);
} else {
throw std::logic_error(strprintf("Unrecognized address type. Serialization not implemented for %s", EncodeDestination(dest)));
}
}
void CustomReadMessage(InvokeContext& invoke_context,
const ipc::capnp::messages::TxDestination::Reader& reader,
CTxDestination& dest)
{
if (reader.hasPubKey()) {
auto data = reader.getPubKey();
dest = PubKeyDestination(CPubKey{data.begin(), data.end()});
} else if (reader.hasPkHash()) {
dest = PKHash(ipc::capnp::ToBlob<uint160>(reader.getPkHash()));
} else if (reader.hasScriptHash()) {
dest = ScriptHash(ipc::capnp::ToBlob<uint160>(reader.getScriptHash()));
} else if (reader.hasWitnessV0ScriptHash()) {
dest = WitnessV0ScriptHash(ipc::capnp::ToBlob<uint256>(reader.getWitnessV0ScriptHash()));
} else if (reader.hasWitnessV0KeyHash()) {
dest = WitnessV0KeyHash(ipc::capnp::ToBlob<uint160>(reader.getWitnessV0KeyHash()));
} else if (reader.hasWitnessV1Taproot()) {
const auto& data = reader.getWitnessV1Taproot();
dest = WitnessV1Taproot{XOnlyPubKey{Span{data.begin(), data.size()}}};
} else if (reader.hasWitnessUnknown()) {
ReadField(TypeList<WitnessUnknown>(), invoke_context, Make<ValueField>(reader.getWitnessUnknown()),
ReadDestValue(std::get<WitnessUnknown>(dest)));
}
}
void CustomBuildMessage(InvokeContext& invoke_context,
const WitnessUnknown& dest,
ipc::capnp::messages::WitnessUnknown::Builder&& builder)
{
builder.setVersion(dest.GetWitnessVersion());
builder.setProgram(ipc::capnp::ToArray(dest.GetWitnessProgram()));
}
void CustomReadMessage(InvokeContext& invoke_context,
const ipc::capnp::messages::WitnessUnknown::Reader& reader,
WitnessUnknown& dest)
{
auto data = reader.getProgram();
dest = WitnessUnknown{reader.getVersion(), {data.begin(), data.end()}};
}
void CustomBuildMessage(InvokeContext& invoke_context, const CKey& key, ipc::capnp::messages::Key::Builder&& builder)
{
builder.setSecret(ipc::capnp::ToArray(key));
builder.setIsCompressed(key.IsCompressed());
}
void CustomReadMessage(InvokeContext& invoke_context, const ipc::capnp::messages::Key::Reader& reader, CKey& key)
{
auto secret = reader.getSecret();
key.Set(secret.begin(), secret.end(), reader.getIsCompressed());
}
void CustomBuildMessage(InvokeContext& invoke_context,
const wallet::CCoinControl& coin_control,
ipc::capnp::messages::CoinControl::Builder&& builder)
{
CustomBuildMessage(invoke_context, coin_control.destChange, builder.initDestChange());
if (coin_control.m_change_type) {
builder.setHasChangeType(true);
builder.setChangeType(static_cast<int>(*coin_control.m_change_type));
}
builder.setIncludeUnsafeInputs(coin_control.m_include_unsafe_inputs);
builder.setAllowOtherInputs(coin_control.m_allow_other_inputs);
builder.setAllowWatchOnly(coin_control.fAllowWatchOnly);
builder.setOverrideFeeRate(coin_control.fOverrideFeeRate);
if (coin_control.m_feerate) {
builder.setFeeRate(ipc::capnp::ToArray(ipc::capnp::Serialize(*coin_control.m_feerate)));
}
if (coin_control.m_confirm_target) {
builder.setHasConfirmTarget(true);
builder.setConfirmTarget(*coin_control.m_confirm_target);
}
if (coin_control.m_signal_bip125_rbf) {
builder.setHasSignalRbf(true);
builder.setSignalRbf(*coin_control.m_signal_bip125_rbf);
}
builder.setAvoidPartialSpends(coin_control.m_avoid_partial_spends);
builder.setAvoidAddressReuse(coin_control.m_avoid_address_reuse);
builder.setFeeMode(int32_t(coin_control.m_fee_mode));
builder.setMinDepth(coin_control.m_min_depth);
builder.setMaxDepth(coin_control.m_min_depth);
if (coin_control.m_locktime) {
builder.setLockTime(*coin_control.m_locktime);
}
if (coin_control.m_version) {
builder.setVersion(*coin_control.m_version);
}
if (coin_control.m_max_tx_weight) {
builder.setMaxTxWeight(*coin_control.m_max_tx_weight);
}
std::vector<COutPoint> selected = coin_control.ListSelected();
auto builder_selected = builder.initSetSelected(selected.size());
size_t i = 0;
for (const COutPoint& output : selected) {
builder_selected.set(i, ipc::capnp::ToArray(ipc::capnp::Serialize(output)));
++i;
}
}
void CustomReadMessage(InvokeContext& invoke_context,
const ipc::capnp::messages::CoinControl::Reader& reader,
wallet::CCoinControl& coin_control)
{
CustomReadMessage(invoke_context, reader.getDestChange(), coin_control.destChange);
if (reader.getHasChangeType()) {
coin_control.m_change_type = OutputType(reader.getChangeType());
}
coin_control.m_include_unsafe_inputs = reader.getIncludeUnsafeInputs();
coin_control.m_allow_other_inputs = reader.getAllowOtherInputs();
coin_control.fAllowWatchOnly = reader.getAllowWatchOnly();
coin_control.fOverrideFeeRate = reader.getOverrideFeeRate();
if (reader.hasFeeRate()) {
coin_control.m_feerate = ipc::capnp::Unserialize<CFeeRate>(reader.getFeeRate());
}
if (reader.getHasConfirmTarget()) {
coin_control.m_confirm_target = reader.getConfirmTarget();
}
if (reader.getHasSignalRbf()) {
coin_control.m_signal_bip125_rbf = reader.getSignalRbf();
}
coin_control.m_avoid_partial_spends = reader.getAvoidPartialSpends();
coin_control.m_avoid_address_reuse = reader.getAvoidAddressReuse();
coin_control.m_fee_mode = FeeEstimateMode(reader.getFeeMode());
coin_control.m_min_depth = reader.getMinDepth();
coin_control.m_max_depth = reader.getMinDepth();
if (reader.getHasLockTime()) {
coin_control.m_locktime = reader.getLockTime();
}
if (reader.getHasVersion()) {
coin_control.m_version = reader.getVersion();
}
if (reader.getHasMaxTxWeight()) {
coin_control.m_max_tx_weight = reader.getMaxTxWeight();
}
for (const auto output : reader.getSetSelected()) {
coin_control.Select(ipc::capnp::Unserialize<COutPoint>(output));
}
}
} // namespace mp

36
src/ipc/capnp/wallet.h Normal file
View file

@ -0,0 +1,36 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_IPC_CAPNP_WALLET_H
#define BITCOIN_IPC_CAPNP_WALLET_H
#include <interfaces/wallet.h>
#include <ipc/capnp/chain.capnp.proxy.h>
#include <ipc/capnp/wallet.capnp.h>
#include <mp/proxy.h>
class CScheduler;
namespace interfaces {
class WalletLoader;
} // namespace interfaces
namespace ipc {
namespace capnp {
namespace messages {
class SchedulerThread;
struct WalletLoader;
} // namespace messages
} // namespace capnp
} // namespace ipc
//! Specialization of WalletLoader proxy server needed hold a CSCheduler instance.
template <>
struct mp::ProxyServerCustom<ipc::capnp::messages::WalletLoader, interfaces::WalletLoader>
: public mp::ProxyServerBase<ipc::capnp::messages::WalletLoader, interfaces::WalletLoader>
{
public:
using ProxyServerBase::ProxyServerBase;
ipc::capnp::messages::SchedulerThread* m_scheduler{nullptr};
};
#endif // BITCOIN_IPC_CAPNP_WALLET_H

View file

@ -5,6 +5,8 @@
#ifndef BITCOIN_IPC_CONTEXT_H #ifndef BITCOIN_IPC_CONTEXT_H
#define BITCOIN_IPC_CONTEXT_H #define BITCOIN_IPC_CONTEXT_H
#include <functional>
namespace ipc { namespace ipc {
//! Context struct used to give IPC protocol implementations or implementation //! Context struct used to give IPC protocol implementations or implementation
//! hooks access to application state, in case they need to run extra code that //! hooks access to application state, in case they need to run extra code that
@ -13,6 +15,9 @@ namespace ipc {
//! with shared objects that are created or destroyed remotely. //! with shared objects that are created or destroyed remotely.
struct Context struct Context
{ {
//! Callback to initialize spawned process after receiving ArgsManager
//! configuration from parent.
std::function<void()> init_process;
}; };
} // namespace ipc } // namespace ipc

View file

@ -29,8 +29,8 @@ namespace {
class IpcImpl : public interfaces::Ipc class IpcImpl : public interfaces::Ipc
{ {
public: public:
IpcImpl(const char* exe_name, const char* process_argv0, interfaces::Init& init) IpcImpl(const char* exe_name, const char* log_suffix, const char* process_argv0, interfaces::Init& init)
: m_exe_name(exe_name), m_process_argv0(process_argv0), m_init(init), : m_exe_name(exe_name), m_log_suffix(log_suffix), m_process_argv0(process_argv0), m_init(init),
m_protocol(ipc::capnp::MakeCapnpProtocol()), m_process(ipc::MakeProcess()) m_protocol(ipc::capnp::MakeCapnpProtocol()), m_process(ipc::MakeProcess())
{ {
} }
@ -91,7 +91,9 @@ public:
m_protocol->addCleanup(type, iface, std::move(cleanup)); m_protocol->addCleanup(type, iface, std::move(cleanup));
} }
Context& context() override { return m_protocol->context(); } Context& context() override { return m_protocol->context(); }
const char* logSuffix() override { return m_log_suffix; }
const char* m_exe_name; const char* m_exe_name;
const char* m_log_suffix;
const char* m_process_argv0; const char* m_process_argv0;
interfaces::Init& m_init; interfaces::Init& m_init;
std::unique_ptr<Protocol> m_protocol; std::unique_ptr<Protocol> m_protocol;
@ -101,8 +103,8 @@ public:
} // namespace ipc } // namespace ipc
namespace interfaces { namespace interfaces {
std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* process_argv0, Init& init) std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* log_suffix, const char* process_argv0, Init& init)
{ {
return std::make_unique<ipc::IpcImpl>(exe_name, process_argv0, init); return std::make_unique<ipc::IpcImpl>(exe_name, log_suffix, process_argv0, init);
} }
} // namespace interfaces } // namespace interfaces

View file

@ -220,6 +220,9 @@ public:
TransportProtocolType m_transport_type; TransportProtocolType m_transport_type;
/** BIP324 session id string in hex, if any. */ /** BIP324 session id string in hex, if any. */
std::string m_session_id; std::string m_session_id;
// Note: If you add fields to this class, you should also consider updating
// the CNode::CopyStats() method, the getpeerinfo RPC in rpc/net.cpp, and
// the NodeStats struct in ipc/capnp/node.capnp.
}; };

View file

@ -50,6 +50,9 @@ struct CNodeStateStats {
ServiceFlags their_services; ServiceFlags their_services;
int64_t presync_height{-1}; int64_t presync_height{-1};
std::chrono::seconds time_offset{0}; std::chrono::seconds time_offset{0};
// Note: If you add fields to this struct, you should also consider updating
// the getpeerinfo RPC in rpc/net.cpp and the NodeStateStat struct in
// ipc/capnp/node.capnp.
}; };
struct PeerManagerInfo { struct PeerManagerInfo {

View file

@ -66,6 +66,8 @@ public:
std::string m_unix_socket_path; std::string m_unix_socket_path;
bool m_is_unix_socket; bool m_is_unix_socket;
bool m_randomize_credentials; bool m_randomize_credentials;
// Note: If you add fields to this class, you should also update the
// ProxyInfo struct in ipc/capnp/node.capnp.
bool IsValid() const bool IsValid() const
{ {

View file

@ -16,6 +16,8 @@
#include <init.h> #include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/handler.h> #include <interfaces/handler.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/mining.h> #include <interfaces/mining.h>
#include <interfaces/node.h> #include <interfaces/node.h>
#include <interfaces/types.h> #include <interfaces/types.h>
@ -101,7 +103,11 @@ class NodeImpl : public Node
{ {
public: public:
explicit NodeImpl(NodeContext& context) { setContext(&context); } explicit NodeImpl(NodeContext& context) { setContext(&context); }
void initLogging() override { InitLogging(args()); } void initLogging() override
{
interfaces::Ipc* ipc = m_context->init->ipc();
InitLogging(args(), ipc ? ipc->logSuffix() : nullptr);
}
void initParameterInteraction() override { InitParameterInteraction(args()); } void initParameterInteraction() override { InitParameterInteraction(args()); }
bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); } bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); }
int getExitStatus() override { return Assert(m_context)->exit_status.load(); } int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
@ -820,7 +826,7 @@ public:
return result; return result;
} }
bool updateRwSetting(const std::string& name, bool updateRwSetting(const std::string& name,
const interfaces::SettingsUpdate& update_settings_func) override interfaces::SettingsUpdate update_settings_func) override
{ {
std::optional<interfaces::SettingsAction> action; std::optional<interfaces::SettingsAction> action;
args().LockSettings([&](common::Settings& settings) { args().LockSettings([&](common::Settings& settings) {

View file

@ -11,8 +11,10 @@
#include <common/init.h> #include <common/init.h>
#include <common/system.h> #include <common/system.h>
#include <init.h> #include <init.h>
#include <init/common.h>
#include <interfaces/handler.h> #include <interfaces/handler.h>
#include <interfaces/init.h> #include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/node.h> #include <interfaces/node.h>
#include <logging.h> #include <logging.h>
#include <node/context.h> #include <node/context.h>
@ -290,6 +292,18 @@ void BitcoinApplication::createNode(interfaces::Init& init)
{ {
assert(!m_node); assert(!m_node);
m_node = init.makeNode(); m_node = init.makeNode();
if (!m_node) {
// If node is not part of current process, need to initialize logging.
if (!init::StartLogging(gArgs)) {
throw std::runtime_error("StartLogging failed");
}
// If node is not part of current process, spawn new bitcoin-node
// process.
auto node_init = init.ipc()->spawnProcess("bitcoin-node");
m_node = node_init->makeNode();
init.ipc()->addCleanup(*m_node, [node_init = node_init.release()] { delete node_init; });
}
if (m_splash) m_splash->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node);
} }
@ -313,13 +327,14 @@ void BitcoinApplication::startThread()
connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown); connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown);
} }
void BitcoinApplication::parameterSetup() void BitcoinApplication::parameterSetup(interfaces::Init& init)
{ {
// Default printtoconsole to false for the GUI. GUI programs should not // Default printtoconsole to false for the GUI. GUI programs should not
// print to the console unnecessarily. // print to the console unnecessarily.
gArgs.SoftSetBoolArg("-printtoconsole", false); gArgs.SoftSetBoolArg("-printtoconsole", false);
InitLogging(gArgs); interfaces::Ipc* ipc = init.ipc();
InitLogging(gArgs, ipc ? ipc->logSuffix() : nullptr);
InitParameterInteraction(gArgs); InitParameterInteraction(gArgs);
} }
@ -659,7 +674,7 @@ int GuiMain(int argc, char* argv[])
// Install qDebug() message handler to route to debug.log // Install qDebug() message handler to route to debug.log
qInstallMessageHandler(DebugMessageHandler); qInstallMessageHandler(DebugMessageHandler);
// Allow parameter interaction before we create the options model // Allow parameter interaction before we create the options model
app.parameterSetup(); app.parameterSetup(*init);
GUIUtil::LogQtInfo(); GUIUtil::LogQtInfo();
if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))

View file

@ -43,7 +43,7 @@ public:
void createPaymentServer(); void createPaymentServer();
#endif #endif
/// parameter interaction/setup based on rules /// parameter interaction/setup based on rules
void parameterSetup(); void parameterSetup(interfaces::Init& init);
/// Create options model /// Create options model
[[nodiscard]] bool createOptionsModel(bool resetSettings); [[nodiscard]] bool createOptionsModel(bool resetSettings);
/// Initialize prune setting /// Initialize prune setting

View file

@ -52,7 +52,7 @@ void TestRpcCommand(RPCConsole* console)
} // namespace } // namespace
//! Entry point for BitcoinApplication tests. //! Entry point for BitcoinApplication tests.
void AppTests::appTests() void AppTests::appTests(interfaces::Init& init)
{ {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (QApplication::platformName() == "minimal") { if (QApplication::platformName() == "minimal") {
@ -67,7 +67,7 @@ void AppTests::appTests()
#endif #endif
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo"); qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
m_app.parameterSetup(); m_app.parameterSetup(init);
QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true)); QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true));
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().GetChainType())); QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().GetChainType()));
m_app.setupPlatformStyle(); m_app.setupPlatformStyle();

View file

@ -14,6 +14,10 @@ class BitcoinApplication;
class BitcoinGUI; class BitcoinGUI;
class RPCConsole; class RPCConsole;
namespace interfaces {
class Init;
} // namespace interfaces
class AppTests : public QObject class AppTests : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -21,7 +25,7 @@ public:
explicit AppTests(BitcoinApplication& app) : m_app(app) {} explicit AppTests(BitcoinApplication& app) : m_app(app) {}
private Q_SLOTS: private Q_SLOTS:
void appTests(); void appTests(interfaces::Init& init);
void guiTests(BitcoinGUI* window); void guiTests(BitcoinGUI* window);
void consoleTests(RPCConsole* console); void consoleTests(RPCConsole* console);

View file

@ -44,6 +44,8 @@ public:
std::string peerAddr; std::string peerAddr;
std::any context; std::any context;
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;
// Note: If you add fields to this struct, you should also update the
// JSONRPCRequest struct in ipc/capnp/chain.capnp.
void parse(const UniValue& valRequest); void parse(const UniValue& valRequest);
[[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; }; [[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; };

View file

@ -1134,6 +1134,7 @@ public:
void read(Span<std::byte> dst) { GetStream().read(dst); } void read(Span<std::byte> dst) { GetStream().read(dst); }
void ignore(size_t num) { GetStream().ignore(num); } void ignore(size_t num) { GetStream().ignore(num); }
bool eof() const { return GetStream().eof(); } bool eof() const { return GetStream().eof(); }
bool empty() const { return GetStream().empty(); }
size_t size() const { return GetStream().size(); } size_t size() const { return GetStream().size(); }
//! Get reference to stream parameters. //! Get reference to stream parameters.

View file

@ -178,7 +178,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
SelectParams(chainType); SelectParams(chainType);
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN); if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
InitLogging(*m_node.args); InitLogging(*m_node.args, /* log_suffix= */ nullptr);
AppInitParameterInteraction(*m_node.args); AppInitParameterInteraction(*m_node.args);
LogInstance().StartLogging(); LogInstance().StartLogging();
m_node.warnings = std::make_unique<node::Warnings>(); m_node.warnings = std::make_unique<node::Warnings>();

View file

@ -53,7 +53,7 @@ private:
friend bilingual_str ErrorString(const Result<FT>& result); friend bilingual_str ErrorString(const Result<FT>& result);
public: public:
Result() : m_variant{std::in_place_index_t<1>{}, std::monostate{}} {} // constructor for void Result() : m_variant{std::in_place_index_t<1>{}, T{}} {}
Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {} Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {} Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
Result(Result&&) = default; Result(Result&&) = default;

View file

@ -117,6 +117,9 @@ public:
std::optional<uint32_t> m_version; std::optional<uint32_t> m_version;
//! Caps weight of resulting tx //! Caps weight of resulting tx
std::optional<int> m_max_tx_weight{std::nullopt}; std::optional<int> m_max_tx_weight{std::nullopt};
// Note: If you add fields to this struct, you should also update the
// CoinControl struct in ipc/capnp/wallet.capnp and IPC serialization code
// in src/ipc/capnp/wallet.cpp.
CCoinControl(); CCoinControl();

View file

@ -9,6 +9,7 @@
#include <init.h> #include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/init.h> #include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/wallet.h> #include <interfaces/wallet.h>
#include <net.h> #include <net.h>
#include <node/context.h> #include <node/context.h>
@ -130,6 +131,13 @@ void WalletInit::Construct(NodeContext& node) const
return; return;
} }
auto wallet_loader = node.init->makeWalletLoader(*node.chain); auto wallet_loader = node.init->makeWalletLoader(*node.chain);
if (!wallet_loader) {
// If the current process doesn't have wallet support linked in, spawn
// a new wallet process.
auto init = node.init->ipc()->spawnProcess("bitcoin-wallet");
wallet_loader = init->makeWalletLoader(*node.chain);
node.init->ipc()->addCleanup(*wallet_loader, [init = init.release()] { delete init; });
}
node.wallet_loader = wallet_loader.get(); node.wallet_loader = wallet_loader.get();
node.chain_clients.emplace_back(std::move(wallet_loader)); node.chain_clients.emplace_back(std::move(wallet_loader));
} }

View file

@ -290,6 +290,8 @@ struct CRecipient
CTxDestination dest; CTxDestination dest;
CAmount nAmount; CAmount nAmount;
bool fSubtractFeeFromAmount; bool fSubtractFeeFromAmount;
// Note: If you add fields to this struct, you should also update the
// Recipient struct in ipc/capnp/wallet.capnp.
}; };
class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime

View file

@ -62,6 +62,10 @@ def main():
colors["node1"] = "\033[0;32m" # GREEN colors["node1"] = "\033[0;32m" # GREEN
colors["node2"] = "\033[0;31m" # RED colors["node2"] = "\033[0;31m" # RED
colors["node3"] = "\033[0;33m" # YELLOW colors["node3"] = "\033[0;33m" # YELLOW
colors["wall0"] = "\033[0;34;1m" # BLUE
colors["wall1"] = "\033[0;32;1m" # GREEN
colors["wall2"] = "\033[0;31;1m" # RED
colors["wall3"] = "\033[0;33;1m" # YELLOW
colors["reset"] = "\033[0m" # Reset font color colors["reset"] = "\033[0m" # Reset font color
log_events = read_logs(testdir) log_events = read_logs(testdir)
@ -96,6 +100,10 @@ def read_logs(tmp_dir):
break break
files.append(("node%d" % i, logfile)) files.append(("node%d" % i, logfile))
wallet_logfile = "{}/node{}/regtest/debug.log.wallet".format(tmp_dir, i)
if os.path.isfile(wallet_logfile):
files.append(("wall%d" % i, wallet_logfile))
return heapq.merge(*[get_log_events(source, f) for source, f in files]) return heapq.merge(*[get_log_events(source, f) for source, f in files])

View file

@ -89,6 +89,7 @@ class FullBlockTest(BitcoinTestFramework):
'-acceptnonstdtxn=1', # This is a consensus block test, we don't care about tx policy '-acceptnonstdtxn=1', # This is a consensus block test, we don't care about tx policy
'-testactivationheight=bip34@2', '-testactivationheight=bip34@2',
]] ]]
self.rpc_timeout = 1920
def run_test(self): def run_test(self):
node = self.nodes[0] # convenience reference to the node node = self.nodes[0] # convenience reference to the node
@ -1293,7 +1294,7 @@ class FullBlockTest(BitcoinTestFramework):
blocks2 = [] blocks2 = []
for i in range(89, LARGE_REORG_SIZE + 89): for i in range(89, LARGE_REORG_SIZE + 89):
blocks2.append(self.next_block("alt" + str(i))) blocks2.append(self.next_block("alt" + str(i)))
self.send_blocks(blocks2, False, force_send=False) self.send_blocks(blocks2, False, force_send=False, timeout=1920)
# extend alt chain to trigger re-org # extend alt chain to trigger re-org
block = self.next_block("alt" + str(chain1_tip + 1)) block = self.next_block("alt" + str(chain1_tip + 1))
@ -1302,7 +1303,7 @@ class FullBlockTest(BitcoinTestFramework):
# ... and re-org back to the first chain # ... and re-org back to the first chain
self.move_tip(chain1_tip) self.move_tip(chain1_tip)
block = self.next_block(chain1_tip + 1) block = self.next_block(chain1_tip + 1)
self.send_blocks([block], False, force_send=True) self.send_blocks([block], False, force_send=True, timeout=1920)
block = self.next_block(chain1_tip + 2) block = self.next_block(chain1_tip + 2)
self.send_blocks([block], True, timeout=2440) self.send_blocks([block], True, timeout=2440)
@ -1318,6 +1319,13 @@ class FullBlockTest(BitcoinTestFramework):
b_cb34.solve() b_cb34.solve()
self.send_blocks([b_cb34], success=False, reject_reason='bad-cb-height', reconnect=True) self.send_blocks([b_cb34], success=False, reject_reason='bad-cb-height', reconnect=True)
# Flush the notification queue before shutting down, so the
# FlushBackgroundCallbacks call made during shutdown won't exceed the
# test framework's 60 second shutdown timeout on slow systems, due to
# all the BlockConnected notifications generated during the test.
self.log.info("Wait for BlockConnected notifications to be processed before shutdown")
self.nodes[0].syncwithvalidationinterfacequeue()
# Helper methods # Helper methods
################ ################

View file

@ -36,8 +36,8 @@ class ConfArgsTest(BitcoinTestFramework):
def setup_nodes(self): def setup_nodes(self):
self.add_nodes(self.num_nodes, self.extra_args) self.add_nodes(self.num_nodes, self.extra_args)
# Ensure a log file exists as TestNode.assert_debug_log() expects it. # Ensure a log file exists as TestNode.assert_debug_log() expects it.
self.nodes[0].debug_log_path.parent.mkdir() self.nodes[0].debug_log_path(wallet=False).parent.mkdir()
self.nodes[0].debug_log_path.touch() self.nodes[0].debug_log_path(wallet=False).touch()
def test_dir_config(self): def test_dir_config(self):
self.log.info('Error should be emitted if config file is a directory') self.log.info('Error should be emitted if config file is a directory')
@ -251,6 +251,7 @@ class ConfArgsTest(BitcoinTestFramework):
'Command-line arg: rpcallowip=****', 'Command-line arg: rpcallowip=****',
]): ]):
self.start_node(0, extra_args=[ self.start_node(0, extra_args=[
'-debugexclude=ipc',
'-addnode=some.node', '-addnode=some.node',
'-rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0', '-rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
'-rpcbind=127.1.1.1', '-rpcbind=127.1.1.1',

View file

@ -35,7 +35,7 @@ class PosixFsPermissionsTest(BitcoinTestFramework):
self.check_directory_permissions(datadir) self.check_directory_permissions(datadir)
walletsdir = self.nodes[0].wallets_path walletsdir = self.nodes[0].wallets_path
self.check_directory_permissions(walletsdir) self.check_directory_permissions(walletsdir)
debuglog = self.nodes[0].debug_log_path debuglog = self.nodes[0].debug_log_path(wallet=False)
self.check_file_permissions(debuglog) self.check_file_permissions(debuglog)

View file

@ -1285,6 +1285,7 @@ class TaprootTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
self.setup_clean_chain = True self.setup_clean_chain = True
self.rpc_timeout = 120
def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False): def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False):

View file

@ -14,6 +14,7 @@ import logging
import os import os
import platform import platform
import re import re
import signal
import subprocess import subprocess
import tempfile import tempfile
import time import time
@ -463,9 +464,13 @@ class TestNode():
def chain_path(self) -> Path: def chain_path(self) -> Path:
return self.datadir_path / self.chain return self.datadir_path / self.chain
@property def debug_log_path(self, wallet: bool) -> Path:
def debug_log_path(self) -> Path: path = self.chain_path / 'debug.log'
return self.chain_path / 'debug.log' if wallet:
wallet_path = path.with_name(path.name + ".wallet")
if wallet_path.exists():
path = wallet_path
return path
@property @property
def blocks_path(self) -> Path: def blocks_path(self) -> Path:
@ -483,26 +488,26 @@ class TestNode():
def wallets_path(self) -> Path: def wallets_path(self) -> Path:
return self.chain_path / "wallets" return self.chain_path / "wallets"
def debug_log_size(self, **kwargs) -> int: def debug_log_size(self, wallet: bool, **kwargs) -> int:
with open(self.debug_log_path, **kwargs) as dl: with open(self.debug_log_path(wallet), **kwargs) as dl:
dl.seek(0, 2) dl.seek(0, 2)
return dl.tell() return dl.tell()
@contextlib.contextmanager @contextlib.contextmanager
def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2, wallet=False):
if unexpected_msgs is None: if unexpected_msgs is None:
unexpected_msgs = [] unexpected_msgs = []
assert_equal(type(expected_msgs), list) assert_equal(type(expected_msgs), list)
assert_equal(type(unexpected_msgs), list) assert_equal(type(unexpected_msgs), list)
time_end = time.time() + timeout * self.timeout_factor time_end = time.time() + timeout * self.timeout_factor
prev_size = self.debug_log_size(encoding="utf-8") # Must use same encoding that is used to read() below prev_size = self.debug_log_size(wallet, encoding="utf-8") # Must use same encoding that is used to read() below
yield yield
while True: while True:
found = True found = True
with open(self.debug_log_path, encoding="utf-8", errors="replace") as dl: with open(self.debug_log_path(wallet), encoding="utf-8", errors="replace") as dl:
dl.seek(prev_size) dl.seek(prev_size)
log = dl.read() log = dl.read()
print_log = " - " + "\n - ".join(log.splitlines()) print_log = " - " + "\n - ".join(log.splitlines())
@ -520,20 +525,20 @@ class TestNode():
self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log))
@contextlib.contextmanager @contextlib.contextmanager
def busy_wait_for_debug_log(self, expected_msgs, timeout=60): def busy_wait_for_debug_log(self, expected_msgs, timeout=60, wallet=False):
""" """
Block until we see a particular debug log message fragment or until we exceed the timeout. Block until we see a particular debug log message fragment or until we exceed the timeout.
Return: Return:
the number of log lines we encountered when matching the number of log lines we encountered when matching
""" """
time_end = time.time() + timeout * self.timeout_factor time_end = time.time() + timeout * self.timeout_factor
prev_size = self.debug_log_size(mode="rb") # Must use same mode that is used to read() below prev_size = self.debug_log_size(wallet, mode="rb") # Must use same mode that is used to read() below
yield yield
while True: while True:
found = True found = True
with open(self.debug_log_path, "rb") as dl: with open(self.debug_log_path(wallet), "rb") as dl:
dl.seek(prev_size) dl.seek(prev_size)
log = dl.read() log = dl.read()
@ -840,6 +845,23 @@ class TestNode():
def wait_until(self, test_function, timeout=60, check_interval=0.05): def wait_until(self, test_function, timeout=60, check_interval=0.05):
return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.timeout_factor, check_interval=check_interval) return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.timeout_factor, check_interval=check_interval)
def kill(self):
"""Abruptly kill the node to test recovery from an unclean shutdown."""
# Before killing the node, check the log to see if it spawned a wallet
# process. If it did, we need to send a kill signal to the wallet
# process as well, so the wallet shutdown is unclean and the wallet
# databases are not flushed. If the wallet process were not killed here,
# it would shut down cleanly instead of uncleanly when it lost the
# connection to the node, which is not what we want for testing.
wallet_pid = None
with open(self.debug_log_path(wallet=False), encoding="utf-8", errors="replace") as dl:
for line in dl:
if m := re.search(r"\[spawnProcess\] \[ipc\] Process bitcoin-wallet pid ([0-9]+) launched", line):
wallet_pid = int(m.group(1))
if wallet_pid is not None:
os.kill(wallet_pid, signal.SIGKILL)
self.process.kill()
class TestNodeCLIAttr: class TestNodeCLIAttr:
def __init__(self, cli, command): def __init__(self, cli, command):

View file

@ -532,7 +532,7 @@ class ToolWalletTest(BitcoinTestFramework):
# Next cause a bunch of writes by filling the keypool # Next cause a bunch of writes by filling the keypool
wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100) wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100)
# Lastly kill bitcoind so that the LSNs don't get reset # Lastly kill bitcoind so that the LSNs don't get reset
self.nodes[0].process.kill() self.nodes[0].kill()
self.nodes[0].wait_until_stopped(expected_ret_code=1 if platform.system() == "Windows" else -9) self.nodes[0].wait_until_stopped(expected_ret_code=1 if platform.system() == "Windows" else -9)
assert self.nodes[0].is_node_stopped() assert self.nodes[0].is_node_stopped()

View file

@ -209,7 +209,7 @@ class WalletDumpTest(BitcoinTestFramework):
assert result['ismine'] assert result['ismine']
self.log.info('Check that wallet is flushed') self.log.info('Check that wallet is flushed')
with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20): with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20, wallet=True):
self.nodes[0].getnewaddress() self.nodes[0].getnewaddress()
# Make sure that dumpwallet doesn't have a lock order issue when there is an unconfirmed tx and it is reloaded # Make sure that dumpwallet doesn't have a lock order issue when there is an unconfirmed tx and it is reloaded

View file

@ -65,26 +65,26 @@ class WalletFastRescanTest(BitcoinTestFramework):
self.generate(node, 1) self.generate(node, 1)
self.log.info("Import wallet backup with block filter index") self.log.info("Import wallet backup with block filter index")
with node.assert_debug_log(['fast variant using block filters']): with node.assert_debug_log(['fast variant using block filters'], wallet=True):
node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME) node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME)
txids_fast = self.get_wallet_txids(node, 'rescan_fast') txids_fast = self.get_wallet_txids(node, 'rescan_fast')
self.log.info("Import non-active descriptors with block filter index") self.log.info("Import non-active descriptors with block filter index")
node.createwallet(wallet_name='rescan_fast_nonactive', descriptors=True, disable_private_keys=True, blank=True) node.createwallet(wallet_name='rescan_fast_nonactive', descriptors=True, disable_private_keys=True, blank=True)
with node.assert_debug_log(['fast variant using block filters']): with node.assert_debug_log(['fast variant using block filters'], wallet=True):
w = node.get_wallet_rpc('rescan_fast_nonactive') w = node.get_wallet_rpc('rescan_fast_nonactive')
w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive') txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive')
self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0']) self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0'])
self.log.info("Import wallet backup w/o block filter index") self.log.info("Import wallet backup w/o block filter index")
with node.assert_debug_log(['slow variant inspecting all blocks']): with node.assert_debug_log(['slow variant inspecting all blocks'], wallet=True):
node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME) node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME)
txids_slow = self.get_wallet_txids(node, 'rescan_slow') txids_slow = self.get_wallet_txids(node, 'rescan_slow')
self.log.info("Import non-active descriptors w/o block filter index") self.log.info("Import non-active descriptors w/o block filter index")
node.createwallet(wallet_name='rescan_slow_nonactive', descriptors=True, disable_private_keys=True, blank=True) node.createwallet(wallet_name='rescan_slow_nonactive', descriptors=True, disable_private_keys=True, blank=True)
with node.assert_debug_log(['slow variant inspecting all blocks']): with node.assert_debug_log(['slow variant inspecting all blocks'], wallet=True):
w = node.get_wallet_rpc('rescan_slow_nonactive') w = node.get_wallet_rpc('rescan_slow_nonactive')
w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive') txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive')

View file

@ -127,7 +127,7 @@ class WalletGroupTest(BitcoinTestFramework):
self.nodes[0].sendtoaddress(addr_aps, 1.0) self.nodes[0].sendtoaddress(addr_aps, 1.0)
self.nodes[0].sendtoaddress(addr_aps, 1.0) self.nodes[0].sendtoaddress(addr_aps, 1.0)
self.generate(self.nodes[0], 1) self.generate(self.nodes[0], 1)
with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx4_ungrouped_fee}, grouped = {tx4_grouped_fee}, using grouped']): with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx4_ungrouped_fee}, grouped = {tx4_grouped_fee}, using grouped'], wallet=True):
txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1) txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
tx4 = self.nodes[3].getrawtransaction(txid4, True) tx4 = self.nodes[3].getrawtransaction(txid4, True)
# tx4 should have 2 inputs and 2 outputs although one output would # tx4 should have 2 inputs and 2 outputs although one output would
@ -138,7 +138,7 @@ class WalletGroupTest(BitcoinTestFramework):
addr_aps2 = self.nodes[3].getnewaddress() addr_aps2 = self.nodes[3].getnewaddress()
[self.nodes[0].sendtoaddress(addr_aps2, 1.0) for _ in range(5)] [self.nodes[0].sendtoaddress(addr_aps2, 1.0) for _ in range(5)]
self.generate(self.nodes[0], 1) self.generate(self.nodes[0], 1)
with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using non-grouped']): with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using non-grouped'], wallet=True):
txid5 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 2.95) txid5 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 2.95)
tx5 = self.nodes[3].getrawtransaction(txid5, True) tx5 = self.nodes[3].getrawtransaction(txid5, True)
# tx5 should have 3 inputs (1.0, 1.0, 1.0) and 2 outputs # tx5 should have 3 inputs (1.0, 1.0, 1.0) and 2 outputs
@ -151,7 +151,7 @@ class WalletGroupTest(BitcoinTestFramework):
addr_aps3 = self.nodes[4].getnewaddress() addr_aps3 = self.nodes[4].getnewaddress()
[self.nodes[0].sendtoaddress(addr_aps3, 1.0) for _ in range(5)] [self.nodes[0].sendtoaddress(addr_aps3, 1.0) for _ in range(5)]
self.generate(self.nodes[0], 1) self.generate(self.nodes[0], 1)
with self.nodes[4].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using grouped']): with self.nodes[4].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using grouped'], wallet=True):
txid6 = self.nodes[4].sendtoaddress(self.nodes[0].getnewaddress(), 2.95) txid6 = self.nodes[4].sendtoaddress(self.nodes[0].getnewaddress(), 2.95)
tx6 = self.nodes[4].getrawtransaction(txid6, True) tx6 = self.nodes[4].getrawtransaction(txid6, True)
# tx6 should have 5 inputs and 2 outputs # tx6 should have 5 inputs and 2 outputs

View file

@ -689,7 +689,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
encrypted_wallet.walletpassphrase("passphrase", 99999) encrypted_wallet.walletpassphrase("passphrase", 99999)
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread: with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
with self.nodes[0].assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=10): with self.nodes[0].assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=10, wallet=True):
importing = thread.submit(encrypted_wallet.importdescriptors, requests=[descriptor]) importing = thread.submit(encrypted_wallet.importdescriptors, requests=[descriptor])
# Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan # Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan

View file

@ -132,7 +132,7 @@ class MultiWalletTest(BitcoinTestFramework):
os.mkdir(wallet_dir('no_access')) os.mkdir(wallet_dir('no_access'))
os.chmod(wallet_dir('no_access'), 0) os.chmod(wallet_dir('no_access'), 0)
try: try:
with self.nodes[0].assert_debug_log(expected_msgs=['Error scanning']): with self.nodes[0].assert_debug_log(expected_msgs=['Error scanning'], wallet=True):
walletlist = self.nodes[0].listwalletdir()['wallets'] walletlist = self.nodes[0].listwalletdir()['wallets']
finally: finally:
# Need to ensure access is restored for cleanup # Need to ensure access is restored for cleanup

View file

@ -72,7 +72,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
self.log.info("Bump time & check that transaction is rebroadcast") self.log.info("Bump time & check that transaction is rebroadcast")
# Transaction should be rebroadcast approximately 24 hours in the future, # Transaction should be rebroadcast approximately 24 hours in the future,
# but can range from 12-36. So bump 36 hours to be sure. # but can range from 12-36. So bump 36 hours to be sure.
with node.assert_debug_log(['resubmit 1 unconfirmed transactions']): with node.assert_debug_log(['resubmit 1 unconfirmed transactions'], wallet=True):
node.setmocktime(now + 36 * 60 * 60) node.setmocktime(now + 36 * 60 * 60)
# Tell scheduler to call MaybeResendWalletTxs now. # Tell scheduler to call MaybeResendWalletTxs now.
node.mockscheduler(60) node.mockscheduler(60)
@ -130,7 +130,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5 evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5
# Flush out currently scheduled resubmit attempt now so that there can't be one right between eviction and check. # Flush out currently scheduled resubmit attempt now so that there can't be one right between eviction and check.
with node.assert_debug_log(['resubmit 2 unconfirmed transactions']): with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], wallet=True):
node.setmocktime(evict_time) node.setmocktime(evict_time)
node.mockscheduler(60) node.mockscheduler(60)
@ -141,7 +141,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid) assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid)
# Rebroadcast and check that parent and child are both in the mempool # Rebroadcast and check that parent and child are both in the mempool
with node.assert_debug_log(['resubmit 2 unconfirmed transactions']): with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], wallet=True):
node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer
node.mockscheduler(60) node.mockscheduler(60)
node.getmempoolentry(txid) node.getmempoolentry(txid)

View file

@ -32,6 +32,7 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
["-keypool=400"], ["-keypool=400"],
[] []
] ]
self.rpc_timeout = 120
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
@ -202,7 +203,7 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
encrypted_wallet.sethdseed(seed=hd_seed) encrypted_wallet.sethdseed(seed=hd_seed)
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread: with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
with minernode.assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=5): with minernode.assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=5, wallet=True):
rescanning = thread.submit(encrypted_wallet.rescanblockchain) rescanning = thread.submit(encrypted_wallet.rescanblockchain)
# set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan # set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan