Skip to content

Commit 29c2c90

Browse files
committed
Merge bitcoin/bitcoin#28721: multiprocess compatibility updates
3b70f7b doc: fix broken doc/design/multiprocess.md links after #24352 (Ryan Ofsky) 6d43aad span: Make Span template deduction guides work in SFINAE context (Ryan Ofsky) 8062c3b util: Add ArgsManager SetConfigFilePath method (Ryan Ofsky) 441d00c interfaces: Rename CalculateBumpFees methods to be compatible with capn'proto (Ryan Ofsky) 156f49d interfaces: Change getUnspentOutput return type to avoid multiprocess segfault (Ryan Ofsky) 4978754 interfaces: Add schedulerMockForward method so mockscheduler RPC can work across processes (Ryan Ofsky) 924327e interfaces: Fix const virtual method that breaks multiprocess support (Ryan Ofsky) 82a379e streams: Add SpanReader ignore method (Russell Yanofsky) Pull request description: This is a collection of small changes to interfaces and code which were needed as part of multiprocess PR #10102, but have been moved here to make that PR smaller. All of these changes are refactoring changes which do not affect behavior of current code --- This PR is part of the [process separation project](bitcoin/bitcoin#28722). ACKs for top commit: achow101: ACK 3b70f7b naumenkogs: ACK 3b70f7b maflcko: re-ACK 3b70f7b 🎆 Tree-SHA512: 2368772b887056ad8a9f84c299cfde76ba45943770e3b5353130580900afa9611302195b899ced7b6e303b11f053ff204cae7c28ff4e12c55562fcc81119ba4c
2 parents e862bce + 3b70f7b commit 29c2c90

19 files changed

+147
-29
lines changed

doc/design/multiprocess.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The `-debug=ipc` command line option can be used to see requests and responses b
1919

2020
## Installation
2121

22-
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:
22+
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:
2323

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

3333
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).
3434

35-
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.
35+
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.
3636

3737
## IPC implementation details
3838

3939
Cross process Node, Wallet, and Chain interfaces are defined in
40-
[`src/interfaces/`](../src/interfaces/). These are C++ classes which follow
40+
[`src/interfaces/`](../../src/interfaces/). These are C++ classes which follow
4141
[conventions](../developer-notes.md#internal-interface-guidelines), like passing
4242
serializable arguments so they can be called from different processes, and
4343
making methods pure virtual so they can have proxy implementations that forward
@@ -58,7 +58,7 @@ actual serialization and socket communication.
5858
As much as possible, calls between processes are meant to work the same as
5959
calls within a single process without adding limitations or requiring extra
6060
implementation effort. Processes communicate with each other by calling regular
61-
[C++ interface methods](../src/interfaces/README.md). Method arguments and
61+
[C++ interface methods](../../src/interfaces/README.md). Method arguments and
6262
return values are automatically serialized and sent between processes. Object
6363
references and `std::function` arguments are automatically tracked and mapped
6464
to allow invoked code to call back into invoking code at any time, and there is

src/Makefile.test.include

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ BITCOIN_TESTS =\
146146
test/sigopcount_tests.cpp \
147147
test/skiplist_tests.cpp \
148148
test/sock_tests.cpp \
149+
test/span_tests.cpp \
149150
test/streams_tests.cpp \
150151
test/sync_tests.cpp \
151152
test/system_tests.cpp \

src/common/args.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,13 @@ fs::path ArgsManager::GetConfigFilePath() const
720720
return *Assert(m_config_path);
721721
}
722722

