Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiprocess compatibility updates #28721

Merged
merged 8 commits into from
Nov 13, 2023
8 changes: 4 additions & 4 deletions doc/design/multiprocess.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The `-debug=ipc` command line option can be used to see requests and responses b

## Installation

The multiprocess feature requires [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) as dependencies. A simple way to get starting using it without installing these dependencies manually is to use the [depends system](../depends) with the `MULTIPROCESS=1` [dependency option](../depends#dependency-options) passed to make:
The multiprocess feature requires [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) as dependencies. A simple way to get starting using it without installing these dependencies manually is to use the [depends system](../../depends) with the `MULTIPROCESS=1` [dependency option](../../depends#dependency-options) passed to make:

```
cd <BITCOIN_SOURCE_DIRECTORY>
Expand All @@ -32,12 +32,12 @@ BITCOIND=bitcoin-node test/functional/test_runner.py

The configure script will pick up settings and library locations from the depends directory, so there is no need to pass `--enable-multiprocess` as a separate flag when using the depends system (it's controlled by the `MULTIPROCESS=1` option).

Alternately, you can install [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) packages on your system, and just run `./configure --enable-multiprocess` without using the depends system. The configure script will be able to locate the installed packages via [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/). See [Installation](https://github.com/chaincodelabs/libmultiprocess#installation) section of the libmultiprocess readme for install steps. See [build-unix.md](build-unix.md) and [build-osx.md](build-osx.md) for information about installing dependencies in general.
Alternately, you can install [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) packages on your system, and just run `./configure --enable-multiprocess` without using the depends system. The configure script will be able to locate the installed packages via [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/). See [Installation](https://github.com/chaincodelabs/libmultiprocess/blob/master/doc/install.md) section of the libmultiprocess readme for install steps. See [build-unix.md](../build-unix.md) and [build-osx.md](../build-osx.md) for information about installing dependencies in general.

## IPC implementation details

Cross process Node, Wallet, and Chain interfaces are defined in
[`src/interfaces/`](../src/interfaces/). These are C++ classes which follow
[`src/interfaces/`](../../src/interfaces/). These are C++ classes which follow
[conventions](../developer-notes.md#internal-interface-guidelines), like passing
serializable arguments so they can be called from different processes, and
making methods pure virtual so they can have proxy implementations that forward
Expand All @@ -58,7 +58,7 @@ actual serialization and socket communication.
As much as possible, calls between processes are meant to work the same as
calls within a single process without adding limitations or requiring extra
implementation effort. Processes communicate with each other by calling regular
[C++ interface methods](../src/interfaces/README.md). Method arguments and
[C++ interface methods](../../src/interfaces/README.md). Method arguments and
return values are automatically serialized and sent between processes. Object
references and `std::function` arguments are automatically tracked and mapped
to allow invoked code to call back into invoking code at any time, and there is
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ BITCOIN_TESTS =\
test/sigopcount_tests.cpp \
test/skiplist_tests.cpp \
test/sock_tests.cpp \
test/span_tests.cpp \
test/streams_tests.cpp \
test/sync_tests.cpp \
test/system_tests.cpp \
Expand Down
7 changes: 7 additions & 0 deletions src/common/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,13 @@ fs::path ArgsManager::GetConfigFilePath() const
return *Assert(m_config_path);
}

void ArgsManager::SetConfigFilePath(fs::path path)
{
LOCK(cs_args);
assert(!m_config_path);
m_config_path = path;
}

ChainType ArgsManager::GetChainType() const
{
std::variant<ChainType, std::string> arg = GetChainArg();
Expand Down
1 change: 1 addition & 0 deletions src/common/args.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class ArgsManager
* Return config file path (read-only)
*/
fs::path GetConfigFilePath() const;
void SetConfigFilePath(fs::path);
[[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);

/**
Expand Down
7 changes: 5 additions & 2 deletions src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,15 @@ class Chain
// outputs in the same transaction) or have shared ancestry, the bump fees are calculated
// independently, i.e. as if only one of them is spent. This may result in double-fee-bumping. This
// caveat can be rectified per use of the sister-function CalculateCombinedBumpFee(…).
virtual std::map<COutPoint, CAmount> CalculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;
virtual std::map<COutPoint, CAmount> calculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;

//! Calculate the combined bump fee for an input set per the same strategy
// as in CalculateIndividualBumpFees(…).
// Unlike CalculateIndividualBumpFees(…), this does not return individual
// bump fees per outpoint, but a single bump fee for the shared ancestry.
// The combined bump fee may be used to correct overestimation due to
// shared ancestry by multiple UTXOs after coin selection.
virtual std::optional<CAmount> CalculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;
virtual std::optional<CAmount> calculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;

//! Get the node's package limits.
//! Currently only returns the ancestor and descendant count limits, but could be enhanced to
Expand Down Expand Up @@ -395,6 +395,9 @@ class ChainClient

//! Set mock time.
virtual void setMockTime(int64_t time) = 0;

//! Mock the scheduler to fast forward in time.
virtual void schedulerMockForward(std::chrono::seconds delta_seconds) = 0;
};

//! Return implementation of Chain interface.
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ class Node
//! Unset RPC timer interface.
virtual void rpcUnsetTimerInterface(RPCTimerInterface* iface) = 0;

//! Get unspent outputs associated with a transaction.
virtual bool getUnspentOutput(const COutPoint& output, Coin& coin) = 0;
//! Get unspent output associated with a transaction.
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;

//! Broadcast transaction.
virtual TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class Wallet
wallet::AddressPurpose* purpose) = 0;

//! Get wallet address list.
virtual std::vector<WalletAddress> getAddresses() const = 0;
virtual std::vector<WalletAddress> getAddresses() = 0;

//! Get receive requests.
virtual std::vector<std::string> getAddressReceiveRequests() = 0;
Expand Down
10 changes: 6 additions & 4 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,12 @@ class NodeImpl : public Node
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
bool getUnspentOutput(const COutPoint& output, Coin& coin) override
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
{
LOCK(::cs_main);
return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin);
Coin coin;
if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin;
return {};
}
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
{
Expand Down Expand Up @@ -669,7 +671,7 @@ class ChainImpl : public Chain
m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, ancestorsize, ancestorfees);
}

std::map<COutPoint, CAmount> CalculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
std::map<COutPoint, CAmount> calculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
{
if (!m_node.mempool) {
std::map<COutPoint, CAmount> bump_fees;
Expand All @@ -681,7 +683,7 @@ class ChainImpl : public Chain
return MiniMiner(*m_node.mempool, outpoints).CalculateBumpFees(target_feerate);
}

std::optional<CAmount> CalculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
std::optional<CAmount> calculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
{
if (!m_node.mempool) {
return 0;
Expand Down
6 changes: 2 additions & 4 deletions src/qt/transactiondesc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
{
COutPoint prevout = txin.prevout;

Coin prev;
if(node.getUnspentOutput(prevout, prev))
{
if (auto prev{node.getUnspentOutput(prevout)}) {
{
strHTML += "<li>";
const CTxOut &vout = prev.out;
const CTxOut& vout = prev->out;
CTxDestination address;
if (ExtractDestination(vout.scriptPubKey, address))
{
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ static RPCHelpMan mockscheduler()
const NodeContext& node_context{EnsureAnyNodeContext(request.context)};
CHECK_NONFATAL(node_context.scheduler)->MockForward(std::chrono::seconds{delta_seconds});
SyncWithValidationInterfaceQueue();
for (const auto& chain_client : node_context.chain_clients) {
chain_client->schedulerMockForward(std::chrono::seconds(delta_seconds));
}

return UniValue::VNULL;
},
Expand Down
21 changes: 19 additions & 2 deletions src/span.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,32 @@ class Span
template <typename O> friend class Span;
};

// Return result of calling .data() method on type T. This is used to be able to
// write template deduction guides for the single-parameter Span constructor
// below that will work if the value that is passed has a .data() method, and if
// the data method does not return a void pointer.
//
// It is important to check for the void type specifically below, so the
// deduction guides can be used in SFINAE contexts to check whether objects can
// be converted to spans. If the deduction guides did not explicitly check for
// void, and an object was passed that returned void* from data (like
// std::vector<bool>), the template deduction would succeed, but the Span<void>
// object instantiation would fail, resulting in a hard error, rather than a
// SFINAE error.
// https://stackoverflow.com/questions/68759148/sfinae-to-detect-the-explicitness-of-a-ctad-deduction-guide
// https://stackoverflow.com/questions/16568986/what-happens-when-you-call-data-on-a-stdvectorbool
template<typename T>
using DataResult = std::remove_pointer_t<decltype(std::declval<T&>().data())>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1fba885: I wonder if it is easier to just delete the content of this file and replace it with using Span = std::span?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #28721 (comment)

1fba885: I wonder if it is easier to just delete the content of this file and replace it with using Span = std::span?

Switching from Span to std::span might work, but it's a bigger change and I'd expect it to require other fixes and not just be a drop-in replacement.

If replacing span gets implemented and merged first, that's great and this commit can be dropped. But otherwise the actual code change here is small. I wouldn't want multiprocess code to depend on replacing span when there is a direct fix here which is 3 lines and is just adding two !std::is_void_v checks.


// Deduction guides for Span
// For the pointer/size based and iterator based constructor:
template <typename T, typename EndOrSize> Span(T*, EndOrSize) -> Span<T>;
// For the array constructor:
template <typename T, std::size_t N> Span(T (&)[N]) -> Span<T>;
// For the temporaries/rvalue references constructor, only supporting const output.
template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T>, const std::remove_pointer_t<decltype(std::declval<T&&>().data())>>>;
template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T> && !std::is_void_v<DataResult<T&&>>, const DataResult<T&&>>>;
// For (lvalue) references, supporting mutable output.
template <typename T> Span(T&) -> Span<std::remove_pointer_t<decltype(std::declval<T&>().data())>>;
template <typename T> Span(T&) -> Span<std::enable_if_t<!std::is_void_v<DataResult<T&>>, DataResult<T&>>>;

/** Pop the last element off a span, and return a reference to that element. */
template <typename T>
Expand Down
5 changes: 5 additions & 0 deletions src/streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ class SpanReader
memcpy(dst.data(), m_data.data(), dst.size());
m_data = m_data.subspan(dst.size());
}

void ignore(size_t n)
{
m_data = m_data.subspan(n);
}
};

/** Double ended buffer combining vector and stream-like interfaces.
Expand Down
73 changes: 73 additions & 0 deletions src/test/span_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <span.h>

#include <boost/test/unit_test.hpp>
#include <array>
#include <set>
#include <vector>

namespace {
struct Ignore
{
template<typename T> Ignore(T&&) {}
};
template<typename T>
bool Spannable(T&& value, decltype(Span{value})* enable = nullptr)
{
return true;
}
bool Spannable(Ignore)
{
return false;
}

#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunneeded-member-function"
# pragma clang diagnostic ignored "-Wunused-member-function"
#endif
struct SpannableYes
{
int* data();
size_t size();
};
struct SpannableNo
{
void* data();
size_t size();
};
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
} // namespace

BOOST_AUTO_TEST_SUITE(span_tests)

// Make sure template Span template deduction guides accurately enable calls to
// Span constructor overloads that work, and disable calls to constructor overloads that
// don't work. This makes it is possible to use the Span constructor in a SFINAE
// contexts like in the Spannable function above to detect whether types are or
// aren't compatible with Spans at compile time.
//
// Previously there was a bug where writing a SFINAE check for vector<bool> was
// not possible, because in libstdc++ vector<bool> has a data() memeber
// returning void*, and the Span template guide ignored the data() return value,
// so the template substitution would succeed, but the constructor would fail,
// resulting in a fatal compile error, rather than a SFINAE error that could be
// handled.
BOOST_AUTO_TEST_CASE(span_constructor_sfinae)
{
BOOST_CHECK(Spannable(std::vector<int>{}));
BOOST_CHECK(!Spannable(std::set<int>{}));
BOOST_CHECK(!Spannable(std::vector<bool>{}));
BOOST_CHECK(Spannable(std::array<int, 3>{}));
BOOST_CHECK(Spannable(Span<int>{}));
BOOST_CHECK(Spannable("char array"));
BOOST_CHECK(Spannable(SpannableYes{}));
BOOST_CHECK(!Spannable(SpannableNo{}));
}

BOOST_AUTO_TEST_SUITE_END()
2 changes: 2 additions & 0 deletions src/wallet/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <vector>

class ArgsManager;
class CScheduler;
namespace interfaces {
class Chain;
class Wallet;
Expand All @@ -34,6 +35,7 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wall
//! behavior.
struct WalletContext {
interfaces::Chain* chain{nullptr};
CScheduler* scheduler{nullptr};
ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
// It is unsafe to lock this after locking a CWallet::cs_wallet mutex because
// this could introduce inconsistent lock ordering and cause deadlocks.
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/feebumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CMutableTrans
reused_inputs.push_back(txin.prevout);
}

std::optional<CAmount> combined_bump_fee = wallet.chain().CalculateCombinedBumpFee(reused_inputs, newFeerate);
std::optional<CAmount> combined_bump_fee = wallet.chain().calculateCombinedBumpFee(reused_inputs, newFeerate);
if (!combined_bump_fee.has_value()) {
errors.push_back(strprintf(Untranslated("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")));
}
Expand Down
10 changes: 8 additions & 2 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <policy/fees.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <scheduler.h>
#include <support/allocators/secure.h>
#include <sync.h>
#include <uint256.h>
Expand Down Expand Up @@ -212,7 +213,7 @@ class WalletImpl : public Wallet
}
return true;
}
std::vector<WalletAddress> getAddresses() const override
std::vector<WalletAddress> getAddresses() override
{
LOCK(m_wallet->cs_wallet);
std::vector<WalletAddress> result;
Expand Down Expand Up @@ -585,10 +586,15 @@ class WalletLoaderImpl : public WalletLoader
}
bool verify() override { return VerifyWallets(m_context); }
bool load() override { return LoadWallets(m_context); }
void start(CScheduler& scheduler) override { return StartWallets(m_context, scheduler); }
void start(CScheduler& scheduler) override
{
m_context.scheduler = &scheduler;
return StartWallets(m_context);
}
void flush() override { return FlushWallets(m_context); }
void stop() override { return StopWallets(m_context); }
void setMockTime(int64_t time) override { return SetMockTime(time); }
void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); }

//! WalletLoader methods
util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
Expand Down
6 changes: 3 additions & 3 deletions src/wallet/load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,17 @@ bool LoadWallets(WalletContext& context)
}
}

void StartWallets(WalletContext& context, CScheduler& scheduler)
void StartWallets(WalletContext& context)
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
pwallet->postInitProcess();
}

// Schedule periodic wallet flushes and tx rebroadcasts
if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
scheduler.scheduleEvery([&context] { MaybeCompactWalletDB(context); }, std::chrono::milliseconds{500});
context.scheduler->scheduleEvery([&context] { MaybeCompactWalletDB(context); }, 500ms);
}
scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
}

void FlushWallets(WalletContext& context)
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/load.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ bool VerifyWallets(WalletContext& context);
bool LoadWallets(WalletContext& context);

//! Complete startup of wallets.
void StartWallets(WalletContext& context, CScheduler& scheduler);
void StartWallets(WalletContext& context);

//! Flush all wallets in preparation for shutdown.
void FlushWallets(WalletContext& context);
Expand Down