aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Ofsky <ryan@ofsky.org>2023-11-20 15:49:55 -0500
committerRyan Ofsky <ryan@ofsky.org>2023-11-28 12:35:50 -0500
commit0cc74fce72e0c79849109ee5d7afe707991b3512 (patch)
tree234486b6cef3518731c9c283cfac3ec3afd128fd
parent4aaee239211a5287fbc361c0eb158b105ae8c8db (diff)
multiprocess: Add type conversion code for serializable types
Allow any C++ object that has Serialize and Unserialize methods and can be serialized to a bitcoin CDataStream to be converted to a capnproto Data field and passed as arguments or return values to capnproto methods using the Data type. Extend IPC unit test to cover this and verify the serialization happens correctly.
-rw-r--r--src/Makefile.am1
-rw-r--r--src/ipc/capnp/common-types.h89
-rw-r--r--src/test/ipc_test.capnp2
-rw-r--r--src/test/ipc_test.cpp6
-rw-r--r--src/test/ipc_test.h1
5 files changed, 98 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 99b2184cf2..746c4df677 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1096,6 +1096,7 @@ ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h)
if BUILD_MULTIPROCESS
LIBBITCOIN_IPC=libbitcoin_ipc.a
libbitcoin_ipc_a_SOURCES = \
+ ipc/capnp/common-types.h \
ipc/capnp/context.h \
ipc/capnp/init-types.h \
ipc/capnp/protocol.cpp \
diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h
new file mode 100644
index 0000000000..d1343c40dd
--- /dev/null
+++ b/src/ipc/capnp/common-types.h
@@ -0,0 +1,89 @@
+// 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.
+
+#ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H
+#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
+
+#include <clientversion.h>
+#include <streams.h>
+
+#include <cstddef>
+#include <mp/proxy-types.h>
+#include <type_traits>
+#include <utility>
+
+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;
+};
+
+//! Use SFINAE to define Unserializeable<T> trait which is true if type T has
+//! an Unserialize(stream) method, false otherwise.
+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;
+};
+} // namespace capnp
+} // namespace ipc
+
+//! Functions to serialize / deserialize common bitcoin types.
+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
+//! 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)
+{
+ DataStream stream;
+ value.Serialize(stream);
+ 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
+//! 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)
+{
+ return read_dest.update([&](auto& value) {
+ if (!input.has()) return;
+ auto data = input.get();
+ SpanReader stream({data.begin(), data.end()});
+ value.Unserialize(stream);
+ });
+}
+} // namespace mp
+
+#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H
diff --git a/src/test/ipc_test.capnp b/src/test/ipc_test.capnp
index f8473a4dec..7b970e2aff 100644
--- a/src/test/ipc_test.capnp
+++ b/src/test/ipc_test.capnp
@@ -9,7 +9,9 @@ $Cxx.namespace("gen");
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("test/ipc_test.h");
+$Proxy.includeTypes("ipc/capnp/common-types.h");
interface FooInterface $Proxy.wrap("FooImplementation") {
add @0 (a :Int32, b :Int32) -> (result :Int32);
+ passOutPoint @1 (arg :Data) -> (result :Data);
}
diff --git a/src/test/ipc_test.cpp b/src/test/ipc_test.cpp
index b84255f68b..f835859705 100644
--- a/src/test/ipc_test.cpp
+++ b/src/test/ipc_test.cpp
@@ -2,8 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <mp/proxy-types.h>
#include <logging.h>
+#include <mp/proxy-types.h>
#include <test/ipc_test.capnp.h>
#include <test/ipc_test.capnp.proxy.h>
#include <test/ipc_test.h>
@@ -51,6 +51,10 @@ void IpcTest()
// Test: make sure arguments were sent and return value is received
BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
+ COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
+ COutPoint txout2{foo->passOutPoint(txout1)};
+ BOOST_CHECK(txout1 == txout2);
+
// Test cleanup: disconnect pipe and join thread
disconnect_client();
thread.join();
diff --git a/src/test/ipc_test.h b/src/test/ipc_test.h
index 61c85b5a47..f100ae8c5d 100644
--- a/src/test/ipc_test.h
+++ b/src/test/ipc_test.h
@@ -11,6 +11,7 @@ class FooImplementation
{
public:
int add(int a, int b) { return a + b; }
+ COutPoint passOutPoint(COutPoint o) { return o; }
};
void IpcTest();