mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 14:37:42 -03:00
Merge remote-tracking branch 'origin/pull/10102/head'
This commit is contained in:
commit
5a865918d6
69 changed files with 2360 additions and 89 deletions
|
@ -15,7 +15,7 @@ MAPPING = {
|
|||
# define functions and variables declared in corresponding .h files is
|
||||
# incorrect.
|
||||
HEADER_MODULE_PATHS = [
|
||||
'interfaces/'
|
||||
'ipc/'
|
||||
]
|
||||
|
||||
def module_name(path):
|
||||
|
|
|
@ -48,7 +48,7 @@ See [dependencies.md](dependencies.md) for a complete overview.
|
|||
To install, run the following from your terminal:
|
||||
|
||||
``` bash
|
||||
brew install cmake boost pkgconf libevent
|
||||
brew install cmake boost pkgconf libevent capnp
|
||||
```
|
||||
|
||||
### 4. Clone Bitcoin repository
|
||||
|
|
|
@ -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
|
||||
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:
|
||||
|
||||
|
|
|
@ -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 |
|
||||
| [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 |
|
||||
|
|
|
@ -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.
|
||||
[#19460](https://github.com/bitcoin/bitcoin/pull/19460) also adds a new `bitcoin-node` `-ipcbind` option and an `bitcoind-wallet` `-ipcconnect` option to allow new wallet processes to connect to an existing node process.
|
||||
And [#19461](https://github.com/bitcoin/bitcoin/pull/19461) adds a new `bitcoin-gui` `-ipcconnect` option to allow new GUI processes to connect to an existing node process.
|
||||
|
||||
## Known issues
|
||||
|
||||
- Unexpected socket disconnects aren't handled cleanly many places. Interface calls that used to never throw can now throw exceptions if a socket is disconnected (typically because a process on the other side of the connection has crashed or been killed), leading to errors.
|
||||
|
||||
- Internally spawned bitcoin-node and bitcoin-wallet processes don't currently install signal handlers and so won't shut down cleanly if terminated with [CTRL-C](https://github.com/bitcoin/bitcoin/pull/10102#issuecomment-595353238). Shutting down with `bitcoin-cli stop` should still shut down cleanly, and is a suggested alternative.
|
||||
|
|
|
@ -139,6 +139,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
|
|||
net_types.cpp
|
||||
netaddress.cpp
|
||||
netbase.cpp
|
||||
node/interface_ui.cpp
|
||||
outputtype.cpp
|
||||
policy/feerate.cpp
|
||||
policy/policy.cpp
|
||||
|
@ -173,12 +174,18 @@ set(installable_targets)
|
|||
if(ENABLE_WALLET)
|
||||
add_subdirectory(wallet)
|
||||
|
||||
if(BUILD_WALLET_TOOL)
|
||||
if(BUILD_WALLET_TOOL OR WITH_MULTIPROCESS)
|
||||
add_executable(bitcoin-wallet
|
||||
bitcoin-wallet.cpp
|
||||
init/bitcoin-wallet.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)
|
||||
target_link_libraries(bitcoin-wallet
|
||||
core_interface
|
||||
|
@ -237,7 +244,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
|
|||
node/context.cpp
|
||||
node/database_args.cpp
|
||||
node/eviction.cpp
|
||||
node/interface_ui.cpp
|
||||
node/interfaces.cpp
|
||||
node/kernel_notifications.cpp
|
||||
node/mempool_args.cpp
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <kernel/context.h>
|
||||
#include <node/context.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
|
||||
args.SoftSetBoolArg("-server", true);
|
||||
// Set this early so that parameter interactions go to console
|
||||
InitLogging(args);
|
||||
interfaces::Ipc* ipc = node.init->ipc();
|
||||
InitLogging(args, ipc ? ipc->logSuffix() : nullptr);
|
||||
InitParameterInteraction(args);
|
||||
if (!AppInitBasicSetup(args, node.exit_status)) {
|
||||
// InitError will have been called with detailed error, which ends up on console
|
||||
|
|
|
@ -798,9 +798,9 @@ void InitParameterInteraction(ArgsManager& args)
|
|||
* Note that this is called very early in the process lifetime, so you should be
|
||||
* careful about what global state you rely on here.
|
||||
*/
|
||||
void InitLogging(const ArgsManager& args)
|
||||
void InitLogging(const ArgsManager& args, const char* log_suffix)
|
||||
{
|
||||
init::SetLoggingOptions(args);
|
||||
init::SetLoggingOptions(args, log_suffix);
|
||||
init::LogPackageVersion();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ bool ShutdownRequested(node::NodeContext& node);
|
|||
void Interrupt(node::NodeContext& node);
|
||||
void Shutdown(node::NodeContext& node);
|
||||
//!Initialize the logging infrastructure
|
||||
void InitLogging(const ArgsManager& args);
|
||||
void InitLogging(const ArgsManager& args, const char* log_suffix);
|
||||
//!Parameter interaction: change current parameters depending on various rules
|
||||
void InitParameterInteraction(ArgsManager& args);
|
||||
|
||||
|
|
|
@ -3,17 +3,18 @@
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/echo.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <node/context.h>
|
||||
#include <util/check.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ipc {
|
||||
namespace capnp {
|
||||
void SetupNodeClient(ipc::Context& context);
|
||||
} // namespace capnp
|
||||
} // namespace ipc
|
||||
|
||||
namespace init {
|
||||
namespace {
|
||||
const char* EXE_NAME = "bitcoin-gui";
|
||||
|
@ -21,25 +22,16 @@ const char* EXE_NAME = "bitcoin-gui";
|
|||
class BitcoinGuiInit : public interfaces::Init
|
||||
{
|
||||
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);
|
||||
m_node.init = this;
|
||||
ipc::capnp::SetupNodeClient(m_ipc->context());
|
||||
}
|
||||
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(); }
|
||||
// bitcoin-gui accepts -ipcbind option even though it does not use it
|
||||
// directly. It just returns true here to accept the option because
|
||||
// bitcoin-node accepts the option, and bitcoin-gui accepts all bitcoin-node
|
||||
// options and will start the node with those options.
|
||||
bool canListenIpc() override { return true; }
|
||||
node::NodeContext m_node;
|
||||
std::unique_ptr<interfaces::Ipc> m_ipc;
|
||||
};
|
||||
} // namespace
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/echo.h>
|
||||
|
@ -9,10 +10,21 @@
|
|||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <ipc/context.h>
|
||||
#include <node/context.h>
|
||||
#include <util/check.h>
|
||||
|
||||
#include <functional>
|
||||
#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 {
|
||||
|
@ -22,19 +34,22 @@ class BitcoinNodeInit : public interfaces::Init
|
|||
{
|
||||
public:
|
||||
BitcoinNodeInit(node::NodeContext& node, const char* arg0)
|
||||
: m_node(node),
|
||||
m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this))
|
||||
: m_node(node), m_ipc(interfaces::MakeIpc(EXE_NAME, "", arg0, *this))
|
||||
{
|
||||
InitContext(m_node);
|
||||
m_node.init = this;
|
||||
// Extra initialization code that runs when a bitcoin-node process is
|
||||
// spawned by a bitcoin-gui process, after the ArgsManager configuration
|
||||
// is transferred from the parent process to the child process.
|
||||
m_ipc->context().init_process = [this] {
|
||||
InitLogging(*Assert(m_node.args), m_ipc->logSuffix());
|
||||
InitParameterInteraction(*Assert(m_node.args));
|
||||
};
|
||||
ipc::capnp::SetupNodeServer(m_ipc->context());
|
||||
}
|
||||
std::unique_ptr<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::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(); }
|
||||
interfaces::Ipc* ipc() override { return m_ipc.get(); }
|
||||
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)
|
||||
{
|
||||
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
|
||||
// bypass normal execution and just respond to requests over the IPC
|
||||
// channel and return null.
|
||||
// Check if bitcoin-node is being invoked as an IPC server by the gui. If
|
||||
// so, then bypass normal execution and just respond to requests over the
|
||||
// IPC channel and return null.
|
||||
if (init->m_ipc->startSpawnedProcess(argc, argv, exit_status)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
94
src/init/bitcoin-wallet-ipc.cpp
Normal file
94
src/init/bitcoin-wallet-ipc.cpp
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
||||
void SetLoggingOptions(const ArgsManager& args)
|
||||
void SetLoggingOptions(const ArgsManager& args, const char* log_suffix)
|
||||
{
|
||||
LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile");
|
||||
LogInstance().m_file_path = AbsPathForConfigVal(args, args.GetPathArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
|
||||
if (log_suffix && LogInstance().m_file_path != "/dev/null") LogInstance().m_file_path += log_suffix;
|
||||
LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false));
|
||||
LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
|
||||
LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
|
||||
|
|
|
@ -14,7 +14,7 @@ class ArgsManager;
|
|||
|
||||
namespace init {
|
||||
void AddLoggingArgs(ArgsManager& args);
|
||||
void SetLoggingOptions(const ArgsManager& args);
|
||||
void SetLoggingOptions(const ArgsManager& args, const char* log_suffix);
|
||||
[[nodiscard]] util::Result<void> SetLoggingCategories(const ArgsManager& args);
|
||||
[[nodiscard]] util::Result<void> SetLoggingLevel(const ArgsManager& args);
|
||||
bool StartLogging(const ArgsManager& args);
|
||||
|
|
|
@ -354,7 +354,7 @@ public:
|
|||
//! support for writing null values to settings.json.
|
||||
//! Depending on the action returned by the update function, this will either
|
||||
//! 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.
|
||||
//! Null can be passed to erase the setting.
|
||||
|
@ -401,7 +401,8 @@ public:
|
|||
//! Load saved state.
|
||||
virtual bool load() = 0;
|
||||
|
||||
//! Start client execution and provide a scheduler.
|
||||
//! Start client execution and provide a scheduler. (Scheduler is
|
||||
//! ignored if client is out-of-process).
|
||||
virtual void start(CScheduler& scheduler) = 0;
|
||||
|
||||
//! Save state to disk.
|
||||
|
|
|
@ -81,6 +81,9 @@ public:
|
|||
//! IPC context struct accessor (see struct definition for more description).
|
||||
virtual ipc::Context& context() = 0;
|
||||
|
||||
//! Suffix for debug.log to avoid output clashes from different processes.
|
||||
virtual const char* logSuffix() = 0;
|
||||
|
||||
protected:
|
||||
//! Internal implementation of public addCleanup method (above) as a
|
||||
//! type-erased virtual function, since template functions can't be virtual.
|
||||
|
@ -88,7 +91,7 @@ protected:
|
|||
};
|
||||
|
||||
//! 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
|
||||
|
||||
#endif // BITCOIN_INTERFACES_IPC_H
|
||||
|
|
|
@ -369,6 +369,8 @@ struct WalletAddress
|
|||
wallet::AddressPurpose purpose;
|
||||
std::string name;
|
||||
|
||||
WalletAddress() = default;
|
||||
|
||||
WalletAddress(CTxDestination dest, wallet::isminetype is_mine, wallet::AddressPurpose purpose, std::string name)
|
||||
: dest(std::move(dest)), is_mine(is_mine), purpose(std::move(purpose)), name(std::move(name))
|
||||
{
|
||||
|
|
|
@ -3,17 +3,26 @@
|
|||
# file COPYING or https://opensource.org/license/mit/.
|
||||
|
||||
add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL
|
||||
capnp/chain.cpp
|
||||
capnp/common.cpp
|
||||
capnp/init.cpp
|
||||
capnp/mining.cpp
|
||||
capnp/node.cpp
|
||||
capnp/protocol.cpp
|
||||
capnp/wallet.cpp
|
||||
interfaces.cpp
|
||||
process.cpp
|
||||
)
|
||||
|
||||
target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR}
|
||||
capnp/chain.capnp
|
||||
capnp/common.capnp
|
||||
capnp/echo.capnp
|
||||
capnp/handler.capnp
|
||||
capnp/init.capnp
|
||||
capnp/mining.capnp
|
||||
capnp/node.capnp
|
||||
capnp/wallet.capnp
|
||||
)
|
||||
|
||||
target_link_libraries(bitcoin_ipc
|
||||
|
|
96
src/ipc/capnp/chain-types.h
Normal file
96
src/ipc/capnp/chain-types.h
Normal 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
195
src/ipc/capnp/chain.capnp
Normal 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
205
src/ipc/capnp/chain.cpp
Normal 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
|
|
@ -5,22 +5,57 @@
|
|||
#ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H
|
||||
#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <clientversion.h>
|
||||
#include <consensus/validation.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 <protocol.h>
|
||||
#include <serialize.h>
|
||||
#include <streams.h>
|
||||
#include <univalue.h>
|
||||
#include <util/result.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <mp/proxy-types.h>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace ipc {
|
||||
namespace capnp {
|
||||
//! Construct a ParamStream wrapping a data stream with serialization parameters
|
||||
//! needed to pass transaction objects between bitcoin processes.
|
||||
//! Convert kj::StringPtr to std::string.
|
||||
inline std::string ToString(const kj::StringPtr& str) { return {str.cStr(), str.size()}; }
|
||||
|
||||
//! Convert kj::ArrayPtr to std::string.
|
||||
inline std::string ToString(const kj::ArrayPtr<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
|
||||
//! require serialization parameters. Params should just be chosen to serialize
|
||||
//! objects completely and ensure that serializing and deserializing objects
|
||||
|
@ -29,7 +64,28 @@ namespace capnp {
|
|||
template <typename 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
|
||||
|
@ -114,6 +170,41 @@ decltype(auto) CustomReadField(TypeList<std::chrono::duration<Rep, Period>>, Pri
|
|||
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
|
||||
//! parameters and return values as JSON strings.
|
||||
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
|
||||
//! 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
|
||||
|
@ -160,6 +327,39 @@ requires
|
|||
auto result = output.init(data.size());
|
||||
memcpy(result.begin(), data.data(), data.size());
|
||||
}
|
||||
|
||||
// libmultiprocess only provides read/build functions for std::set, not
|
||||
// std::unordered_set, so copy and paste those functions here.
|
||||
// TODO: Move these to libmultiprocess and dedup std::set, std::unordered_set,
|
||||
// and std::vector implementations.
|
||||
template <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
|
||||
|
||||
#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H
|
||||
|
|
|
@ -8,9 +8,52 @@ using Cxx = import "/capnp/c++.capnp";
|
|||
$Cxx.namespace("ipc::capnp::messages");
|
||||
|
||||
using Proxy = import "/mp/proxy.capnp";
|
||||
$Proxy.include("ipc/capnp/common.h");
|
||||
$Proxy.includeTypes("ipc/capnp/common-types.h");
|
||||
|
||||
struct BlockRef $Proxy.wrap("interfaces::BlockRef") {
|
||||
hash @0 :Data;
|
||||
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
51
src/ipc/capnp/common.cpp
Normal 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
29
src/ipc/capnp/common.h
Normal 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
|
|
@ -5,8 +5,16 @@
|
|||
#ifndef BITCOIN_IPC_CAPNP_CONTEXT_H
|
||||
#define BITCOIN_IPC_CAPNP_CONTEXT_H
|
||||
|
||||
#include <ipc/capnp/node.capnp.h>
|
||||
#include <ipc/context.h>
|
||||
|
||||
namespace interfaces {
|
||||
class Node;
|
||||
} // namespace interfaces
|
||||
namespace mp {
|
||||
struct InvokeContext;
|
||||
} // namespace mp
|
||||
|
||||
namespace ipc {
|
||||
namespace capnp {
|
||||
//! Cap'n Proto context struct. Generally the parent ipc::Context struct should
|
||||
|
@ -16,6 +24,12 @@ namespace capnp {
|
|||
//! function and object types to capnp hooks.
|
||||
struct Context : ipc::Context
|
||||
{
|
||||
using MakeNodeClient = std::unique_ptr<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 ipc
|
||||
|
|
16
src/ipc/capnp/handler.capnp
Normal file
16
src/ipc/capnp/handler.capnp
Normal 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) -> ();
|
||||
}
|
|
@ -5,7 +5,32 @@
|
|||
#ifndef 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/init.capnp.proxy.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
|
||||
|
|
|
@ -8,16 +8,25 @@ using Cxx = import "/capnp/c++.capnp";
|
|||
$Cxx.namespace("ipc::capnp::messages");
|
||||
|
||||
using Proxy = import "/mp/proxy.capnp";
|
||||
$Proxy.include("interfaces/chain.h");
|
||||
$Proxy.include("interfaces/echo.h");
|
||||
$Proxy.include("interfaces/init.h");
|
||||
$Proxy.include("interfaces/mining.h");
|
||||
$Proxy.include("interfaces/node.h");
|
||||
$Proxy.includeTypes("ipc/capnp/init-types.h");
|
||||
|
||||
using Chain = import "chain.capnp";
|
||||
using Common = import "common.capnp";
|
||||
using Echo = import "echo.capnp";
|
||||
using Mining = import "mining.capnp";
|
||||
using Node = import "node.capnp";
|
||||
using Wallet = import "wallet.capnp";
|
||||
|
||||
interface Init $Proxy.wrap("interfaces::Init") {
|
||||
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
|
||||
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
|
||||
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
36
src/ipc/capnp/init.cpp
Normal 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
114
src/ipc/capnp/node-types.h
Normal 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
213
src/ipc/capnp/node.capnp
Normal 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
142
src/ipc/capnp/node.cpp
Normal 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
54
src/ipc/capnp/node.h
Normal 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
|
55
src/ipc/capnp/wallet-types.h
Normal file
55
src/ipc/capnp/wallet-types.h
Normal 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
260
src/ipc/capnp/wallet.capnp
Normal 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
246
src/ipc/capnp/wallet.cpp
Normal 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
36
src/ipc/capnp/wallet.h
Normal 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
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef BITCOIN_IPC_CONTEXT_H
|
||||
#define BITCOIN_IPC_CONTEXT_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace ipc {
|
||||
//! Context struct used to give IPC protocol implementations or implementation
|
||||
//! 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.
|
||||
struct Context
|
||||
{
|
||||
//! Callback to initialize spawned process after receiving ArgsManager
|
||||
//! configuration from parent.
|
||||
std::function<void()> init_process;
|
||||
};
|
||||
} // namespace ipc
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ namespace {
|
|||
class IpcImpl : public interfaces::Ipc
|
||||
{
|
||||
public:
|
||||
IpcImpl(const char* exe_name, const char* process_argv0, interfaces::Init& init)
|
||||
: m_exe_name(exe_name), m_process_argv0(process_argv0), m_init(init),
|
||||
IpcImpl(const char* exe_name, const char* log_suffix, const char* process_argv0, interfaces::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())
|
||||
{
|
||||
}
|
||||
|
@ -91,7 +91,9 @@ public:
|
|||
m_protocol->addCleanup(type, iface, std::move(cleanup));
|
||||
}
|
||||
Context& context() override { return m_protocol->context(); }
|
||||
const char* logSuffix() override { return m_log_suffix; }
|
||||
const char* m_exe_name;
|
||||
const char* m_log_suffix;
|
||||
const char* m_process_argv0;
|
||||
interfaces::Init& m_init;
|
||||
std::unique_ptr<Protocol> m_protocol;
|
||||
|
@ -101,8 +103,8 @@ public:
|
|||
} // namespace ipc
|
||||
|
||||
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
|
||||
|
|
|
@ -220,6 +220,9 @@ public:
|
|||
TransportProtocolType m_transport_type;
|
||||
/** BIP324 session id string in hex, if any. */
|
||||
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.
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ struct CNodeStateStats {
|
|||
ServiceFlags their_services;
|
||||
int64_t presync_height{-1};
|
||||
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 {
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
std::string m_unix_socket_path;
|
||||
bool m_is_unix_socket;
|
||||
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
|
||||
{
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/mining.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <interfaces/types.h>
|
||||
|
@ -101,7 +103,11 @@ class NodeImpl : public Node
|
|||
{
|
||||
public:
|
||||
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()); }
|
||||
bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); }
|
||||
int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
|
||||
|
@ -820,7 +826,7 @@ public:
|
|||
return result;
|
||||
}
|
||||
bool updateRwSetting(const std::string& name,
|
||||
const interfaces::SettingsUpdate& update_settings_func) override
|
||||
interfaces::SettingsUpdate update_settings_func) override
|
||||
{
|
||||
std::optional<interfaces::SettingsAction> action;
|
||||
args().LockSettings([&](common::Settings& settings) {
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
#include <common/init.h>
|
||||
#include <common/system.h>
|
||||
#include <init.h>
|
||||
#include <init/common.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <logging.h>
|
||||
#include <node/context.h>
|
||||
|
@ -290,6 +292,18 @@ void BitcoinApplication::createNode(interfaces::Init& init)
|
|||
{
|
||||
assert(!m_node);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -313,13 +327,14 @@ void BitcoinApplication::startThread()
|
|||
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
|
||||
// print to the console unnecessarily.
|
||||
gArgs.SoftSetBoolArg("-printtoconsole", false);
|
||||
|
||||
InitLogging(gArgs);
|
||||
interfaces::Ipc* ipc = init.ipc();
|
||||
InitLogging(gArgs, ipc ? ipc->logSuffix() : nullptr);
|
||||
InitParameterInteraction(gArgs);
|
||||
}
|
||||
|
||||
|
@ -659,7 +674,7 @@ int GuiMain(int argc, char* argv[])
|
|||
// Install qDebug() message handler to route to debug.log
|
||||
qInstallMessageHandler(DebugMessageHandler);
|
||||
// Allow parameter interaction before we create the options model
|
||||
app.parameterSetup();
|
||||
app.parameterSetup(*init);
|
||||
GUIUtil::LogQtInfo();
|
||||
|
||||
if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
void createPaymentServer();
|
||||
#endif
|
||||
/// parameter interaction/setup based on rules
|
||||
void parameterSetup();
|
||||
void parameterSetup(interfaces::Init& init);
|
||||
/// Create options model
|
||||
[[nodiscard]] bool createOptionsModel(bool resetSettings);
|
||||
/// Initialize prune setting
|
||||
|
|
|
@ -52,7 +52,7 @@ void TestRpcCommand(RPCConsole* console)
|
|||
} // namespace
|
||||
|
||||
//! Entry point for BitcoinApplication tests.
|
||||
void AppTests::appTests()
|
||||
void AppTests::appTests(interfaces::Init& init)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
if (QApplication::platformName() == "minimal") {
|
||||
|
@ -67,7 +67,7 @@ void AppTests::appTests()
|
|||
#endif
|
||||
|
||||
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
|
||||
m_app.parameterSetup();
|
||||
m_app.parameterSetup(init);
|
||||
QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true));
|
||||
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().GetChainType()));
|
||||
m_app.setupPlatformStyle();
|
||||
|
|
|
@ -14,6 +14,10 @@ class BitcoinApplication;
|
|||
class BitcoinGUI;
|
||||
class RPCConsole;
|
||||
|
||||
namespace interfaces {
|
||||
class Init;
|
||||
} // namespace interfaces
|
||||
|
||||
class AppTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -21,7 +25,7 @@ public:
|
|||
explicit AppTests(BitcoinApplication& app) : m_app(app) {}
|
||||
|
||||
private Q_SLOTS:
|
||||
void appTests();
|
||||
void appTests(interfaces::Init& init);
|
||||
void guiTests(BitcoinGUI* window);
|
||||
void consoleTests(RPCConsole* console);
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
std::string peerAddr;
|
||||
std::any context;
|
||||
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);
|
||||
[[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; };
|
||||
|
|
|
@ -1134,6 +1134,7 @@ public:
|
|||
void read(Span<std::byte> dst) { GetStream().read(dst); }
|
||||
void ignore(size_t num) { GetStream().ignore(num); }
|
||||
bool eof() const { return GetStream().eof(); }
|
||||
bool empty() const { return GetStream().empty(); }
|
||||
size_t size() const { return GetStream().size(); }
|
||||
|
||||
//! Get reference to stream parameters.
|
||||
|
|
|
@ -178,7 +178,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
|
|||
|
||||
SelectParams(chainType);
|
||||
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);
|
||||
LogInstance().StartLogging();
|
||||
m_node.warnings = std::make_unique<node::Warnings>();
|
||||
|
|
|
@ -53,7 +53,7 @@ private:
|
|||
friend bilingual_str ErrorString(const Result<FT>& result);
|
||||
|
||||
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(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
|
||||
Result(Result&&) = default;
|
||||
|
|
|
@ -117,6 +117,9 @@ public:
|
|||
std::optional<uint32_t> m_version;
|
||||
//! Caps weight of resulting tx
|
||||
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();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/wallet.h>
|
||||
#include <net.h>
|
||||
#include <node/context.h>
|
||||
|
@ -130,6 +131,13 @@ void WalletInit::Construct(NodeContext& node) const
|
|||
return;
|
||||
}
|
||||
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.chain_clients.emplace_back(std::move(wallet_loader));
|
||||
}
|
||||
|
|
|
@ -290,6 +290,8 @@ struct CRecipient
|
|||
CTxDestination dest;
|
||||
CAmount nAmount;
|
||||
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
|
||||
|
|
|
@ -62,6 +62,10 @@ def main():
|
|||
colors["node1"] = "\033[0;32m" # GREEN
|
||||
colors["node2"] = "\033[0;31m" # RED
|
||||
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
|
||||
|
||||
log_events = read_logs(testdir)
|
||||
|
@ -96,6 +100,10 @@ def read_logs(tmp_dir):
|
|||
break
|
||||
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])
|
||||
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class FullBlockTest(BitcoinTestFramework):
|
|||
'-acceptnonstdtxn=1', # This is a consensus block test, we don't care about tx policy
|
||||
'-testactivationheight=bip34@2',
|
||||
]]
|
||||
self.rpc_timeout = 1920
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0] # convenience reference to the node
|
||||
|
@ -1293,7 +1294,7 @@ class FullBlockTest(BitcoinTestFramework):
|
|||
blocks2 = []
|
||||
for i in range(89, LARGE_REORG_SIZE + 89):
|
||||
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
|
||||
block = self.next_block("alt" + str(chain1_tip + 1))
|
||||
|
@ -1302,7 +1303,7 @@ class FullBlockTest(BitcoinTestFramework):
|
|||
# ... and re-org back to the first chain
|
||||
self.move_tip(chain1_tip)
|
||||
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)
|
||||
self.send_blocks([block], True, timeout=2440)
|
||||
|
||||
|
@ -1318,6 +1319,13 @@ class FullBlockTest(BitcoinTestFramework):
|
|||
b_cb34.solve()
|
||||
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
|
||||
################
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ class ConfArgsTest(BitcoinTestFramework):
|
|||
def setup_nodes(self):
|
||||
self.add_nodes(self.num_nodes, self.extra_args)
|
||||
# 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.touch()
|
||||
self.nodes[0].debug_log_path(wallet=False).parent.mkdir()
|
||||
self.nodes[0].debug_log_path(wallet=False).touch()
|
||||
|
||||
def test_dir_config(self):
|
||||
self.log.info('Error should be emitted if config file is a directory')
|
||||
|
@ -251,6 +251,7 @@ class ConfArgsTest(BitcoinTestFramework):
|
|||
'Command-line arg: rpcallowip=****',
|
||||
]):
|
||||
self.start_node(0, extra_args=[
|
||||
'-debugexclude=ipc',
|
||||
'-addnode=some.node',
|
||||
'-rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
|
||||
'-rpcbind=127.1.1.1',
|
||||
|
|
|
@ -35,7 +35,7 @@ class PosixFsPermissionsTest(BitcoinTestFramework):
|
|||
self.check_directory_permissions(datadir)
|
||||
walletsdir = self.nodes[0].wallets_path
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -1285,6 +1285,7 @@ class TaprootTest(BitcoinTestFramework):
|
|||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
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):
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import logging
|
|||
import os
|
||||
import platform
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
@ -463,9 +464,13 @@ class TestNode():
|
|||
def chain_path(self) -> Path:
|
||||
return self.datadir_path / self.chain
|
||||
|
||||
@property
|
||||
def debug_log_path(self) -> Path:
|
||||
return self.chain_path / 'debug.log'
|
||||
def debug_log_path(self, wallet: bool) -> Path:
|
||||
path = 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
|
||||
def blocks_path(self) -> Path:
|
||||
|
@ -483,26 +488,26 @@ class TestNode():
|
|||
def wallets_path(self) -> Path:
|
||||
return self.chain_path / "wallets"
|
||||
|
||||
def debug_log_size(self, **kwargs) -> int:
|
||||
with open(self.debug_log_path, **kwargs) as dl:
|
||||
def debug_log_size(self, wallet: bool, **kwargs) -> int:
|
||||
with open(self.debug_log_path(wallet), **kwargs) as dl:
|
||||
dl.seek(0, 2)
|
||||
return dl.tell()
|
||||
|
||||
@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:
|
||||
unexpected_msgs = []
|
||||
assert_equal(type(expected_msgs), list)
|
||||
assert_equal(type(unexpected_msgs), list)
|
||||
|
||||
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
|
||||
|
||||
while 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)
|
||||
log = dl.read()
|
||||
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))
|
||||
|
||||
@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.
|
||||
Return:
|
||||
the number of log lines we encountered when matching
|
||||
"""
|
||||
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
|
||||
|
||||
while 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)
|
||||
log = dl.read()
|
||||
|
||||
|
@ -840,6 +845,23 @@ class TestNode():
|
|||
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)
|
||||
|
||||
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:
|
||||
def __init__(self, cli, command):
|
||||
|
|
|
@ -532,7 +532,7 @@ class ToolWalletTest(BitcoinTestFramework):
|
|||
# Next cause a bunch of writes by filling the keypool
|
||||
wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100)
|
||||
# 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)
|
||||
assert self.nodes[0].is_node_stopped()
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ class WalletDumpTest(BitcoinTestFramework):
|
|||
assert result['ismine']
|
||||
|
||||
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()
|
||||
|
||||
# Make sure that dumpwallet doesn't have a lock order issue when there is an unconfirmed tx and it is reloaded
|
||||
|
|
|
@ -65,26 +65,26 @@ class WalletFastRescanTest(BitcoinTestFramework):
|
|||
self.generate(node, 1)
|
||||
|
||||
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)
|
||||
txids_fast = self.get_wallet_txids(node, 'rescan_fast')
|
||||
|
||||
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)
|
||||
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.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
|
||||
txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive')
|
||||
|
||||
self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0'])
|
||||
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)
|
||||
txids_slow = self.get_wallet_txids(node, 'rescan_slow')
|
||||
|
||||
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)
|
||||
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.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
|
||||
txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive')
|
||||
|
|
|
@ -127,7 +127,7 @@ class WalletGroupTest(BitcoinTestFramework):
|
|||
self.nodes[0].sendtoaddress(addr_aps, 1.0)
|
||||
self.nodes[0].sendtoaddress(addr_aps, 1.0)
|
||||
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)
|
||||
tx4 = self.nodes[3].getrawtransaction(txid4, True)
|
||||
# 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()
|
||||
[self.nodes[0].sendtoaddress(addr_aps2, 1.0) for _ in range(5)]
|
||||
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)
|
||||
tx5 = self.nodes[3].getrawtransaction(txid5, True)
|
||||
# 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()
|
||||
[self.nodes[0].sendtoaddress(addr_aps3, 1.0) for _ in range(5)]
|
||||
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)
|
||||
tx6 = self.nodes[4].getrawtransaction(txid6, True)
|
||||
# tx6 should have 5 inputs and 2 outputs
|
||||
|
|
|
@ -689,7 +689,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
|
|||
|
||||
encrypted_wallet.walletpassphrase("passphrase", 99999)
|
||||
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])
|
||||
|
||||
# Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
|
||||
|
|
|
@ -132,7 +132,7 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||
os.mkdir(wallet_dir('no_access'))
|
||||
os.chmod(wallet_dir('no_access'), 0)
|
||||
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']
|
||||
finally:
|
||||
# Need to ensure access is restored for cleanup
|
||||
|
|
|
@ -72,7 +72,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
|
|||
self.log.info("Bump time & check that transaction is rebroadcast")
|
||||
# Transaction should be rebroadcast approximately 24 hours in the future,
|
||||
# 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)
|
||||
# Tell scheduler to call MaybeResendWalletTxs now.
|
||||
node.mockscheduler(60)
|
||||
|
@ -130,7 +130,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
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.
|
||||
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.mockscheduler(60)
|
||||
|
||||
|
@ -141,7 +141,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
|
|||
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
|
||||
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.mockscheduler(60)
|
||||
node.getmempoolentry(txid)
|
||||
|
|
|
@ -32,6 +32,7 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
|
|||
["-keypool=400"],
|
||||
[]
|
||||
]
|
||||
self.rpc_timeout = 120
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
@ -202,7 +203,7 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
|
|||
encrypted_wallet.sethdseed(seed=hd_seed)
|
||||
|
||||
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)
|
||||
|
||||
# set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
|
||||
|
|
Loading…
Add table
Reference in a new issue