/tmp/bitcoin/src/ipc/test/ipc_test.cpp
Line | Count | Source |
1 | | // Copyright (c) 2023-present 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 <interfaces/init.h> |
6 | | #include <ipc/capnp/protocol.h> |
7 | | #include <ipc/process.h> |
8 | | #include <ipc/protocol.h> |
9 | | #include <logging.h> |
10 | | #include <mp/proxy-types.h> |
11 | | #include <ipc/capnp/mining.capnp.h> |
12 | | #include <ipc/test/ipc_test.capnp.h> |
13 | | #include <ipc/test/ipc_test.capnp.proxy.h> |
14 | | #include <ipc/test/ipc_test.h> |
15 | | #include <tinyformat.h> |
16 | | #include <validation.h> |
17 | | |
18 | | #include <future> |
19 | | #include <thread> |
20 | | #include <kj/common.h> |
21 | | #include <kj/memory.h> |
22 | | #include <kj/test.h> |
23 | | #include <stdexcept> |
24 | | |
25 | | #include <boost/test/unit_test.hpp> |
26 | | |
27 | | static_assert(ipc::capnp::messages::MAX_MONEY == MAX_MONEY); |
28 | | static_assert(ipc::capnp::messages::MAX_DOUBLE == std::numeric_limits<double>::max()); |
29 | | static_assert(ipc::capnp::messages::DEFAULT_BLOCK_RESERVED_WEIGHT == DEFAULT_BLOCK_RESERVED_WEIGHT); |
30 | | static_assert(ipc::capnp::messages::DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS == DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS); |
31 | | |
32 | | //! Remote init class. |
33 | | class TestInit : public interfaces::Init |
34 | | { |
35 | | public: |
36 | 6 | std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } |
37 | | }; |
38 | | |
39 | | //! Generate a temporary path with temp_directory_path and mkstemp |
40 | | static std::string TempPath(std::string_view pattern) |
41 | 2 | { |
42 | 2 | std::string temp{fs::PathToString(fs::path{fs::temp_directory_path()} / fs::PathFromString(std::string{pattern}))}; |
43 | 2 | temp.push_back('\0'); |
44 | 2 | int fd{mkstemp(temp.data())}; |
45 | 2 | BOOST_CHECK_GE(fd, 0); |
46 | 2 | BOOST_CHECK_EQUAL(close(fd), 0); |
47 | 2 | temp.resize(temp.size() - 1); |
48 | 2 | fs::remove(fs::PathFromString(temp)); |
49 | 2 | return temp; |
50 | 2 | } |
51 | | |
52 | | //! Unit test that tests execution of IPC calls without actually creating a |
53 | | //! separate process. This test is primarily intended to verify behavior of type |
54 | | //! conversion code that converts C++ objects to Cap'n Proto messages and vice |
55 | | //! versa. |
56 | | //! |
57 | | //! The test creates a thread which creates a FooImplementation object (defined |
58 | | //! in ipc_test.h) and a two-way pipe accepting IPC requests which call methods |
59 | | //! on the object through FooInterface (defined in ipc_test.capnp). |
60 | | void IpcPipeTest() |
61 | 1 | { |
62 | | // Setup: create FooImplementation object and listen for FooInterface requests |
63 | 1 | std::promise<std::unique_ptr<mp::ProxyClient<gen::FooInterface>>> foo_promise; |
64 | 1 | std::thread thread([&]() { |
65 | 53 | mp::EventLoop loop("IpcPipeTest", [](bool raise, const std::string& log) { LogInfo("LOG%i: %s", raise, log); }); |
66 | 1 | auto pipe = loop.m_io_context.provider->newTwoWayPipe(); |
67 | | |
68 | 1 | auto connection_client = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[0])); |
69 | 1 | auto foo_client = std::make_unique<mp::ProxyClient<gen::FooInterface>>( |
70 | 1 | connection_client->m_rpc_system->bootstrap(mp::ServerVatId().vat_id).castAs<gen::FooInterface>(), |
71 | 1 | connection_client.get(), /* destroy_connection= */ true); |
72 | 1 | (void)connection_client.release(); |
73 | 1 | foo_promise.set_value(std::move(foo_client)); |
74 | | |
75 | 1 | auto connection_server = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) { |
76 | 1 | auto foo_server = kj::heap<mp::ProxyServer<gen::FooInterface>>(std::make_shared<FooImplementation>(), connection); |
77 | 1 | return capnp::Capability::Client(kj::mv(foo_server)); |
78 | 1 | }); |
79 | 1 | connection_server->onDisconnect([&] { connection_server.reset(); }); |
80 | 1 | loop.loop(); |
81 | 1 | }); |
82 | 1 | std::unique_ptr<mp::ProxyClient<gen::FooInterface>> foo{foo_promise.get_future().get()}; |
83 | | |
84 | | // Test: make sure arguments were sent and return value is received |
85 | 1 | BOOST_CHECK_EQUAL(foo->add(1, 2), 3); |
86 | | |
87 | 1 | COutPoint txout1{Txid::FromUint256(uint256{100}), 200}; |
88 | 1 | COutPoint txout2{foo->passOutPoint(txout1)}; |
89 | 1 | BOOST_CHECK(txout1 == txout2); |
90 | | |
91 | 1 | UniValue uni1{UniValue::VOBJ}; |
92 | 1 | uni1.pushKV("i", 1); |
93 | 1 | uni1.pushKV("s", "two"); |
94 | 1 | UniValue uni2{foo->passUniValue(uni1)}; |
95 | 1 | BOOST_CHECK_EQUAL(uni1.write(), uni2.write()); |
96 | | |
97 | 1 | CMutableTransaction mtx; |
98 | 1 | mtx.version = 2; |
99 | 1 | mtx.nLockTime = 3; |
100 | 1 | mtx.vin.emplace_back(txout1); |
101 | 1 | mtx.vout.emplace_back(COIN, CScript()); |
102 | 1 | CTransactionRef tx1{MakeTransactionRef(mtx)}; |
103 | 1 | CTransactionRef tx2{foo->passTransaction(tx1)}; |
104 | 1 | BOOST_CHECK(*Assert(tx1) == *Assert(tx2)); |
105 | | |
106 | 1 | std::vector<char> vec1{'H', 'e', 'l', 'l', 'o'}; |
107 | 1 | std::vector<char> vec2{foo->passVectorChar(vec1)}; |
108 | 1 | BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end())); |
109 | | |
110 | 1 | auto script1{CScript() << OP_11}; |
111 | 1 | auto script2{foo->passScript(script1)}; |
112 | 1 | BOOST_CHECK_EQUAL(HexStr(script1), HexStr(script2)); |
113 | | |
114 | | // Test cleanup: disconnect and join thread |
115 | 1 | foo.reset(); |
116 | 1 | thread.join(); |
117 | 1 | } |
118 | | |
119 | | //! Test ipc::Protocol connect() and serve() methods connecting over a socketpair. |
120 | | void IpcSocketPairTest() |
121 | 1 | { |
122 | 1 | int fds[2]; |
123 | 1 | BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); |
124 | 1 | std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()}; |
125 | 1 | std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol()}; |
126 | 1 | std::promise<void> promise; |
127 | 1 | std::thread thread([&]() { |
128 | 1 | protocol->serve(fds[0], "test-serve", *init, [&] { promise.set_value(); }); |
129 | 1 | }); |
130 | 1 | promise.get_future().wait(); |
131 | 1 | std::unique_ptr<interfaces::Init> remote_init{protocol->connect(fds[1], "test-connect")}; |
132 | 1 | std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()}; |
133 | 1 | BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test"); |
134 | 1 | remote_echo.reset(); |
135 | 1 | remote_init.reset(); |
136 | 1 | thread.join(); |
137 | 1 | } |
138 | | |
139 | | //! Test ipc::Process bind() and connect() methods connecting over a unix socket. |
140 | | void IpcSocketTest(const fs::path& datadir) |
141 | 1 | { |
142 | 1 | std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()}; |
143 | 1 | std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol()}; |
144 | 1 | std::unique_ptr<ipc::Process> process{ipc::MakeProcess()}; |
145 | | |
146 | 1 | std::string invalid_bind{"invalid:"}; |
147 | 1 | BOOST_CHECK_THROW(process->bind(datadir, "test_bitcoin", invalid_bind), std::invalid_argument); |
148 | 1 | BOOST_CHECK_THROW(process->connect(datadir, "test_bitcoin", invalid_bind), std::invalid_argument); |
149 | | |
150 | 2 | auto bind_and_listen{[&](const std::string& bind_address) { |
151 | 2 | std::string address{bind_address}; |
152 | 2 | int serve_fd = process->bind(datadir, "test_bitcoin", address); |
153 | 2 | BOOST_CHECK_GE(serve_fd, 0); |
154 | 2 | BOOST_CHECK_EQUAL(address, bind_address); |
155 | 2 | protocol->listen(serve_fd, "test-serve", *init); |
156 | 2 | }}; |
157 | | |
158 | 5 | auto connect_and_test{[&](const std::string& connect_address) { |
159 | 5 | std::string address{connect_address}; |
160 | 5 | int connect_fd{process->connect(datadir, "test_bitcoin", address)}; |
161 | 5 | BOOST_CHECK_EQUAL(address, connect_address); |
162 | 5 | std::unique_ptr<interfaces::Init> remote_init{protocol->connect(connect_fd, "test-connect")}; |
163 | 5 | std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()}; |
164 | 5 | BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test"); |
165 | 5 | }}; |
166 | | |
167 | | // Need to specify explicit socket addresses outside the data directory, because the data |
168 | | // directory path is so long that the default socket address and any other |
169 | | // addresses in the data directory would fail with errors like: |
170 | | // Address 'unix' path '"/tmp/test_common_Bitcoin Core/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/test_bitcoin.sock"' exceeded maximum socket path length |
171 | 1 | std::vector<std::string> addresses{ |
172 | 1 | strprintf("unix:%s", TempPath("bitcoin_sock0_XXXXXX")), |
173 | 1 | strprintf("unix:%s", TempPath("bitcoin_sock1_XXXXXX")), |
174 | 1 | }; |
175 | | |
176 | | // Bind and listen on multiple addresses |
177 | 2 | for (const auto& address : addresses) { |
178 | 2 | bind_and_listen(address); |
179 | 2 | } |
180 | | |
181 | | // Connect and test each address multiple times. |
182 | 5 | for (int i : {0, 1, 0, 0, 1}) { |
183 | 5 | connect_and_test(addresses[i]); |
184 | 5 | } |
185 | 1 | } |