723+
void ArgsManager::SetConfigFilePath(fs::path path)
724+
{
725+
LOCK(cs_args);
726+
assert(!m_config_path);
727+
m_config_path = path;
728+
}
729+
723730
ChainType ArgsManager::GetChainType() const
724731
{
725732
std::variant<ChainType, std::string> arg = GetChainArg();

src/common/args.h

+1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class ArgsManager
180180
* Return config file path (read-only)
181181
*/
182182
fs::path GetConfigFilePath() const;
183+
void SetConfigFilePath(fs::path);
183184
[[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);
184185

185186
/**

src/interfaces/chain.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,15 @@ class Chain
244244
// outputs in the same transaction) or have shared ancestry, the bump fees are calculated
245245
// independently, i.e. as if only one of them is spent. This may result in double-fee-bumping. This
246246
// caveat can be rectified per use of the sister-function CalculateCombinedBumpFee(…).
247-
virtual std::map<COutPoint, CAmount> CalculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;
247+
virtual std::map<COutPoint, CAmount> calculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;
248248

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

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

396396
//! Set mock time.
397397
virtual void setMockTime(int64_t time) = 0;
398+
399+
//! Mock the scheduler to fast forward in time.
400+
virtual void schedulerMockForward(std::chrono::seconds delta_seconds) = 0;
398401
};
399402

400403
//! Return implementation of Chain interface.

src/interfaces/node.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ class Node
204204
//! Unset RPC timer interface.
205205
virtual void rpcUnsetTimerInterface(RPCTimerInterface* iface) = 0;
206206

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

210210
//! Broadcast transaction.
211211
virtual TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) = 0;

src/interfaces/wallet.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class Wallet
118118
wallet::AddressPurpose* purpose) = 0;
119119

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

123123
//! Get receive requests.
124124
virtual std::vector<std::string> getAddressReceiveRequests() = 0;

src/node/interfaces.cpp

+6-4
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,12 @@ class NodeImpl : public Node
328328
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
329329
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
330330
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
331-
bool getUnspentOutput(const COutPoint& output, Coin& coin) override
331+
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
332332
{
333333
LOCK(::cs_main);
334-
return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin);
334+
Coin coin;
335+
if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin;
336+
return {};
335337
}
336338
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
337339
{
@@ -670,7 +672,7 @@ class ChainImpl : public Chain
670672
m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, ancestorsize, ancestorfees);
671673
}
672674

673-
std::map<COutPoint, CAmount> CalculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
675+
std::map<COutPoint, CAmount> calculateIndividualBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
674676
{
675677
if (!m_node.mempool) {
676678
std::map<COutPoint, CAmount> bump_fees;
@@ -682,7 +684,7 @@ class ChainImpl : public Chain
682684
return MiniMiner(*m_node.mempool, outpoints).CalculateBumpFees(target_feerate);
683685
}
684686

685-
std::optional<CAmount> CalculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
687+
std::optional<CAmount> calculateCombinedBumpFee(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) override
686688
{
687689
if (!m_node.mempool) {
688690
return 0;

src/qt/transactiondesc.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
360360
{
361361
COutPoint prevout = txin.prevout;
362362

363-
Coin prev;
364-
if(node.getUnspentOutput(prevout, prev))
365-
{
363+
if (auto prev{node.getUnspentOutput(prevout)}) {
366364
{
367365
strHTML += "<li>";
368-
const CTxOut &vout = prev.out;
366+
const CTxOut& vout = prev->out;
369367
CTxDestination address;
370368
if (ExtractDestination(vout.scriptPubKey, address))
371369
{

src/rpc/node.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ static RPCHelpMan mockscheduler()
9191
const NodeContext& node_context{EnsureAnyNodeContext(request.context)};
9292
CHECK_NONFATAL(node_context.scheduler)->MockForward(std::chrono::seconds{delta_seconds});
9393
SyncWithValidationInterfaceQueue();
94+
for (const auto& chain_client : node_context.chain_clients) {
95+
chain_client->schedulerMockForward(std::chrono::seconds(delta_seconds));
96+
}
9497

9598
return UniValue::VNULL;
9699
},

src/span.h

+19-2
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,32 @@ class Span
222222
template <typename O> friend class Span;
223223
};
224224

225+
// Return result of calling .data() method on type T. This is used to be able to
226+
// write template deduction guides for the single-parameter Span constructor
227+
// below that will work if the value that is passed has a .data() method, and if
228+
// the data method does not return a void pointer.
229+
//
230+
// It is important to check for the void type specifically below, so the
231+
// deduction guides can be used in SFINAE contexts to check whether objects can
232+
// be converted to spans. If the deduction guides did not explicitly check for
233+
// void, and an object was passed that returned void* from data (like
234+
// std::vector<bool>), the template deduction would succeed, but the Span<void>
235+
// object instantiation would fail, resulting in a hard error, rather than a
236+
// SFINAE error.
237+
// https://stackoverflow.com/questions/68759148/sfinae-to-detect-the-explicitness-of-a-ctad-deduction-guide
238+
// https://stackoverflow.com/questions/16568986/what-happens-when-you-call-data-on-a-stdvectorbool
239+
template<typename T>
240+
using DataResult = std::remove_pointer_t<decltype(std::declval<T&>().data())>;
241+
225242
// Deduction guides for Span
226243
// For the pointer/size based and iterator based constructor:
227244
template <typename T, typename EndOrSize> Span(T*, EndOrSize) -> Span<T>;
228245
// For the array constructor:
229246
template <typename T, std::size_t N> Span(T (&)[N]) -> Span<T>;
230247
// For the temporaries/rvalue references constructor, only supporting const output.
231-
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())>>>;
248+
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&&>>>;
232249
// For (lvalue) references, supporting mutable output.
233-
template <typename T> Span(T&) -> Span<std::remove_pointer_t<decltype(std::declval<T&>().data())>>;
250+
template <typename T> Span(T&) -> Span<std::enable_if_t<!std::is_void_v<DataResult<T&>>, DataResult<T&>>>;
234251

235252
/** Pop the last element off a span, and return a reference to that element. */
236253
template <typename T>

src/streams.h

+5
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ class SpanReader
182182
memcpy(dst.data(), m_data.data(), dst.size());
183183
m_data = m_data.subspan(dst.size());
184184
}
185+
186+
void ignore(size_t n)
187+
{
188+
m_data = m_data.subspan(n);
189+
}
185190
};
186191

