From 82a379eca8251c736b4de6e7a2516582641ce397 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Fri, 17 Dec 2021 15:56:37 -0500 Subject: [PATCH 1/8] streams: Add SpanReader ignore method Needed to deserialize some types from spans like CScripts --- src/streams.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/streams.h b/src/streams.h index d58de5233b..ae1434cc1c 100644 --- a/src/streams.h +++ b/src/streams.h @@ -182,6 +182,11 @@ public: 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. From 924327eaf3ada45a603e80aa4a3ab38a0f8c8673 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 23 Aug 2022 22:03:49 -0400 Subject: [PATCH 2/8] interfaces: Fix const virtual method that breaks multiprocess support --- src/interfaces/wallet.h | 2 +- src/wallet/interfaces.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 4b896c11a3..6114236623 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -118,7 +118,7 @@ public: wallet::AddressPurpose* purpose) = 0; //! Get wallet address list. - virtual std::vector getAddresses() const = 0; + virtual std::vector getAddresses() = 0; //! Get receive requests. virtual std::vector getAddressReceiveRequests() = 0; diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 65285187f4..4caf8a6066 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -212,7 +212,7 @@ public: } return true; } - std::vector getAddresses() const override + std::vector getAddresses() override { LOCK(m_wallet->cs_wallet); std::vector result; From 4978754c0058bbdfbcd492f25fa49ef211e11d6e Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Thu, 8 Sep 2022 15:21:08 -0400 Subject: [PATCH 3/8] interfaces: Add schedulerMockForward method so mockscheduler RPC can work across processes Needed to fix new wallet_groups.py and wallet_resendwallettransactions.py tests with multiprocess bitcoin-node executable. --- src/interfaces/chain.h | 3 +++ src/rpc/node.cpp | 3 +++ src/wallet/context.h | 2 ++ src/wallet/interfaces.cpp | 8 +++++++- src/wallet/load.cpp | 6 +++--- src/wallet/load.h | 2 +- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index dea868f844..18d5b8f4a3 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -395,6 +395,9 @@ public: //! 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. diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index 6b3662996c..b085828215 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -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; }, diff --git a/src/wallet/context.h b/src/wallet/context.h index 57a6ed77f7..58b9924fb4 100644 --- a/src/wallet/context.h +++ b/src/wallet/context.h @@ -13,6 +13,7 @@ #include class ArgsManager; +class CScheduler; namespace interfaces { class Chain; class Wallet; @@ -34,6 +35,7 @@ using LoadWalletFn = std::function 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. diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 4caf8a6066..a64c42603a 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -585,10 +586,15 @@ public: } 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> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector& warnings) override diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 4cdfadbee2..8b78a670e4 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -141,7 +141,7 @@ bool LoadWallets(WalletContext& context) } } -void StartWallets(WalletContext& context, CScheduler& scheduler) +void StartWallets(WalletContext& context) { for (const std::shared_ptr& pwallet : GetWallets(context)) { pwallet->postInitProcess(); @@ -149,9 +149,9 @@ void StartWallets(WalletContext& context, CScheduler& scheduler) // 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) diff --git a/src/wallet/load.h b/src/wallet/load.h index 0882f7f8ad..c079cad955 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -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); From 156f49d682ef025fb942c997a6c5475e18eef9cf Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Mon, 12 Sep 2022 11:09:06 -0400 Subject: [PATCH 4/8] interfaces: Change getUnspentOutput return type to avoid multiprocess segfault Coin serialize method segfaults if IsSpent condition is true. This caused multiprocess code to segfault when serializing the Coin& output argument to of the Node::getUnspentOutput method if the coin was not found. Segfault could be triggered by double clicking and viewing transaction details in the GUI transaction list. Fix this by replacing Coin& output argument with optional return value to avoid trying to serializing spent coins. --- src/interfaces/node.h | 4 ++-- src/node/interfaces.cpp | 6 ++++-- src/qt/transactiondesc.cpp | 6 ++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 3f8df57124..aeb2612c07 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -204,8 +204,8 @@ public: //! 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 getUnspentOutput(const COutPoint& output) = 0; //! Broadcast transaction. virtual TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index f6dbe4f008..944c26f00a 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -328,10 +328,12 @@ public: std::vector 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 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 { diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index dae6a2dea9..34daa8493a 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -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 += "
  • "; - const CTxOut &vout = prev.out; + const CTxOut& vout = prev->out; CTxDestination address; if (ExtractDestination(vout.scriptPubKey, address)) { From 441d00c60f0a67889d23f8556190ff99dde488bc Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 26 Sep 2023 13:47:05 -0400 Subject: [PATCH 5/8] interfaces: Rename CalculateBumpFees methods to be compatible with capn'proto --- src/interfaces/chain.h | 4 ++-- src/node/interfaces.cpp | 4 ++-- src/wallet/feebumper.cpp | 2 +- src/wallet/spend.cpp | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 18d5b8f4a3..b6ea91aaa7 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -244,7 +244,7 @@ public: // 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 CalculateIndividualBumpFees(const std::vector& outpoints, const CFeeRate& target_feerate) = 0; + virtual std::map calculateIndividualBumpFees(const std::vector& outpoints, const CFeeRate& target_feerate) = 0; //! Calculate the combined bump fee for an input set per the same strategy // as in CalculateIndividualBumpFees(…). @@ -252,7 +252,7 @@ public: // 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 CalculateCombinedBumpFee(const std::vector& outpoints, const CFeeRate& target_feerate) = 0; + virtual std::optional calculateCombinedBumpFee(const std::vector& 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 diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 944c26f00a..c876d9b6a7 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -671,7 +671,7 @@ public: m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, ancestorsize, ancestorfees); } - std::map CalculateIndividualBumpFees(const std::vector& outpoints, const CFeeRate& target_feerate) override + std::map calculateIndividualBumpFees(const std::vector& outpoints, const CFeeRate& target_feerate) override { if (!m_node.mempool) { std::map bump_fees; @@ -683,7 +683,7 @@ public: return MiniMiner(*m_node.mempool, outpoints).CalculateBumpFees(target_feerate); } - std::optional CalculateCombinedBumpFee(const std::vector& outpoints, const CFeeRate& target_feerate) override + std::optional calculateCombinedBumpFee(const std::vector& outpoints, const CFeeRate& target_feerate) override { if (!m_node.mempool) { return 0; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index f4cb4bbd66..f0fd789c96 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -86,7 +86,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CMutableTrans reused_inputs.push_back(txin.prevout); } - std::optional combined_bump_fee = wallet.chain().CalculateCombinedBumpFee(reused_inputs, newFeerate); + std::optional 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."))); } diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 8314a2ddfa..ed0134375c 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -259,7 +259,7 @@ util::Result FetchSelectedInputs(const CWallet& wallet, const { PreSelectedInputs result; const bool can_grind_r = wallet.CanGrindR(); - std::map map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate); + std::map map_of_bump_fees = wallet.chain().calculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate); for (const COutPoint& outpoint : coin_control.ListSelected()) { int input_bytes = -1; CTxOut txout; @@ -453,7 +453,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, } if (feerate.has_value()) { - std::map map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(outpoints, feerate.value()); + std::map map_of_bump_fees = wallet.chain().calculateIndividualBumpFees(outpoints, feerate.value()); for (auto& [_, outputs] : result.coins) { for (auto& output : outputs) { @@ -725,7 +725,7 @@ util::Result ChooseSelectionResult(interfaces::Chain& chain, co outpoints.push_back(coin->outpoint); summed_bump_fees += coin->ancestor_bump_fees; } - std::optional combined_bump_fee = chain.CalculateCombinedBumpFee(outpoints, coin_selection_params.m_effective_feerate); + std::optional combined_bump_fee = chain.calculateCombinedBumpFee(outpoints, coin_selection_params.m_effective_feerate); if (!combined_bump_fee.has_value()) { return util::Error{_("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")}; } From 8062c3bdb9dd3062597ed8299e99151b612d32b7 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 26 Sep 2023 13:52:35 -0400 Subject: [PATCH 6/8] util: Add ArgsManager SetConfigFilePath method Needed by multiprocess support code to pass parsed configuration to a spawned process. --- src/common/args.cpp | 7 +++++++ src/common/args.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/common/args.cpp b/src/common/args.cpp index ca04175696..cfaa4de72d 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -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 arg = GetChainArg(); diff --git a/src/common/args.h b/src/common/args.h index ae3ed02bc7..1c5db718f4 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -180,6 +180,7 @@ protected: * 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); /** From 6d43aad742c7ea28303cf2799528188938e7ce32 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Thu, 28 Sep 2023 12:58:51 -0400 Subject: [PATCH 7/8] span: Make Span template deduction guides work in SFINAE context Also add test to make sure this doesn't get broken in the future. This was breaking vector serialization in multiprocess code because template current deduction guides would make it appear like vector could be converted to a span, but then the actual conversion to span would fail. --- src/Makefile.test.include | 1 + src/span.h | 21 +++++++++-- src/test/span_tests.cpp | 73 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/test/span_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index b610dabd07..33ff9688b2 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -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 \ diff --git a/src/span.h b/src/span.h index 7209d21a58..2e8da27cde 100644 --- a/src/span.h +++ b/src/span.h @@ -222,15 +222,32 @@ public: template 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), the template deduction would succeed, but the Span +// 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 +using DataResult = std::remove_pointer_t().data())>; + // Deduction guides for Span // For the pointer/size based and iterator based constructor: template Span(T*, EndOrSize) -> Span; // For the array constructor: template Span(T (&)[N]) -> Span; // For the temporaries/rvalue references constructor, only supporting const output. -template Span(T&&) -> Span, const std::remove_pointer_t().data())>>>; +template Span(T&&) -> Span && !std::is_void_v>, const DataResult>>; // For (lvalue) references, supporting mutable output. -template Span(T&) -> Span().data())>>; +template Span(T&) -> Span>, DataResult>>; /** Pop the last element off a span, and return a reference to that element. */ template diff --git a/src/test/span_tests.cpp b/src/test/span_tests.cpp new file mode 100644 index 0000000000..f6cac10b09 --- /dev/null +++ b/src/test/span_tests.cpp @@ -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 + +#include +#include +#include +#include + +namespace { +struct Ignore +{ + template Ignore(T&&) {} +}; +template +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 was +// not possible, because in libstdc++ vector 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{})); + BOOST_CHECK(!Spannable(std::set{})); + BOOST_CHECK(!Spannable(std::vector{})); + BOOST_CHECK(Spannable(std::array{})); + BOOST_CHECK(Spannable(Span{})); + BOOST_CHECK(Spannable("char array")); + BOOST_CHECK(Spannable(SpannableYes{})); + BOOST_CHECK(!Spannable(SpannableNo{})); +} + +BOOST_AUTO_TEST_SUITE_END() From 3b70f7b6156cb110c47a6e482791cf337bb6ad6d Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 20 Sep 2022 10:53:20 -0400 Subject: [PATCH 8/8] doc: fix broken doc/design/multiprocess.md links after #24352 --- doc/design/multiprocess.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/design/multiprocess.md b/doc/design/multiprocess.md index e6a77dbbc0..45681d12de 100644 --- a/doc/design/multiprocess.md +++ b/doc/design/multiprocess.md @@ -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 @@ -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 @@ -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