/tmp/bitcoin/src/ipc/test/ipc_tests.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/mining.capnp.h> |
7 | | #include <ipc/capnp/protocol.h> |
8 | | #include <ipc/process.h> |
9 | | #include <ipc/protocol.h> |
10 | | #include <ipc/test/ipc_test.capnp.h> |
11 | | #include <ipc/test/ipc_test.capnp.proxy.h> |
12 | | #include <ipc/test/ipc_test.h> |
13 | | #include <mp/proxy-types.h> |
14 | | #include <test/util/common.h> |
15 | | #include <test/util/setup_common.h> |
16 | | #include <tinyformat.h> |
17 | | #include <util/log.h> |
18 | | #include <validation.h> |
19 | | |
20 | | #include <future> |
21 | | #include <optional> |
22 | | #include <thread> |
23 | | #include <kj/common.h> |
24 | | #include <kj/memory.h> |
25 | | #include <kj/test.h> |
26 | | #include <stdexcept> |
27 | | |
28 | | #include <boost/test/unit_test.hpp> |
29 | | |
30 | | static_assert(ipc::capnp::messages::MAX_MONEY == MAX_MONEY); |
31 | | static_assert(ipc::capnp::messages::MAX_DOUBLE == std::numeric_limits<double>::max()); |
32 | | static_assert(ipc::capnp::messages::DEFAULT_BLOCK_RESERVED_WEIGHT == DEFAULT_BLOCK_RESERVED_WEIGHT); |
33 | | static_assert(ipc::capnp::messages::DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS == DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS); |
34 | | |
35 | | //! Remote init class. |
36 | | class TestInit : public interfaces::Init |
37 | | { |
38 | | public: |
39 | 6 | std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } |
40 | | }; |
41 | | |
42 | | //! Generate a temporary path with temp_directory_path and mkstemp |
43 | | static std::string TempPath(std::string_view pattern) |
44 | 2 | { |
45 | 2 | std::string temp{fs::PathToString(fs::path{fs::temp_directory_path()} / fs::PathFromString(std::string{pattern}))}; |
46 | 2 | temp.push_back('\0'); |
47 | 2 | int fd{mkstemp(temp.data())}; |
48 | 2 | BOOST_CHECK_GE(fd, 0); |
49 | 2 | BOOST_CHECK_EQUAL(close(fd), 0); |
50 | 2 | temp.resize(temp.size() - 1); |
51 | 2 | fs::remove(fs::PathFromString(temp)); |
52 | 2 | return temp; |
53 | 2 | } |
54 | | |
55 | | //! Unit test that tests execution of IPC calls without actually creating a |
56 | | //! separate process. This test is primarily intended to verify behavior of type |
57 | | //! conversion code that converts C++ objects to Cap'n Proto messages and vice |
58 | | //! versa. |
59 | | //! |
60 | | //! The test creates a thread which creates a FooImplementation object (defined |
61 | | //! in ipc_test.h) and a two-way pipe accepting IPC requests which call methods |
62 | | //! on the object through FooInterface (defined in ipc_test.capnp). |
63 | | void IpcPipeTest() |
64 | 1 | { |
65 | | // Setup: create FooImplementation object and listen for FooInterface requests |
66 | 1 | std::promise<std::unique_ptr<mp::ProxyClient<gen::FooInterface>>> foo_promise; |
67 | 1 | std::thread thread([&]() { |
68 | 67 | mp::EventLoop loop("IpcPipeTest", [](bool raise, const std::string& log) { LogInfo("LOG%i: %s", raise, log); }); |
69 | 1 | auto pipe = loop.m_io_context.provider->newTwoWayPipe(); |
70 | | |
71 | 1 | auto connection_client = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[0])); |
72 | 1 | auto foo_client = std::make_unique<mp::ProxyClient<gen::FooInterface>>( |
73 | 1 | connection_client->m_rpc_system->bootstrap(mp::ServerVatId().vat_id).castAs<gen::FooInterface>(), |
74 | 1 | connection_client.get(), /* destroy_connection= */ true); |
75 | 1 | (void)connection_client.release(); |
76 | 1 | foo_promise.set_value(std::move(foo_client)); |
77 | | |
78 | 1 | auto connection_server = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) { |
79 | 1 | auto foo_server = kj::heap<mp::ProxyServer<gen::FooInterface>>(std::make_shared<FooImplementation>(), connection); |
80 | 1 | return capnp::Capability::Client(kj::mv(foo_server)); |
81 | 1 | }); |
82 | 1 | connection_server->onDisconnect([&] { connection_server.reset(); }); |
83 | 1 | loop.loop(); |
84 | 1 | }); |
85 | 1 | std::unique_ptr<mp::ProxyClient<gen::FooInterface>> foo{foo_promise.get_future().get()}; |
86 | | |
87 | | // Test: make sure arguments were sent and return value is received |
88 | 1 | BOOST_CHECK_EQUAL(foo->add(1, 2), 3); |
89 | | |
90 | 1 | COutPoint txout1{Txid::FromUint256(uint256{100}), 200}; |
91 | 1 | COutPoint txout2{foo->passOutPoint(txout1)}; |
92 | 1 | BOOST_CHECK(txout1 == txout2); |
93 | | |
94 | 1 | UniValue uni1{UniValue::VOBJ}; |
95 | 1 | uni1.pushKV("i", 1); |
96 | 1 | uni1.pushKV("s", "two"); |
97 | 1 | UniValue uni2{foo->passUniValue(uni1)}; |
98 | 1 | BOOST_CHECK_EQUAL(uni1.write(), uni2.write()); |
99 | | |
100 | 1 | CMutableTransaction mtx; |
101 | 1 | mtx.version = 2; |
102 | 1 | mtx.nLockTime = 3; |
103 | 1 | mtx.vin.emplace_back(txout1); |
104 | 1 | mtx.vout.emplace_back(COIN, CScript()); |
105 | 1 | CTransactionRef tx1{MakeTransactionRef(mtx)}; |
106 | 1 | CTransactionRef tx2{foo->passTransaction(tx1)}; |
107 | 1 | BOOST_CHECK(Assert(tx1)->Equals(*Assert(tx2))); |
108 | | |
109 | 1 | std::vector<CTransactionRef> txs1; |
110 | 1 | txs1.push_back(tx1); |
111 | 1 | txs1.push_back(nullptr); |
112 | 1 | std::vector<CTransactionRef> txs2(foo->passTransactions(txs1)); |
113 | 1 | BOOST_CHECK_EQUAL(txs2.size(), 2); |
114 | 1 | BOOST_CHECK(Assert(txs1[0])->Equals(*Assert(txs2[0]))); |
115 | 1 | BOOST_CHECK(!txs2[1]); |
116 | | |
117 | 1 | std::vector<char> vec1{'H', 'e', 'l', 'l', 'o'}; |
118 | 1 | std::vector<char> vec2{foo->passVectorChar(vec1)}; |
119 | 1 | BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end())); |
120 | | |
121 | 1 | auto script1{CScript() << OP_11}; |
122 | 1 | auto script2{foo->passScript(script1)}; |
123 | 1 | BOOST_CHECK_EQUAL(HexStr(script1), HexStr(script2)); |
124 | | |
125 | | // Test cleanup: disconnect and join thread |
126 | 1 | foo.reset(); |
127 | 1 | thread.join(); |
128 | 1 | } |
129 | | |
130 | | //! Test ipc::Protocol connect() and serve() methods connecting over a socketpair. |
131 | | void IpcSocketPairTest() |
132 | 1 | { |
133 | 1 | std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()}; |
134 | 1 | std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol("IpcSocketPairTest")}; |
135 | 1 | mp::Stream client_stream; |
136 | 1 | std::promise<void> promise; |
137 | 1 | std::thread thread([&]() { |
138 | 1 | protocol->serve(*init, [&] { |
139 | 1 | auto pair{mp::SocketPair()}; |
140 | 1 | client_stream = protocol->makeStream(pair[0]); |
141 | 1 | promise.set_value(); |
142 | 1 | return protocol->makeStream(pair[1]); |
143 | 1 | }); |
144 | 1 | }); |
145 | 1 | promise.get_future().wait(); |
146 | 1 | std::unique_ptr<interfaces::Init> remote_init{protocol->connect(std::move(client_stream))}; |
147 | 1 | std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()}; |
148 | 1 | BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test"); |
149 | 1 | remote_echo.reset(); |
150 | 1 | remote_init.reset(); |
151 | 1 | thread.join(); |
152 | 1 | } |
153 | | |
154 | | //! Test ipc::Process bind() and connect() methods connecting over a unix socket. |
155 | | void IpcSocketTest(const fs::path& datadir) |
156 | 1 | { |
157 | 1 | std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()}; |
158 | 1 | std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol("IpcSocketTest")}; |
159 | 1 | std::unique_ptr<ipc::Process> process{ipc::MakeProcess()}; |
160 | | |
161 | 1 | std::string invalid_bind{"invalid:"}; |
162 | 1 | BOOST_CHECK_THROW(process->bind(datadir, "test_bitcoin", invalid_bind), std::invalid_argument); |
163 | 1 | BOOST_CHECK_THROW(process->connect(datadir, "test_bitcoin", invalid_bind), std::invalid_argument); |
164 | | |
165 | 2 | auto bind_and_listen{[&](const std::string& bind_address) { |
166 | 2 | std::string address{bind_address}; |
167 | 2 | mp::SocketId serve_fd = process->bind(datadir, "test_bitcoin", address); |
168 | 2 | BOOST_CHECK_NE(serve_fd, mp::SocketError); |
169 | 2 | BOOST_CHECK_EQUAL(address, bind_address); |
170 | 2 | protocol->listen(serve_fd, *init); |
171 | 2 | }}; |
172 | | |
173 | 5 | auto connect_and_test{[&](const std::string& connect_address) { |
174 | 5 | std::string address{connect_address}; |
175 | 5 | mp::SocketId connect_fd{process->connect(datadir, "test_bitcoin", address)}; |
176 | 5 | BOOST_CHECK_EQUAL(address, connect_address); |
177 | 5 | std::unique_ptr<interfaces::Init> remote_init{protocol->connect(protocol->makeStream(connect_fd))}; |
178 | 5 | std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()}; |
179 | 5 | BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test"); |
180 | 5 | }}; |
181 | | |
182 | | // Need to specify explicit socket addresses outside the data directory, because the data |
183 | | // directory path is so long that the default socket address and any other |
184 | | // addresses in the data directory would fail with errors like: |
185 | | // Address 'unix' path '"/tmp/test_common_Bitcoin Core/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/test_bitcoin.sock"' exceeded maximum socket path length |
186 | 1 | std::vector<std::string> addresses{ |
187 | 1 | strprintf("unix:%s", TempPath("bitcoin_sock0_XXXXXX")), |
188 | 1 | strprintf("unix:%s", TempPath("bitcoin_sock1_XXXXXX")), |
189 | 1 | }; |
190 | | |
191 | | // Bind and listen on multiple addresses |
192 | 2 | for (const auto& address : addresses) { |
193 | 2 | bind_and_listen(address); |
194 | 2 | } |
195 | | |
196 | | // Connect and test each address multiple times. |
197 | 5 | for (int i : {0, 1, 0, 0, 1}) { |
198 | 5 | connect_and_test(addresses[i]); |
199 | 5 | } |
200 | 1 | } |
201 | | |
202 | | BOOST_FIXTURE_TEST_SUITE(ipc_tests, BasicTestingSetup) |
203 | | BOOST_AUTO_TEST_CASE(ipc_tests) |
204 | 1 | { |
205 | 1 | IpcPipeTest(); |
206 | 1 | IpcSocketPairTest(); |
207 | 1 | IpcSocketTest(m_args.GetDataDirNet()); |
208 | 1 | } |
209 | | |
210 | | // Test address parsing. |
211 | | BOOST_AUTO_TEST_CASE(parse_address_test) |
212 | 1 | { |
213 | 1 | std::unique_ptr<ipc::Process> process{ipc::MakeProcess()}; |
214 | 1 | fs::path datadir{"/var/empty/notexist"}; |
215 | 3 | auto check_notexist{[](const std::system_error& e) { return e.code() == std::errc::no_such_file_or_directory; }}; |
216 | 5 | auto check_address{[&](std::string address, std::string expect_address, std::string expect_error) { |
217 | 5 | if (expect_error.empty()) { |
218 | 3 | BOOST_CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::system_error, check_notexist); |
219 | 3 | } else { |
220 | 2 | BOOST_CHECK_EXCEPTION(process->connect(datadir, "test_bitcoin", address), std::invalid_argument, HasReason(expect_error)); |
221 | 2 | } |
222 | 5 | BOOST_CHECK_EQUAL(address, expect_address); |
223 | 5 | }}; |
224 | 1 | std::string prefix{fs::PathToString(datadir / "")}; |
225 | 1 | check_address("unix", "unix:" + prefix + "test_bitcoin.sock", ""); |
226 | 1 | check_address("unix:", "unix:" + prefix + "test_bitcoin.sock", ""); |
227 | 1 | check_address("unix:path.sock", "unix:" + prefix + "path.sock", ""); |
228 | 1 | check_address("unix:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.sock", |
229 | 1 | "unix:" + prefix + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.sock", |
230 | 1 | "Unix address path \"" + prefix + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.sock\" exceeded maximum socket path length"); |
231 | 1 | check_address("invalid", "invalid", "Unrecognized address 'invalid'"); |
232 | 1 | } |
233 | | |
234 | | // Test command line parsing in ipc::Process::checkSpawned(). |
235 | | BOOST_AUTO_TEST_CASE(check_spawned_test) |
236 | 1 | { |
237 | 1 | std::unique_ptr<ipc::Process> process{ipc::MakeProcess()}; |
238 | 1 | char arg0[]{"bitcoin-node"}; |
239 | 1 | char arg_spawn[]{"-ipcchild"}; |
240 | 1 | char arg_invalid[]{"invalid"}; |
241 | 1 | char arg_other[]{"-ipcbind=unix"}; |
242 | | |
243 | | // no -ipcchild arg. |
244 | 1 | { |
245 | 1 | char* argv[]{arg0}; |
246 | 1 | BOOST_CHECK(!process->checkSpawned(1, argv)); |
247 | 1 | } |
248 | 1 | { |
249 | 1 | char* argv[]{arg0, arg_other}; |
250 | 1 | BOOST_CHECK(!process->checkSpawned(2, argv)); |
251 | 1 | } |
252 | 1 | { |
253 | 1 | char* argv[]{arg0, arg_other, arg_invalid}; |
254 | 1 | BOOST_CHECK(!process->checkSpawned(3, argv)); |
255 | 1 | } |
256 | | // -ipcchild without a value. |
257 | 1 | { |
258 | 1 | char* argv[]{arg0, arg_spawn}; |
259 | 1 | BOOST_CHECK(!process->checkSpawned(2, argv)); |
260 | 1 | } |
261 | | // -ipcchild combined with other arguments. |
262 | 1 | { |
263 | 1 | char* argv[]{arg0, arg_spawn, arg_invalid, arg_other}; |
264 | 1 | BOOST_CHECK(!process->checkSpawned(4, argv)); |
265 | 1 | } |
266 | | // -ipcchild with a value that is not a valid way of connecting to the |
267 | | // parent process. |
268 | 1 | { |
269 | 1 | char* argv[]{arg0, arg_spawn, arg_invalid}; |
270 | | BOOST_CHECK_THROW(process->checkSpawned(3, argv), std::runtime_error); |
271 | 1 | } |
272 | 1 | } |
273 | | |
274 | | BOOST_AUTO_TEST_SUITE_END() |