Skip to content

Commit 2f218c6

Browse files
committed
Merge bitcoin/bitcoin#28921: multiprocess: Add basic type conversion hooks
6acec6b multiprocess: Add type conversion code for UniValue types (Ryan Ofsky) 0cc74fc multiprocess: Add type conversion code for serializable types (Ryan Ofsky) 4aaee23 test: add ipc test to test multiprocess type conversion code (Ryan Ofsky) Pull request description: Add type conversion hooks to allow `UniValue` objects, and objects that have `CDataStream` `Serialize` and `Unserialize` methods to be used as arguments and return values in Cap'nProto interface methods. Also add unit test to verify the hooks are working and data can be round-tripped correctly. The non-test code in this PR was previously part of #10102 and has been split off for easier review, but the test code is new. --- This PR is part of the [process separation project](bitcoin/bitcoin#28722). ACKs for top commit: achow101: ACK 6acec6b dergoegge: reACK 6acec6b Tree-SHA512: 5d2cbc5215d488b876d34420adf91205dabf09b736183dcc85aa86255e3804c2bac5bab6792dacd585ef99a1d92cf29c8afb3eb65e4d953abc7ffe41994340c6
2 parents 874c8bd + 6acec6b commit 2f218c6

File tree

9 files changed

+267
-1
lines changed

9 files changed

+267
-1
lines changed

build_msvc/test_bitcoin/test_bitcoin.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111
<ItemGroup>
1212
<ClCompile Include="..\..\src\test\*_properties.cpp" />
13-
<ClCompile Include="..\..\src\test\*_tests.cpp" />
13+
<ClCompile Include="..\..\src\test\*_tests.cpp" Exclude="..\..\src\test\ipc_tests.cpp" />
1414
<ClCompile Include="..\..\src\test\gen\*_gen.cpp" />
1515
<ClCompile Include="..\..\src\test\main.cpp" />
1616
<ClCompile Include="..\..\src\test\util\*.cpp" />

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h)
10901090
if BUILD_MULTIPROCESS
10911091
LIBBITCOIN_IPC=libbitcoin_ipc.a
10921092
libbitcoin_ipc_a_SOURCES = \
1093+
ipc/capnp/common-types.h \
10931094
ipc/capnp/context.h \
10941095
ipc/capnp/init-types.h \
10951096
ipc/capnp/protocol.cpp \

src/Makefile.test.include

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,49 @@ BITCOIN_TEST_SUITE += \
216216
wallet/test/init_test_fixture.h
217217
endif # ENABLE_WALLET
218218

219+
if BUILD_MULTIPROCESS
220+
# Add boost ipc_tests definition to BITCOIN_TESTS
221+
BITCOIN_TESTS += test/ipc_tests.cpp
222+
223+
# Build ipc_test code in a separate library so it can be compiled with custom
224+
# LIBMULTIPROCESS_CFLAGS without those flags affecting other tests
225+
LIBBITCOIN_IPC_TEST=libbitcoin_ipc_test.a
226+
EXTRA_LIBRARIES += $(LIBBITCOIN_IPC_TEST)
227+
libbitcoin_ipc_test_a_SOURCES = \
228+
test/ipc_test.cpp \
229+
test/ipc_test.h
230+
libbitcoin_ipc_test_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
231+
libbitcoin_ipc_test_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS)
232+
233+
# Generate various .c++/.h files from the ipc_test.capnp file
234+
include $(MPGEN_PREFIX)/include/mpgen.mk
235+
EXTRA_DIST += test/ipc_test.capnp
236+
libbitcoin_ipc_test_mpgen_output = \
237+
test/ipc_test.capnp.c++ \
238+
test/ipc_test.capnp.h \
239+
test/ipc_test.capnp.proxy-client.c++ \
240+
test/ipc_test.capnp.proxy-server.c++ \
241+
test/ipc_test.capnp.proxy-types.c++ \
242+
test/ipc_test.capnp.proxy-types.h \
243+
test/ipc_test.capnp.proxy.h
244+
nodist_libbitcoin_ipc_test_a_SOURCES = $(libbitcoin_ipc_test_mpgen_output)
245+
CLEANFILES += $(libbitcoin_ipc_test_mpgen_output)
246+
endif
247+
248+
# Explicitly list dependencies on generated headers as described in
249+
# https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually
250+
test/libbitcoin_ipc_test_a-ipc_test.$(OBJEXT): test/ipc_test.capnp.h
251+
219252
test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
220253
test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS)
221254
test_test_bitcoin_LDADD = $(LIBTEST_UTIL)
222255
if ENABLE_WALLET
223256
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
224257
test_test_bitcoin_CPPFLAGS += $(BDB_CPPFLAGS)
225258
endif
259+
if BUILD_MULTIPROCESS
260+
test_test_bitcoin_LDADD += $(LIBBITCOIN_IPC_TEST) $(LIBMULTIPROCESS_LIBS)
261+
endif
226262