187192
/** Double ended buffer combining vector and stream-like interfaces.

src/test/span_tests.cpp

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <span.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
#include <array>
9+
#include <set>
10+
#include <vector>
11+
12+
namespace {
13+
struct Ignore
14+
{
15+
template<typename T> Ignore(T&&) {}
16+
};
17+
template<typename T>
18+
bool Spannable(T&& value, decltype(Span{value})* enable = nullptr)
19+
{
20+
return true;
21+
}
22+
bool Spannable(Ignore)
23+
{
24+
return false;
25+
}
26+
27+
#if defined(__clang__)
28+
# pragma clang diagnostic push
29+
# pragma clang diagnostic ignored "-Wunneeded-member-function"
30+
# pragma clang diagnostic ignored "-Wunused-member-function"
31+
#endif
32+
struct SpannableYes
33+
{
34+
int* data();
35+
size_t size();
36+
};
37+
struct SpannableNo
38+
{
39+
void* data();
40+
size_t size();
41+
};
42+
#if defined(__clang__)
43+
# pragma clang diagnostic pop
44+
#endif
45+
} // namespace
46+
47+
BOOST_AUTO_TEST_SUITE(span_tests)
48+
49+
// Make sure template Span template deduction guides accurately enable calls to
50+
// Span constructor overloads that work, and disable calls to constructor overloads that
51+
// don't work. This makes it is possible to use the Span constructor in a SFINAE
52+
// contexts like in the Spannable function above to detect whether types are or
53+
// aren't compatible with Spans at compile time.
54+
//
55+
// Previously there was a bug where writing a SFINAE check for vector<bool> was
56+
// not possible, because in libstdc++ vector<bool> has a data() memeber
57+
// returning void*, and the Span template guide ignored the data() return value,
58+
// so the template substitution would succeed, but the constructor would fail,
59+
// resulting in a fatal compile error, rather than a SFINAE error that could be
60+
// handled.
61+
BOOST_AUTO_TEST_CASE(span_constructor_sfinae)
62+
{
63+
BOOST_CHECK(Spannable(std::vector<int>{}));
64+
BOOST_CHECK(!Spannable(std::set<int>{}));
65+
BOOST_CHECK(!Spannable(std::vector<bool>{}));
66+
BOOST_CHECK(Spannable(std::array<int, 3>{}));
67+
BOOST_CHECK(Spannable(Span<int>{}));
68+
BOOST_CHECK(Spannable("char array"));
69+
BOOST_CHECK(Spannable(SpannableYes{}));
70+
BOOST_CHECK(!Spannable(SpannableNo{}));
71+
}
72+
73+
BOOST_AUTO_TEST_SUITE_END()

src/wallet/context.h

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <vector>
1414

1515
class ArgsManager;
16+
class CScheduler;
1617
namespace interfaces {
1718
class Chain;
1819
class Wallet;
@@ -34,6 +35,7 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wall
3435
//! behavior.
3536
struct WalletContext {
3637
interfaces::Chain* chain{nullptr};
38+
CScheduler* scheduler{nullptr};
3739
ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
3840
// It is unsafe to lock this after locking a CWallet::cs_wallet mutex because
3941
// this could introduce inconsistent lock ordering and cause deadlocks.

src/wallet/feebumper.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CMutableTrans
8686
reused_inputs.push_back(txin.prevout);
8787
}
8888

89-
std::optional<CAmount> combined_bump_fee = wallet.chain().CalculateCombinedBumpFee(reused_inputs, newFeerate);
89+
std::optional<CAmount> combined_bump_fee = wallet.chain().calculateCombinedBumpFee(reused_inputs, newFeerate);
9090
if (!combined_bump_fee.has_value()) {
9191
errors.push_back(strprintf(Untranslated("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")));
9292
}

src/wallet/interfaces.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <policy/fees.h>
1212
#include <primitives/transaction.h>
1313
#include <rpc/server.h>
14+
#include <scheduler.h>
1415
#include <support/allocators/secure.h>
1516
#include <sync.h>
1617
#include <uint256.h>
@@ -212,7 +213,7 @@ class WalletImpl : public Wallet
212213
}
213214
return true;
214215
}
215-
std::vector<WalletAddress> getAddresses() const override
216+
std::vector<WalletAddress> getAddresses() override
216217
{
217218
LOCK(m_wallet->cs_wallet);
218219
std::vector<WalletAddress> result;
@@ -585,10 +586,15 @@ class WalletLoaderImpl : public WalletLoader
585586
}
586587
bool verify() override { return VerifyWallets(m_context); }
587588
bool load() override { return LoadWallets(m_context); }
588-
void start(CScheduler& scheduler) override { return StartWallets(m_context, scheduler); }
589+
void start(CScheduler& scheduler) override
590+
{
591+
m_context.scheduler = &scheduler;
592+
return StartWallets(m_context);
593+
}
589594
void flush() override { return FlushWallets(m_context); }
590595
void stop() override { return StopWallets(m_context); }
591596
void setMockTime(int64_t time) override { return SetMockTime(time); }
597+
void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); }
592598

593599
//! WalletLoader methods
594600
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

src/wallet/load.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,17 @@ bool LoadWallets(WalletContext& context)
141141
}
142142
}
143143

144-
void StartWallets(WalletContext& context, CScheduler& scheduler)
144+
void StartWallets(WalletContext& context)
145145
{
146146
for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
147147
pwallet->postInitProcess();
148148
}
149149

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

157157
void FlushWallets(WalletContext& context)

src/wallet/load.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ bool VerifyWallets(WalletContext& context);
2626
bool LoadWallets(WalletContext& context);
2727

2828
//! Complete startup of wallets.
29-
void StartWallets(WalletContext& context, CScheduler& scheduler);
29+
void StartWallets(WalletContext& context);
3030

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

0 commit comments

Comments
 (0)