Bitcoin Core  31.0.0
P2P Digital Currency
test.cpp
Go to the documentation of this file.
1 // Copyright (c) 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 <mp/test/foo.capnp.h>
6 #include <mp/test/foo.capnp.proxy.h>
7 
8 #include <atomic>
9 #include <capnp/capability.h>
10 #include <capnp/rpc.h>
11 #include <cassert>
12 #include <chrono>
13 #include <condition_variable>
14 #include <cstdint>
15 #include <cstring>
16 #include <functional>
17 #include <future>
18 #include <kj/async.h>
19 #include <kj/async-io.h>
20 #include <kj/common.h>
21 #include <kj/debug.h>
22 #include <kj/memory.h>
23 #include <kj/test.h>
24 #include <memory>
25 #include <mp/proxy.h>
26 #include <mp/proxy.capnp.h>
27 #include <mp/proxy-io.h>
28 #include <mp/util.h>
29 #include <mp/version.h>
30 #include <optional>
31 #include <set>
32 #include <stdexcept>
33 #include <string>
34 #include <string_view>
35 #include <thread>
36 #include <type_traits>
37 #include <utility>
38 #include <vector>
39 
40 namespace mp {
41 namespace test {
42 
46 static_assert(std::is_integral_v<decltype(kMP_MAJOR_VERSION)>, "MP_MAJOR_VERSION must be an integral constant");
47 static_assert(std::is_integral_v<decltype(kMP_MINOR_VERSION)>, "MP_MINOR_VERSION must be an integral constant");
48 
63 class TestSetup
64 {
65 public:
66  std::function<void()> server_disconnect;
67  std::function<void()> server_disconnect_later;
68  std::function<void()> client_disconnect;
69  std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
70  std::unique_ptr<ProxyClient<messages::FooInterface>> client;
74  std::thread thread;
75 
76  TestSetup(bool client_owns_connection = true)
77  : thread{[&] {
78  EventLoop loop("mptest", [](mp::LogMessage log) {
79  // Info logs are not printed by default, but will be shown with `mptest --verbose`
80  KJ_LOG(INFO, log.level, log.message);
81  if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
82  });
83  auto pipe = loop.m_io_context.provider->newTwoWayPipe();
84 
85  auto server_connection =
86  std::make_unique<Connection>(loop, kj::mv(pipe.ends[0]), [&](Connection& connection) {
87  auto server_proxy = kj::heap<ProxyServer<messages::FooInterface>>(
88  std::make_shared<FooImplementation>(), connection);
89  server = server_proxy;
90  return capnp::Capability::Client(kj::mv(server_proxy));
91  });
92  server_disconnect = [&] { loop.sync([&] { server_connection.reset(); }); };
94  assert(std::this_thread::get_id() == loop.m_thread_id);
95  loop.m_task_set->add(kj::evalLater([&] { server_connection.reset(); }));
96  };
97  // Set handler to destroy the server when the client disconnects. This
98  // is ignored if server_disconnect() is called instead.
99  server_connection->onDisconnect([&] { server_connection.reset(); });
100 
101  auto client_connection = std::make_unique<Connection>(loop, kj::mv(pipe.ends[1]));
102  auto client_proxy = std::make_unique<ProxyClient<messages::FooInterface>>(
103  client_connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<messages::FooInterface>(),
104  client_connection.get(), /* destroy_connection= */ client_owns_connection);
105  if (client_owns_connection) {
106  (void)client_connection.release();
107  } else {
108  client_disconnect = [&] { loop.sync([&] { client_connection.reset(); }); };
109  }
110 
111  client_promise.set_value(std::move(client_proxy));
112  loop.loop();
113  }}
114  {
115  client = client_promise.get_future().get();
116  }
117 
119  {
120  // Test that client cleanup_fns are executed.
121  bool destroyed = false;
122  client->m_context.cleanup_fns.emplace_front([&destroyed] { destroyed = true; });
123  client.reset();
124  KJ_EXPECT(destroyed);
125 
126  thread.join();
127  }
128 };
129 
130 KJ_TEST("Call FooInterface methods")
131 {
133  ProxyClient<messages::FooInterface>* foo = setup.client.get();
134 
135  KJ_EXPECT(foo->add(1, 2) == 3);
136  int ret;
137  foo->addOut(3, 4, ret);
138  KJ_EXPECT(ret == 7);
139  foo->addInOut(3, ret);
140  KJ_EXPECT(ret == 10);
141 
142  FooStruct in;
143  in.name = "name";
144  in.setint.insert(2);
145  in.setint.insert(1);
146  in.vbool.push_back(false);
147  in.vbool.push_back(true);
148  in.vbool.push_back(false);
149  FooStruct out = foo->pass(in);
150  KJ_EXPECT(in.name == out.name);
151  KJ_EXPECT(in.setint.size() == out.setint.size());
152  for (auto init{in.setint.begin()}, outit{out.setint.begin()}; init != in.setint.end() && outit != out.setint.end(); ++init, ++outit) {
153  KJ_EXPECT(*init == *outit);
154  }
155  KJ_EXPECT(in.vbool.size() == out.vbool.size());
156  for (size_t i = 0; i < in.vbool.size(); ++i) {
157  KJ_EXPECT(in.vbool[i] == out.vbool[i]);
158  }
159 
160  FooStruct err;
161  try {
162  foo->raise(in);
163  } catch (const FooStruct& e) {
164  err = e;
165  }
166  KJ_EXPECT(in.name == err.name);
167 
168  class Callback : public ExtendedCallback
169  {
170  public:
171  Callback(int expect, int ret) : m_expect(expect), m_ret(ret) {}
172  int call(int arg) override
173  {
174  KJ_EXPECT(arg == m_expect);
175  return m_ret;
176  }
177  int callExtended(int arg) override
178  {
179  KJ_EXPECT(arg == m_expect + 10);
180  return m_ret + 10;
181  }
182  int m_expect, m_ret;
183  };
184 
185  foo->initThreadMap();
186  Callback callback(1, 2);
187  KJ_EXPECT(foo->callback(callback, 1) == 2);
188  KJ_EXPECT(foo->callbackUnique(std::make_unique<Callback>(3, 4), 3) == 4);
189  KJ_EXPECT(foo->callbackShared(std::make_shared<Callback>(5, 6), 5) == 6);
190  auto saved = std::make_shared<Callback>(7, 8);
191  KJ_EXPECT(saved.use_count() == 1);
192  foo->saveCallback(saved);
193  KJ_EXPECT(saved.use_count() == 2);
194  foo->callbackSaved(7);
195  KJ_EXPECT(foo->callbackSaved(7) == 8);
196  foo->saveCallback(nullptr);
197  KJ_EXPECT(saved.use_count() == 1);
198  KJ_EXPECT(foo->callbackExtended(callback, 11) == 12);
199 
200  FooCustom custom_in;
201  custom_in.v1 = "v1";
202  custom_in.v2 = 5;
203  FooCustom custom_out = foo->passCustom(custom_in);
204  KJ_EXPECT(custom_in.v1 == custom_out.v1);
205  KJ_EXPECT(custom_in.v2 == custom_out.v2);
206 
207  foo->passEmpty(FooEmpty{});
208 
209  FooMessage message1;
210  message1.message = "init";
211  FooMessage message2{foo->passMessage(message1)};
212  KJ_EXPECT(message2.message == "init build read call build read");
213 
214  FooMutable mut;
215  mut.message = "init";
216  foo->passMutable(mut);
217  KJ_EXPECT(mut.message == "init build pass call return read");
218 
219  KJ_EXPECT(foo->passFn([]{ return 10; }) == 10);
220 
221  std::vector<FooDataRef> data_in;
222  data_in.push_back(std::make_shared<FooData>(FooData{'H', 'i'}));
223  data_in.push_back(nullptr);
224  std::vector<FooDataRef> data_out{foo->passDataPointers(data_in)};
225  KJ_EXPECT(data_out.size() == 2);
226  KJ_REQUIRE(data_out[0] != nullptr);
227  KJ_EXPECT(*data_out[0] == *data_in[0]);
228  KJ_EXPECT(!data_out[1]);
229 }
230 
231 KJ_TEST("Call IPC method after client connection is closed")
232 {
233  TestSetup setup{/*client_owns_connection=*/false};
234  ProxyClient<messages::FooInterface>* foo = setup.client.get();
235  KJ_EXPECT(foo->add(1, 2) == 3);
236  setup.client_disconnect();
237 
238  bool disconnected{false};
239  try {
240  foo->add(1, 2);
241  } catch (const std::runtime_error& e) {
242  KJ_EXPECT(std::string_view{e.what()} == "IPC client method called after disconnect.");
243  disconnected = true;
244  }
245  KJ_EXPECT(disconnected);
246 }
247 
248 KJ_TEST("Calling IPC method after server connection is closed")
249 {
251  ProxyClient<messages::FooInterface>* foo = setup.client.get();
252  KJ_EXPECT(foo->add(1, 2) == 3);
253  setup.server_disconnect();
254 
255  bool disconnected{false};
256  try {
257  foo->add(1, 2);
258  } catch (const std::runtime_error& e) {
259  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
260  disconnected = true;
261  }
262  KJ_EXPECT(disconnected);
263 }
264 
265 KJ_TEST("Calling IPC method and disconnecting during the call")
266 {
267  TestSetup setup{/*client_owns_connection=*/false};
268  ProxyClient<messages::FooInterface>* foo = setup.client.get();
269  KJ_EXPECT(foo->add(1, 2) == 3);
270 
271  // Set m_fn to initiate client disconnect when server is in the middle of
272  // handling the callFn call to make sure this case is handled cleanly.
273  setup.server->m_impl->m_fn = setup.client_disconnect;
274 
275  bool disconnected{false};
276  try {
277  foo->callFn();
278  } catch (const std::runtime_error& e) {
279  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
280  disconnected = true;
281  }
282  KJ_EXPECT(disconnected);
283 }
284 
285 KJ_TEST("Calling IPC method, disconnecting and blocking during the call")
286 {
287  // This test is similar to last test, except that instead of letting the IPC
288  // call return immediately after triggering a disconnect, make it disconnect
289  // & wait so server is forced to deal with having a disconnection and call
290  // in flight at the same time.
291  //
292  // Test uses callFnAsync() instead of callFn() to implement this. Both of
293  // these methods have the same implementation, but the callFnAsync() capnp
294  // method declaration takes an mp.Context argument so the method executes on
295  // an asynchronous thread instead of executing in the event loop thread, so
296  // it is able to block without deadlocking the event lock thread.
297  //
298  // This test adds important coverage because it causes the server Connection
299  // object to be destroyed before ProxyServer object, which is not a
300  // condition that usually happens because the m_rpc_system.reset() call in
301  // the ~Connection destructor usually would immediately free all remaining
302  // ProxyServer objects associated with the connection. Having an in-progress
303  // RPC call requires keeping the ProxyServer longer.
304 
305  std::promise<void> signal;
306  TestSetup setup{/*client_owns_connection=*/false};
307  ProxyClient<messages::FooInterface>* foo = setup.client.get();
308  KJ_EXPECT(foo->add(1, 2) == 3);
309 
310  foo->initThreadMap();
311  setup.server->m_impl->m_fn = [&] {
312  EventLoopRef loop{*setup.server->m_context.loop};
313  setup.client_disconnect();
314  signal.get_future().get();
315  };
316 
317  bool disconnected{false};
318  try {
319  foo->callFnAsync();
320  } catch (const std::runtime_error& e) {
321  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
322  disconnected = true;
323  }
324  KJ_EXPECT(disconnected);
325 
326  // Now that the disconnect has been detected, set signal allowing the
327  // callFnAsync() IPC call to return. Since signalling may not wake up the
328  // thread right away, it is important for the signal variable to be declared
329  // *before* the TestSetup variable so is not destroyed while
330  // signal.get_future().get() is called.
331  signal.set_value();
332 }
333 
334 KJ_TEST("Worker thread destroyed before it is initialized")
335 {
336  // Regression test for bitcoin/bitcoin#34711, bitcoin/bitcoin#34756 where a
337  // worker thread is destroyed before it starts waiting for work.
338  //
339  // The test uses the `makethread` hook to trigger a disconnect as soon as
340  // ProxyServer<ThreadMap>::makeThread is called, so without the bugfix,
341  // ProxyServer<Thread>::~ProxyServer would run and destroy the waiter before
342  // the worker thread started waiting, causing a SIGSEGV when it did start.
344  ProxyClient<messages::FooInterface>* foo = setup.client.get();
345  foo->initThreadMap();
346  setup.server->m_impl->m_fn = [] {};
347 
348  EventLoop& loop = *setup.server->m_context.connection->m_loop;
349  loop.testing_hook_makethread = [&] {
350  // Use disconnect_later to queue the disconnect, because the makethread
351  // hook is called on the event loop thread. The disconnect should happen
352  // as soon as the event loop is idle.
353  setup.server_disconnect_later();
354  };
356  // Sleep to allow event loop to run and process the queued disconnect
357  // before the worker thread starts waiting.
358  std::this_thread::sleep_for(std::chrono::milliseconds(10));
359  };
360 
361  bool disconnected{false};
362  try {
363  foo->callFnAsync();
364  } catch (const std::runtime_error& e) {
365  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
366  disconnected = true;
367  }
368  KJ_EXPECT(disconnected);
369 }
370 
371 KJ_TEST("Calling async IPC method, with server disconnect racing the call")
372 {
373  // Regression test for bitcoin/bitcoin#34777 heap-use-after-free where
374  // an async request is canceled before it starts to execute.
375  //
376  // Use testing_hook_async_request_start to trigger a disconnect from the
377  // worker thread as soon as it begins to execute an async request. Without
378  // the bugfix, the worker thread would trigger a SIGSEGV after this by
379  // calling call_context.getParams().
381  ProxyClient<messages::FooInterface>* foo = setup.client.get();
382  foo->initThreadMap();
383  setup.server->m_impl->m_fn = [] {};
384 
385  EventLoop& loop = *setup.server->m_context.connection->m_loop;
387  setup.server_disconnect();
388  // Sleep is necessary to let the event loop fully clean up after the
389  // disconnect and trigger the SIGSEGV.
390  std::this_thread::sleep_for(std::chrono::milliseconds(10));
391  };
392 
393  try {
394  foo->callFnAsync();
395  KJ_EXPECT(false);
396  } catch (const std::runtime_error& e) {
397  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
398  }
399 }
400 
401 KJ_TEST("Calling async IPC method, with server disconnect after cleanup")
402 {
403  // Regression test for bitcoin/bitcoin#34782 stack-use-after-return where
404  // an async request is canceled after it finishes executing but before the
405  // response is sent.
406  //
407  // Use testing_hook_async_request_done to trigger a disconnect from the
408  // worker thread after it executes an async request but before it returns.
409  // Without the bugfix, the m_on_cancel callback would be called at this
410  // point, accessing the cancel_mutex stack variable that had gone out of
411  // scope.
413  ProxyClient<messages::FooInterface>* foo = setup.client.get();
414  foo->initThreadMap();
415  setup.server->m_impl->m_fn = [] {};
416 
417  EventLoop& loop = *setup.server->m_context.connection->m_loop;
419  setup.server_disconnect();
420  };
421 
422  try {
423  foo->callFnAsync();
424  KJ_EXPECT(false);
425  } catch (const std::runtime_error& e) {
426  KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
427  }
428 }
429 
430 KJ_TEST("Make simultaneous IPC calls on single remote thread")
431 {
433  ProxyClient<messages::FooInterface>* foo = setup.client.get();
434  std::promise<void> signal;
435 
436  foo->initThreadMap();
437  // Use callFnAsync() to get the client to set up the request_thread
438  // that will be used for the test.
439  setup.server->m_impl->m_fn = [&] {};
440  foo->callFnAsync();
442  Thread::Client *callback_thread, *request_thread;
443  foo->m_context.loop->sync([&] {
444  Lock lock(tc.waiter->m_mutex);
445  callback_thread = &tc.callback_threads.at(foo->m_context.connection)->m_client;
446  request_thread = &tc.request_threads.at(foo->m_context.connection)->m_client;
447  });
448 
449  // Call callIntFnAsync 3 times with n=100, 200, 300
450  std::atomic<int> expected = 100;
451 
452  setup.server->m_impl->m_int_fn = [&](int n) {
453  assert(n == expected);
454  expected += 100;
455  return n;
456  };
457 
458  auto client{foo->m_client};
459  std::atomic<size_t> running{3};
460  foo->m_context.loop->sync([&]
461  {
462  for (size_t i = 0; i < running; i++)
463  {
464  auto request{client.callIntFnAsyncRequest()};
465  auto context{request.initContext()};
466  context.setCallbackThread(*callback_thread);
467  context.setThread(*request_thread);
468  request.setArg(100 * (i+1));
469  foo->m_context.loop->m_task_set->add(request.send().then(
470  [&running, &tc, i](auto&& results) {
471  assert(results.getResult() == static_cast<int32_t>(100 * (i+1)));
472  running -= 1;
473  tc.waiter->m_cv.notify_all();
474  }));
475  }
476  });
477  {
478  Lock lock(tc.waiter->m_mutex);
479  tc.waiter->wait(lock, [&running] { return running == 0; });
480  }
481  KJ_EXPECT(expected == 400);
482 }
483 
484 } // namespace test
485 } // namespace mp
int ret
assert(!tx.IsCoinBase())
The thread_local ThreadContext g_thread_context struct provides information about individual threads ...
Definition: proxy-io.h:674
std::function< void()> testing_hook_async_request_done
Hook called on the worker thread just before returning results.
Definition: proxy-io.h:358
std::string message
Definition: foo.h:40
constexpr auto kMP_MINOR_VERSION
Definition: test.cpp:45
Object holding network & rpc state associated with either an incoming server connection, or an outgoing client connection.
Definition: proxy-io.h:422
#define expect(bit)
#define MP_MAJOR_VERSION
Major version number.
Definition: version.h:27
ProxyServer< messages::FooInterface > * server
Definition: test.cpp:71
constexpr auto kMP_MAJOR_VERSION
Check version.h header values.
Definition: test.cpp:44
TestSetup(bool client_owns_connection=true)
Definition: test.cpp:76
std::function< void()> testing_hook_makethread
Hook called when ProxyServer<ThreadMap>::makeThread() is called.
Definition: proxy-io.h:345
std::function< void()> server_disconnect
Definition: test.cpp:66
std::set< int > setint
Definition: foo.h:22
Functions to serialize / deserialize common bitcoin types.
Definition: common-types.h:57
Event loop implementation.
Definition: proxy-io.h:238
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
Definition: test.cpp:74
std::function< void()> testing_hook_makethread_created
Hook called on the worker thread inside makeThread(), after the thread context is set up and thread_c...
Definition: proxy-io.h:350
std::string message
Message to be logged.
Definition: proxy-io.h:141
std::function< void()> testing_hook_async_request_start
Hook called on the worker thread when it starts to execute an async request.
Definition: proxy-io.h:355
std::promise< std::unique_ptr< ProxyClient< messages::FooInterface > > > client_promise
Definition: test.cpp:69
std::function< void()> client_disconnect
Definition: test.cpp:68
thread_local ThreadContext g_thread_context
Definition: proxy.cpp:41
KJ_TEST("Call FooInterface methods")
Definition: test.cpp:130
std::unique_ptr< interfaces::Init > init
Log level
The severity level of this message.
Definition: proxy-io.h:144
#define MP_MINOR_VERSION
Minor version number.
Definition: version.h:32
std::unique_ptr< ProxyClient< messages::FooInterface > > client
Definition: test.cpp:70
Major and minor version numbers.
std::string name
Definition: foo.h:21
std::vector< char > FooData
Definition: foo.h:48
static int setup(void)
Definition: tests.c:7808
std::string message
Definition: foo.h:45
Event loop smart pointer automatically managing m_num_clients.
Definition: proxy.h:50
Definition: util.h:171
Vat id for server side of connection.
Definition: proxy-io.h:499
std::vector< bool > vbool
Definition: foo.h:23
Test setup class creating a two way connection between a ProxyServer<FooInterface> object and a Proxy...
Definition: test.cpp:63
Mapping from capnp interface type to proxy client implementation (specializations are generated by pr...
Definition: proxy.h:25
std::string v1
Definition: foo.h:30
kj::AsyncIoContext m_io_context
Capnp IO context.
Definition: proxy-io.h:327
std::function< void()> server_disconnect_later
Definition: test.cpp:67