diff options
author | Ryan Ofsky <ryan@ofsky.org> | 2023-11-20 15:49:55 -0500 |
---|---|---|
committer | Ryan Ofsky <ryan@ofsky.org> | 2023-11-28 12:35:50 -0500 |
commit | 0cc74fce72e0c79849109ee5d7afe707991b3512 (patch) | |
tree | 234486b6cef3518731c9c283cfac3ec3afd128fd | |
parent | 4aaee239211a5287fbc361c0eb158b105ae8c8db (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.am | 1 | ||||
-rw-r--r-- | src/ipc/capnp/common-types.h | 89 | ||||
-rw-r--r-- | src/test/ipc_test.capnp | 2 | ||||
-rw-r--r-- | src/test/ipc_test.cpp | 6 | ||||
-rw-r--r-- | src/test/ipc_test.h | 1 |
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(); |