Skip to content

Commit 0cc74fc

Browse files
committed
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.
1 parent 4aaee23 commit 0cc74fc

File tree

5 files changed

+98
-1
lines changed

5 files changed

+98
-1
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,7 @@ ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h)
10961096
if BUILD_MULTIPROCESS
10971097
LIBBITCOIN_IPC=libbitcoin_ipc.a
10981098
libbitcoin_ipc_a_SOURCES = \
1099+
ipc/capnp/common-types.h \
10991100
ipc/capnp/context.h \
11001101
ipc/capnp/init-types.h \
11011102
ipc/capnp/protocol.cpp \

src/ipc/capnp/common-types.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
#ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H
6+
#define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
7+
8+
#include <clientversion.h>
9+
#include <streams.h>
10+
11+
#include <cstddef>
12+
#include <mp/proxy-types.h>
13+
#include <type_traits>
14+
#include <utility>
15+
16+
namespace ipc {
17+
namespace capnp {
18+
//! Use SFINAE to define Serializeable<T> trait which is true if type T has a
19+
//! Serialize(stream) method, false otherwise.
20+
template <typename T>
21+
struct Serializable {
22+
private:
23+
template <typename C>
24+
static std::true_type test(decltype(std::declval<C>().Serialize(std::declval<std::nullptr_t&>()))*);
25+
template <typename>
26+
static std::false_type test(...);
27+
28+
public:
29+
static constexpr bool value = decltype(test<T>(nullptr))::value;
30+
};
31+
32+
//! Use SFINAE to define Unserializeable<T> trait which is true if type T has
33+
//! an Unserialize(stream) method, false otherwise.
34+
template <typename T>
35+
struct Unserializable {
36+
private:
37+
template <typename C>
38+
static std::true_type test(decltype(std::declval<C>().Unserialize(std::declval<std::nullptr_t&>()))*);
39+
template <typename>
40+
static std::false_type test(...);
41+
42+
public:
43+
static constexpr bool value = decltype(test<T>(nullptr))::value;
44+
};
45+
} // namespace capnp
46+
} // namespace ipc
47+
48+
//! Functions to serialize / deserialize common bitcoin types.
49+
namespace mp {
50+
//! Overload multiprocess library's CustomBuildField hook to allow any
51+
//! serializable object to be stored in a capnproto Data field or passed to a
52+
//! canproto interface. Use Priority<1> so this hook has medium priority, and
53+
//! higher priority hooks could take precedence over this one.
54+
template <typename LocalType, typename Value, typename Output>
55+
void CustomBuildField(
56+
TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output,
57+
// Enable if serializeable and if LocalType is not cv or reference
58+
// qualified. If LocalType is cv or reference qualified, it is important to
59+
// fall back to lower-priority Priority<0> implementation of this function
60+
// that strips cv references, to prevent this CustomBuildField overload from
61+
// taking precedence over more narrow overloads for specific LocalTypes.
62+
std::enable_if_t<ipc::capnp::Serializable<LocalType>::value &&
63+
std::is_same_v<LocalType, std::remove_cv_t<std::remove_reference_t<LocalType>>>>* enable = nullptr)
64+
{
65+
DataStream stream;
66+
value.Serialize(stream);
67+
auto result = output.init(stream.size());
68+
memcpy(result.begin(), stream.data(), stream.size());
69+
}
70+
71+
//! Overload multiprocess library's CustomReadField hook to allow any object
72+
//! with an Unserialize method to be read from a capnproto Data field or
73+
//! returned from canproto interface. Use Priority<1> so this hook has medium
74+
//! priority, and higher priority hooks could take precedence over this one.
75+
template <typename LocalType, typename Input, typename ReadDest>
76+
decltype(auto)
77+
CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest,
78+
std::enable_if_t<ipc::capnp::Unserializable<LocalType>::value>* enable = nullptr)
79+
{
80+
return read_dest.update([&](auto& value) {
81+
if (!input.has()) return;
82+
auto data = input.get();
83+
SpanReader stream({data.begin(), data.end()});
84+
value.Unserialize(stream);
85+
});
86+
}
87+
} // namespace mp
88+
89+
#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H

src/test/ipc_test.capnp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ $Cxx.namespace("gen");
99

1010
using Proxy = import "/mp/proxy.capnp";
1111
$Proxy.include("test/ipc_test.h");
12+
$Proxy.includeTypes("ipc/capnp/common-types.h");
1213

1314
interface FooInterface $Proxy.wrap("FooImplementation") {
1415
add @0 (a :Int32, b :Int32) -> (result :Int32);
16+
passOutPoint @1 (arg :Data) -> (result :Data);
1517
}

src/test/ipc_test.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5-
#include <mp/proxy-types.h>
65
#include <logging.h>
6+
#include <mp/proxy-types.h>
77
#include <test/ipc_test.capnp.h>
88
#include <test/ipc_test.capnp.proxy.h>
99
#include <test/ipc_test.h>
@@ -51,6 +51,10 @@ void IpcTest()
5151
// Test: make sure arguments were sent and return value is received
5252
BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
5353

54+
COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
55+
COutPoint txout2{foo->passOutPoint(txout1)};
56+
BOOST_CHECK(txout1 == txout2);
57+
5458
// Test cleanup: disconnect pipe and join thread
5559
disconnect_client();
5660
thread.join();

src/test/ipc_test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class FooImplementation
1111
{
1212
public:
1313
int add(int a, int b) { return a + b; }
14+
COutPoint passOutPoint(COutPoint o) { return o; }
1415
};
1516

1617
void IpcTest();

0 commit comments

Comments
 (0)