aboutsummaryrefslogtreecommitdiff
path: root/src/ipc
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipc')
-rw-r--r--src/ipc/CMakeLists.txt7
-rw-r--r--src/ipc/capnp/common-types.h139
-rw-r--r--src/ipc/capnp/common.capnp16
-rw-r--r--src/ipc/capnp/init-types.h1
-rw-r--r--src/ipc/capnp/init.capnp3
-rw-r--r--src/ipc/capnp/mining-types.h26
-rw-r--r--src/ipc/capnp/mining.capnp52
-rw-r--r--src/ipc/capnp/mining.cpp47
-rw-r--r--src/ipc/capnp/protocol.cpp13
-rw-r--r--src/ipc/interfaces.cpp30
-rw-r--r--src/ipc/process.cpp96
-rw-r--r--src/ipc/process.h10
-rw-r--r--src/ipc/protocol.h28
13 files changed, 423 insertions, 45 deletions
diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt
index 94b1ceb54e..904d72f56e 100644
--- a/src/ipc/CMakeLists.txt
+++ b/src/ipc/CMakeLists.txt
@@ -3,16 +3,21 @@
# file COPYING or https://opensource.org/license/mit/.
add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL
+ capnp/mining.cpp
capnp/protocol.cpp
interfaces.cpp
process.cpp
)
target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR}
- capnp/echo.capnp capnp/init.capnp
+ capnp/common.capnp
+ capnp/echo.capnp
+ capnp/init.capnp
+ capnp/mining.capnp
)
target_link_libraries(bitcoin_ipc
PRIVATE
core_interface
+ univalue
)
diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h
index 39e368491b..51af6a5f0a 100644
--- a/src/ipc/capnp/common-types.h
+++ b/src/ipc/capnp/common-types.h
@@ -6,6 +6,9 @@
#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
#include <clientversion.h>
+#include <interfaces/types.h>
+#include <primitives/transaction.h>
+#include <serialize.h>
#include <streams.h>
#include <univalue.h>
@@ -16,33 +19,24 @@
namespace ipc {
namespace capnp {
-//! Use SFINAE to define Serializeable<T> trait which is true if type T has a
-//! Serialize(stream) method, false otherwise.
-template <typename T>
-struct Serializable {
-private:
- template <typename C>
- static std::true_type test(decltype(std::declval<C>().Serialize(std::declval<std::nullptr_t&>()))*);
- template <typename>
- static std::false_type test(...);
-
-public:
- static constexpr bool value = decltype(test<T>(nullptr))::value;
-};
+//! Construct a ParamStream wrapping a data stream with serialization parameters
+//! needed to pass transaction 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
+//! with the specified parameters produces equivalent objects. It's also
+//! harmless to specify serialization parameters here that are not used.
+template <typename S>
+auto Wrap(S& s)
+{
+ return ParamsStream{s, TX_WITH_WITNESS};
+}
-//! Use SFINAE to define Unserializeable<T> trait which is true if type T has
-//! an Unserialize(stream) method, false otherwise.
+//! Detect if type has a deserialize_type constructor, which is
+//! used to deserialize types like CTransaction that can't be unserialized into
+//! existing objects because they are immutable.
template <typename T>
-struct Unserializable {
-private:
- template <typename C>
- static std::true_type test(decltype(std::declval<C>().Unserialize(std::declval<std::nullptr_t&>()))*);
- template <typename>
- static std::false_type test(...);
-
-public:
- static constexpr bool value = decltype(test<T>(nullptr))::value;
-};
+concept Deserializable = std::is_constructible_v<T, ::deserialize_type, ::DataStream&>;
} // namespace capnp
} // namespace ipc
@@ -50,42 +44,78 @@ public:
namespace mp {
//! Overload multiprocess library's CustomBuildField hook to allow any
//! serializable object to be stored in a capnproto Data field or passed to a
-//! canproto interface. Use Priority<1> so this hook has medium priority, and
+//! capnproto interface. Use Priority<1> so this hook has medium priority, and
//! higher priority hooks could take precedence over this one.
template <typename LocalType, typename Value, typename Output>
-void CustomBuildField(
- TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output,
- // Enable if serializeable and if LocalType is not cv or reference
- // qualified. If LocalType is cv or reference qualified, it is important to
- // fall back to lower-priority Priority<0> implementation of this function
- // that strips cv references, to prevent this CustomBuildField overload from
- // taking precedence over more narrow overloads for specific LocalTypes.
- std::enable_if_t<ipc::capnp::Serializable<LocalType>::value &&
- std::is_same_v<LocalType, std::remove_cv_t<std::remove_reference_t<LocalType>>>>* enable = nullptr)
+void CustomBuildField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output)
+// Enable if serializeable and if LocalType is not cv or reference qualified. If
+// LocalType is cv or reference qualified, it is important to fall back to
+// lower-priority Priority<0> implementation of this function that strips cv
+// references, to prevent this CustomBuildField overload from taking precedence
+// over more narrow overloads for specific LocalTypes.
+requires Serializable<LocalType, DataStream> && std::is_same_v<LocalType, std::remove_cv_t<std::remove_reference_t<LocalType>>>
{
DataStream stream;
- value.Serialize(stream);
+ auto wrapper{ipc::capnp::Wrap(stream)};
+ value.Serialize(wrapper);
auto result = output.init(stream.size());
memcpy(result.begin(), stream.data(), stream.size());
}
//! Overload multiprocess library's CustomReadField hook to allow any object
//! with an Unserialize method to be read from a capnproto Data field or
-//! returned from canproto interface. Use Priority<1> so this hook has medium
+//! returned from capnproto interface. Use Priority<1> so this hook has medium
//! priority, and higher priority hooks could take precedence over this one.
template <typename LocalType, typename Input, typename ReadDest>
-decltype(auto)
-CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest,
- std::enable_if_t<ipc::capnp::Unserializable<LocalType>::value>* enable = nullptr)
+decltype(auto) CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
+requires Unserializable<LocalType, DataStream> && (!ipc::capnp::Deserializable<LocalType>)
{
return read_dest.update([&](auto& value) {
if (!input.has()) return;
auto data = input.get();
SpanReader stream({data.begin(), data.end()});
- value.Unserialize(stream);
+ auto wrapper{ipc::capnp::Wrap(stream)};
+ value.Unserialize(wrapper);
});
}
+//! Overload multiprocess library's CustomReadField hook to allow any object
+//! with a deserialize constructor to be read from a capnproto Data field or
+//! returned from capnproto interface. Use Priority<1> so this hook has medium
+//! priority, and higher priority hooks could take precedence over this one.
+template <typename LocalType, typename Input, typename ReadDest>
+decltype(auto) CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
+requires ipc::capnp::Deserializable<LocalType>
+{
+ assert(input.has());
+ auto data = input.get();
+ SpanReader stream({data.begin(), data.end()});
+ auto wrapper{ipc::capnp::Wrap(stream)};
+ return read_dest.construct(::deserialize, wrapper);
+}
+
+//! Overload CustomBuildField and CustomReadField to serialize std::chrono
+//! parameters and return values as numbers.
+template <class Rep, class Period, typename Value, typename Output>
+void CustomBuildField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context, Value&& value,
+ Output&& output)
+{
+ static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(),
+ "capnp type does not have enough range to hold lowest std::chrono::duration value");
+ static_assert(std::numeric_limits<decltype(output.get())>::max() >= std::numeric_limits<Rep>::max(),
+ "capnp type does not have enough range to hold highest std::chrono::duration value");
+ output.set(value.count());
+}
+
+template <class Rep, class Period, typename Input, typename ReadDest>
+decltype(auto) CustomReadField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context,
+ Input&& input, ReadDest&& read_dest)
+{
+ return read_dest.construct(input.get());
+}
+
+//! Overload CustomBuildField and CustomReadField to serialize UniValue
+//! parameters and return values as JSON strings.
template <typename Value, typename Output>
void CustomBuildField(TypeList<UniValue>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
@@ -103,6 +133,33 @@ decltype(auto) CustomReadField(TypeList<UniValue>, Priority<1>, InvokeContext& i
value.read(std::string_view{data.begin(), data.size()});
});
}
+
+//! 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
+//! bytes.
+//!
+//! Note: it might make sense to move this function into libmultiprocess, since
+//! it is fairly generic. However this would require decreasing its priority so
+//! it can be overridden, which would require more changes inside
+//! libmultiprocess to avoid conflicting with the Priority<1> CustomBuildField
+//! function it already provides for std::vector. Also, it might make sense to
+//! provide a CustomReadField counterpart to this function, which could be
+//! called to read C++ types that can be constructed from spans of bytes from
+//! ::capnp::Data fields. But so far there hasn't been a need for this.
+template <typename LocalType, typename Value, typename Output>
+void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output)
+requires
+ (std::is_same_v<decltype(output.get()), ::capnp::Data::Builder>) &&
+ (std::convertible_to<Value, std::span<const std::byte>> ||
+ std::convertible_to<Value, std::span<const char>> ||
+ std::convertible_to<Value, std::span<const unsigned char>> ||
+ std::convertible_to<Value, std::span<const signed char>>)
+{
+ auto data = std::span{value};
+ auto result = output.init(data.size());
+ memcpy(result.begin(), data.data(), data.size());
+}
} // namespace mp
#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H
diff --git a/src/ipc/capnp/common.capnp b/src/ipc/capnp/common.capnp
new file mode 100644
index 0000000000..b3359f3f07
--- /dev/null
+++ b/src/ipc/capnp/common.capnp
@@ -0,0 +1,16 @@
+# 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.
+
+@0xcd2c6232cb484a28;
+
+using Cxx = import "/capnp/c++.capnp";
+$Cxx.namespace("ipc::capnp::messages");
+
+using Proxy = import "/mp/proxy.capnp";
+$Proxy.includeTypes("ipc/capnp/common-types.h");
+
+struct BlockRef $Proxy.wrap("interfaces::BlockRef") {
+ hash @0 :Data;
+ height @1 :Int32;
+}
diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h
index 42031441b5..c3ddca27c0 100644
--- a/src/ipc/capnp/init-types.h
+++ b/src/ipc/capnp/init-types.h
@@ -6,5 +6,6 @@
#define BITCOIN_IPC_CAPNP_INIT_TYPES_H
#include <ipc/capnp/echo.capnp.proxy-types.h>
+#include <ipc/capnp/mining.capnp.proxy-types.h>
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H
diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp
index e6d358c665..1001ee5336 100644
--- a/src/ipc/capnp/init.capnp
+++ b/src/ipc/capnp/init.capnp
@@ -10,11 +10,14 @@ $Cxx.namespace("ipc::capnp::messages");
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("interfaces/echo.h");
$Proxy.include("interfaces/init.h");
+$Proxy.include("interfaces/mining.h");
$Proxy.includeTypes("ipc/capnp/init-types.h");
using Echo = import "echo.capnp";
+using Mining = import "mining.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);
}
diff --git a/src/ipc/capnp/mining-types.h b/src/ipc/capnp/mining-types.h
new file mode 100644
index 0000000000..2e60b43fcf
--- /dev/null
+++ b/src/ipc/capnp/mining-types.h
@@ -0,0 +1,26 @@
+// 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_MINING_TYPES_H
+#define BITCOIN_IPC_CAPNP_MINING_TYPES_H
+
+#include <interfaces/mining.h>
+#include <ipc/capnp/common.capnp.proxy-types.h>
+#include <ipc/capnp/common-types.h>
+#include <ipc/capnp/mining.capnp.proxy.h>
+#include <node/miner.h>
+#include <node/types.h>
+#include <validation.h>
+
+namespace mp {
+// Custom serialization for BlockValidationState.
+void CustomBuildMessage(InvokeContext& invoke_context,
+ const BlockValidationState& src,
+ ipc::capnp::messages::BlockValidationState::Builder&& builder);
+void CustomReadMessage(InvokeContext& invoke_context,
+ const ipc::capnp::messages::BlockValidationState::Reader& reader,
+ BlockValidationState& dest);
+} // namespace mp
+
+#endif // BITCOIN_IPC_CAPNP_MINING_TYPES_H
diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp
new file mode 100644
index 0000000000..5e0216acea
--- /dev/null
+++ b/src/ipc/capnp/mining.capnp
@@ -0,0 +1,52 @@
+# 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.
+
+@0xc77d03df6a41b505;
+
+using Cxx = import "/capnp/c++.capnp";
+$Cxx.namespace("ipc::capnp::messages");
+
+using Common = import "common.capnp";
+using Proxy = import "/mp/proxy.capnp";
+$Proxy.include("interfaces/mining.h");
+$Proxy.includeTypes("ipc/capnp/mining-types.h");
+
+interface Mining $Proxy.wrap("interfaces::Mining") {
+ isTestChain @0 (context :Proxy.Context) -> (result: Bool);
+ isInitialBlockDownload @1 (context :Proxy.Context) -> (result: Bool);
+ getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
+ waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
+ createNewBlock @4 (scriptPubKey: Data, options: BlockCreateOptions) -> (result: BlockTemplate);
+ processNewBlock @5 (context :Proxy.Context, block: Data) -> (newBlock: Bool, result: Bool);
+ getTransactionsUpdated @6 (context :Proxy.Context) -> (result: UInt32);
+ testBlockValidity @7 (context :Proxy.Context, block: Data, checkMerkleRoot: Bool) -> (state: BlockValidationState, result: Bool);
+}
+
+interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
+ getBlockHeader @0 (context: Proxy.Context) -> (result: Data);
+ getBlock @1 (context: Proxy.Context) -> (result: Data);
+ getTxFees @2 (context: Proxy.Context) -> (result: List(Int64));
+ getTxSigops @3 (context: Proxy.Context) -> (result: List(Int64));
+ getCoinbaseTx @4 (context: Proxy.Context) -> (result: Data);
+ getCoinbaseCommitment @5 (context: Proxy.Context) -> (result: Data);
+ getWitnessCommitmentIndex @6 (context: Proxy.Context) -> (result: Int32);
+ getCoinbaseMerklePath @7 (context: Proxy.Context) -> (result: List(Data));
+ submitSolution@8 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
+}
+
+struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
+ useMempool @0 :Bool $Proxy.name("use_mempool");
+ coinbaseMaxAdditionalWeight @1 :UInt64 $Proxy.name("coinbase_max_additional_weight");
+ coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
+}
+
+# Note: serialization of the BlockValidationState C++ type is somewhat fragile
+# and using the struct can be awkward. It would be good if testBlockValidity
+# method were changed to return validity information in a simpler format.
+struct BlockValidationState {
+ mode @0 :Int32;
+ result @1 :Int32;
+ rejectReason @2 :Text;
+ debugMessage @3 :Text;
+}
diff --git a/src/ipc/capnp/mining.cpp b/src/ipc/capnp/mining.cpp
new file mode 100644
index 0000000000..0f9533c1c7
--- /dev/null
+++ b/src/ipc/capnp/mining.cpp
@@ -0,0 +1,47 @@
+// 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 <ipc/capnp/mining-types.h>
+#include <ipc/capnp/mining.capnp.proxy-types.h>
+
+#include <mp/proxy-types.h>
+
+namespace mp {
+void CustomBuildMessage(InvokeContext& invoke_context,
+ const BlockValidationState& src,
+ ipc::capnp::messages::BlockValidationState::Builder&& builder)
+{
+ if (src.IsValid()) {
+ builder.setMode(0);
+ } else if (src.IsInvalid()) {
+ builder.setMode(1);
+ } else if (src.IsError()) {
+ builder.setMode(2);
+ } else {
+ assert(false);
+ }
+ builder.setResult(static_cast<int>(src.GetResult()));
+ builder.setRejectReason(src.GetRejectReason());
+ builder.setDebugMessage(src.GetDebugMessage());
+}
+
+void CustomReadMessage(InvokeContext& invoke_context,
+ const ipc::capnp::messages::BlockValidationState::Reader& reader,
+ BlockValidationState& dest)
+{
+ if (reader.getMode() == 0) {
+ assert(reader.getResult() == 0);
+ assert(reader.getRejectReason().size() == 0);
+ assert(reader.getDebugMessage().size() == 0);
+ } else if (reader.getMode() == 1) {
+ dest.Invalid(static_cast<BlockValidationResult>(reader.getResult()), reader.getRejectReason(), reader.getDebugMessage());
+ } else if (reader.getMode() == 2) {
+ assert(reader.getResult() == 0);
+ dest.Error(reader.getRejectReason());
+ assert(reader.getDebugMessage().size() == 0);
+ } else {
+ assert(false);
+ }
+}
+} // namespace mp
diff --git a/src/ipc/capnp/protocol.cpp b/src/ipc/capnp/protocol.cpp
index 73276d6d90..4b67a5bd1e 100644
--- a/src/ipc/capnp/protocol.cpp
+++ b/src/ipc/capnp/protocol.cpp
@@ -23,6 +23,8 @@
#include <mutex>
#include <optional>
#include <string>
+#include <sys/socket.h>
+#include <system_error>
#include <thread>
namespace ipc {
@@ -51,11 +53,20 @@ public:
startLoop(exe_name);
return mp::ConnectStream<messages::Init>(*m_loop, fd);
}
- void serve(int fd, const char* exe_name, interfaces::Init& init) override
+ void listen(int listen_fd, const char* exe_name, interfaces::Init& init) override
+ {
+ startLoop(exe_name);
+ if (::listen(listen_fd, /*backlog=*/5) != 0) {
+ throw std::system_error(errno, std::system_category());
+ }
+ mp::ListenConnections<messages::Init>(*m_loop, listen_fd, init);
+ }
+ void serve(int fd, const char* exe_name, interfaces::Init& init, const std::function<void()>& ready_fn = {}) override
{
assert(!m_loop);
mp::g_thread_context.thread_name = mp::ThreadName(exe_name);
m_loop.emplace(exe_name, &IpcLogFn, &m_context);
+ if (ready_fn) ready_fn();
mp::ServeStream<messages::Init>(*m_loop, fd, init);
m_loop->loop();
m_loop.reset();
diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp
index b409443f64..33555f05d4 100644
--- a/src/ipc/interfaces.cpp
+++ b/src/ipc/interfaces.cpp
@@ -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 <common/args.h>
#include <common/system.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
@@ -56,6 +57,35 @@ public:
exit_status = EXIT_SUCCESS;
return true;
}
+ std::unique_ptr<interfaces::Init> connectAddress(std::string& address) override
+ {
+ if (address.empty() || address == "0") return nullptr;
+ int fd;
+ if (address == "auto") {
+ // Treat "auto" the same as "unix" except don't treat it an as error
+ // if the connection is not accepted. Just return null so the caller
+ // can work offline without a connection, or spawn a new
+ // bitcoin-node process and connect to it.
+ address = "unix";
+ try {
+ fd = m_process->connect(gArgs.GetDataDirNet(), "bitcoin-node", address);
+ } catch (const std::system_error& e) {
+ // If connection type is auto and socket path isn't accepting connections, or doesn't exist, catch the error and return null;
+ if (e.code() == std::errc::connection_refused || e.code() == std::errc::no_such_file_or_directory) {
+ return nullptr;
+ }
+ throw;
+ }
+ } else {
+ fd = m_process->connect(gArgs.GetDataDirNet(), "bitcoin-node", address);
+ }
+ return m_protocol->connect(fd, m_exe_name);
+ }
+ void listenAddress(std::string& address) override
+ {
+ int fd = m_process->bind(gArgs.GetDataDirNet(), m_exe_name, address);
+ m_protocol->listen(fd, m_exe_name, m_init);
+ }
void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) override
{
m_protocol->addCleanup(type, iface, std::move(cleanup));
diff --git a/src/ipc/process.cpp b/src/ipc/process.cpp
index 9657dcd092..432c365d8f 100644
--- a/src/ipc/process.cpp
+++ b/src/ipc/process.cpp
@@ -4,22 +4,28 @@
#include <ipc/process.h>
#include <ipc/protocol.h>
+#include <logging.h>
#include <mp/util.h>
#include <tinyformat.h>
#include <util/fs.h>
#include <util/strencodings.h>
+#include <util/syserror.h>
#include <cstdint>
#include <cstdlib>
+#include <errno.h>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string.h>
-#include <system_error>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#include <utility>
#include <vector>
+using util::RemovePrefixView;
+
namespace ipc {
namespace {
class ProcessImpl : public Process
@@ -54,7 +60,95 @@ public:
}
return true;
}
+ int connect(const fs::path& data_dir,
+ const std::string& dest_exe_name,
+ std::string& address) override;
+ int bind(const fs::path& data_dir, const std::string& exe_name, std::string& address) override;
};
+
+static bool ParseAddress(std::string& address,
+ const fs::path& data_dir,
+ const std::string& dest_exe_name,
+ struct sockaddr_un& addr,
+ std::string& error)
+{
+ if (address.compare(0, 4, "unix") == 0 && (address.size() == 4 || address[4] == ':')) {
+ fs::path path;
+ if (address.size() <= 5) {
+ path = data_dir / fs::PathFromString(strprintf("%s.sock", RemovePrefixView(dest_exe_name, "bitcoin-")));
+ } else {
+ path = data_dir / fs::PathFromString(address.substr(5));
+ }
+ std::string path_str = fs::PathToString(path);
+ address = strprintf("unix:%s", path_str);
+ if (path_str.size() >= sizeof(addr.sun_path)) {
+ error = strprintf("Unix address path %s exceeded maximum socket path length", fs::quoted(fs::PathToString(path)));
+ return false;
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path_str.c_str(), sizeof(addr.sun_path)-1);
+ return true;
+ }
+
+ error = strprintf("Unrecognized address '%s'", address);
+ return false;
+}
+
+int ProcessImpl::connect(const fs::path& data_dir,
+ const std::string& dest_exe_name,
+ std::string& address)
+{
+ struct sockaddr_un addr;
+ std::string error;
+ if (!ParseAddress(address, data_dir, dest_exe_name, addr, error)) {
+ throw std::invalid_argument(error);
+ }
+
+ int fd;
+ if ((fd = ::socket(addr.sun_family, SOCK_STREAM, 0)) == -1) {
+ throw std::system_error(errno, std::system_category());
+ }
+ if (::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
+ return fd;
+ }
+ int connect_error = errno;
+ if (::close(fd) != 0) {
+ LogPrintf("Error closing file descriptor %i '%s': %s\n", fd, address, SysErrorString(errno));
+ }
+ throw std::system_error(connect_error, std::system_category());
+}
+
+int ProcessImpl::bind(const fs::path& data_dir, const std::string& exe_name, std::string& address)
+{
+ struct sockaddr_un addr;
+ std::string error;
+ if (!ParseAddress(address, data_dir, exe_name, addr, error)) {
+ throw std::invalid_argument(error);
+ }
+
+ if (addr.sun_family == AF_UNIX) {
+ fs::path path = addr.sun_path;
+ if (path.has_parent_path()) fs::create_directories(path.parent_path());
+ if (fs::symlink_status(path).type() == fs::file_type::socket) {
+ fs::remove(path);
+ }
+ }
+
+ int fd;
+ if ((fd = ::socket(addr.sun_family, SOCK_STREAM, 0)) == -1) {
+ throw std::system_error(errno, std::system_category());
+ }
+
+ if (::bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
+ return fd;
+ }
+ int bind_error = errno;
+ if (::close(fd) != 0) {
+ LogPrintf("Error closing file descriptor %i: %s\n", fd, SysErrorString(errno));
+ }
+ throw std::system_error(bind_error, std::system_category());
+}
} // namespace
std::unique_ptr<Process> MakeProcess() { return std::make_unique<ProcessImpl>(); }
diff --git a/src/ipc/process.h b/src/ipc/process.h
index 40f2d2acf6..2ed8b73fab 100644
--- a/src/ipc/process.h
+++ b/src/ipc/process.h
@@ -34,6 +34,16 @@ public:
//! process. If so, return true and a file descriptor for communicating
//! with the parent process.
virtual bool checkSpawned(int argc, char* argv[], int& fd) = 0;
+
+ //! Canonicalize and connect to address, returning socket descriptor.
+ virtual int connect(const fs::path& data_dir,
+ const std::string& dest_exe_name,
+ std::string& address) = 0;
+
+ //! Create listening socket, bind and canonicalize address, and return socket descriptor.
+ virtual int bind(const fs::path& data_dir,
+ const std::string& exe_name,
+ std::string& address) = 0;
};
//! Constructor for Process interface. Implementation will vary depending on
diff --git a/src/ipc/protocol.h b/src/ipc/protocol.h
index 4cd892e411..b2ebf99e8c 100644
--- a/src/ipc/protocol.h
+++ b/src/ipc/protocol.h
@@ -25,12 +25,38 @@ public:
//! Return Init interface that forwards requests over given socket descriptor.
//! Socket communication is handled on a background thread.
+ //!
+ //! @note It could be potentially useful in the future to add
+ //! std::function<void()> on_disconnect callback argument here. But there
+ //! isn't an immediate need, because the protocol implementation can clean
+ //! up its own state (calling ProxyServer destructors, etc) on disconnect,
+ //! and any client calls will just throw ipc::Exception errors after a
+ //! disconnect.
virtual std::unique_ptr<interfaces::Init> connect(int fd, const char* exe_name) = 0;
+ //! Listen for connections on provided socket descriptor, accept them, and
+ //! handle requests on accepted connections. This method doesn't block, and
+ //! performs I/O on a background thread.
+ virtual void listen(int listen_fd, const char* exe_name, interfaces::Init& init) = 0;
+
//! Handle requests on provided socket descriptor, forwarding them to the
//! provided Init interface. Socket communication is handled on the
//! current thread, and this call blocks until the socket is closed.
- virtual void serve(int fd, const char* exe_name, interfaces::Init& init) = 0;
+ //!
+ //! @note: If this method is called, it needs be called before connect() or
+ //! listen() methods, because for ease of implementation it's inflexible and
+ //! always runs the event loop in the foreground thread. It can share its
+ //! event loop with the other methods but can't share an event loop that was
+ //! created by them. This isn't really a problem because serve() is only
+ //! called by spawned child processes that call it immediately to
+ //! communicate back with parent processes.
+ //
+ //! The optional `ready_fn` callback will be called after the event loop is
+ //! created but before it is started. This can be useful in tests to trigger
+ //! client connections from another thread as soon as the event loop is
+ //! available, but should not be neccessary in normal code which starts
+ //! clients and servers independently.
+ virtual void serve(int fd, const char* exe_name, interfaces::Init& init, const std::function<void()>& ready_fn = {}) = 0;
//! Add cleanup callback to interface that will run when the interface is
//! deleted.