227263
test_test_bitcoin_LDADD += $(LIBBITCOIN_NODE) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
228264
$(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS)

src/ipc/capnp/common-types.h

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

src/test/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# capnp generated files
2+
*.capnp.*

src/test/ipc_test.capnp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
@0xd71b0fc8727fdf83;
6+
7+
using Cxx = import "/capnp/c++.capnp";
8+
$Cxx.namespace("gen");
9+
10+
using Proxy = import "/mp/proxy.capnp";
11+
$Proxy.include("test/ipc_test.h");
12+
$Proxy.includeTypes("ipc/capnp/common-types.h");
13+
14+
interface FooInterface $Proxy.wrap("FooImplementation") {
15+
add @0 (a :Int32, b :Int32) -> (result :Int32);
16+
passOutPoint @1 (arg :Data) -> (result :Data);
17+
passUniValue @2 (arg :Text) -> (result :Text);
18+
}

src/test/ipc_test.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
#include <logging.h>
6+
#include <mp/proxy-types.h>
7+
#include <test/ipc_test.capnp.h>
8+
#include <test/ipc_test.capnp.proxy.h>
9+
#include <test/ipc_test.h>
10+
11+
#include <future>
12+
#include <kj/common.h>
13+
#include <kj/memory.h>
14+
#include <kj/test.h>
15+
16+
#include <boost/test/unit_test.hpp>
17+
18+
//! Unit test that tests execution of IPC calls without actually creating a
19+
//! separate process. This test is primarily intended to verify behavior of type
20+
//! conversion code that converts C++ objects to Cap'n Proto messages and vice
21+
//! versa.
22+
//!
23+
//! The test creates a thread which creates a FooImplementation object (defined
24+
//! in ipc_test.h) and a two-way pipe accepting IPC requests which call methods
25+
//! on the object through FooInterface (defined in ipc_test.capnp).
26+
void IpcTest()
27+
{
28+
// Setup: create FooImplemention object and listen for FooInterface requests
29+
std::promise<std::unique_ptr<mp::ProxyClient<gen::FooInterface>>> foo_promise;
30+
std::function<void()> disconnect_client;
31+
std::thread thread([&]() {
32+
mp::EventLoop loop("IpcTest", [](bool raise, const std::string& log) { LogPrintf("LOG%i: %s\n", raise, log); });
33+
auto pipe = loop.m_io_context.provider->newTwoWayPipe();
34+
35+
auto connection_client = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[0]));
36+
auto foo_client = std::make_unique<mp::ProxyClient<gen::FooInterface>>(
37+
connection_client->m_rpc_system.bootstrap(mp::ServerVatId().vat_id).castAs<gen::FooInterface>(),
38+
connection_client.get(), /* destroy_connection= */ false);
39+
foo_promise.set_value(std::move(foo_client));
40+
disconnect_client = [&] { loop.sync([&] { connection_client.reset(); }); };
41+
42+
auto connection_server = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) {
43+
auto foo_server = kj::heap<mp::ProxyServer<gen::FooInterface>>(std::make_shared<FooImplementation>(), connection);
44+
return capnp::Capability::Client(kj::mv(foo_server));
45+
});
46+
connection_server->onDisconnect([&] { connection_server.reset(); });
47+
loop.loop();
48+
});
49+
std::unique_ptr<mp::ProxyClient<gen::FooInterface>> foo{foo_promise.get_future().get()};
50+
51+
// Test: make sure arguments were sent and return value is received
52+
BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
53+
54+
COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
55+
COutPoint txout2{foo->passOutPoint(txout1)};
56+
BOOST_CHECK(txout1 == txout2);
57+
58+
UniValue uni1{UniValue::VOBJ};
59+
uni1.pushKV("i", 1);
60+
uni1.pushKV("s", "two");
61+
UniValue uni2{foo->passUniValue(uni1)};
62+
BOOST_CHECK_EQUAL(uni1.write(), uni2.write());
63+
64+
// Test cleanup: disconnect pipe and join thread
65+
disconnect_client();
66+
thread.join();
67+
}

src/test/ipc_test.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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_TEST_IPC_TEST_H
6+
#define BITCOIN_TEST_IPC_TEST_H
7+
8+
#include <primitives/transaction.h>
9+
#include <univalue.h>
10+
11+
class FooImplementation
12+
{
13+
public:
14+
int add(int a, int b) { return a + b; }
15+
COutPoint passOutPoint(COutPoint o) { return o; }
16+
UniValue passUniValue(UniValue v) { return v; }
17+
};
18+
19+
void IpcTest();
20+
21+
#endif // BITCOIN_TEST_IPC_TEST_H

src/test/ipc_tests.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
#include <test/ipc_test.h>
6+
#include <boost/test/unit_test.hpp>
7+
8+
BOOST_AUTO_TEST_SUITE(ipc_tests)
9+
BOOST_AUTO_TEST_CASE(ipc_tests)
10+
{
11+
IpcTest();
12+
}
13+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)