ipc_test.cpp raw

   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 <tinyformat.h>
  15  #include <util/log.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      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  {
  42      std::string temp{fs::PathToString(fs::path{fs::temp_directory_path()} / fs::PathFromString(std::string{pattern}))};
  43      temp.push_back('\0');
  44      int fd{mkstemp(temp.data())};
  45      BOOST_CHECK_GE(fd, 0);
  46      BOOST_CHECK_EQUAL(close(fd), 0);
  47      temp.resize(temp.size() - 1);
  48      fs::remove(fs::PathFromString(temp));
  49      return temp;
  50  }
  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  {
  62      // Setup: create FooImplementation object and listen for FooInterface requests
  63      std::promise<std::unique_ptr<mp::ProxyClient<gen::FooInterface>>> foo_promise;
  64      std::thread thread([&]() {
  65          mp::EventLoop loop("IpcPipeTest", [](bool raise, const std::string& log) { LogInfo("LOG%i: %s", raise, log); });
  66          auto pipe = loop.m_io_context.provider->newTwoWayPipe();
  67  
  68          auto connection_client = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[0]));
  69          auto foo_client = std::make_unique<mp::ProxyClient<gen::FooInterface>>(
  70              connection_client->m_rpc_system->bootstrap(mp::ServerVatId().vat_id).castAs<gen::FooInterface>(),
  71              connection_client.get(), /* destroy_connection= */ true);
  72          (void)connection_client.release();
  73          foo_promise.set_value(std::move(foo_client));
  74  
  75          auto connection_server = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) {
  76              auto foo_server = kj::heap<mp::ProxyServer<gen::FooInterface>>(std::make_shared<FooImplementation>(), connection);
  77              return capnp::Capability::Client(kj::mv(foo_server));
  78          });
  79          connection_server->onDisconnect([&] { connection_server.reset(); });
  80          loop.loop();
  81      });
  82      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      BOOST_CHECK_EQUAL(foo->add(1, 2), 3);
  86  
  87      COutPoint txout1{Txid::FromUint256(uint256{100}), 200};
  88      COutPoint txout2{foo->passOutPoint(txout1)};
  89      BOOST_CHECK(txout1 == txout2);
  90  
  91      UniValue uni1{UniValue::VOBJ};
  92      uni1.pushKV("i", 1);
  93      uni1.pushKV("s", "two");
  94      UniValue uni2{foo->passUniValue(uni1)};
  95      BOOST_CHECK_EQUAL(uni1.write(), uni2.write());
  96  
  97      CMutableTransaction mtx;
  98      mtx.version = 2;
  99      mtx.nLockTime = 3;
 100      mtx.vin.emplace_back(txout1);
 101      mtx.vout.emplace_back(COIN, CScript());
 102      CTransactionRef tx1{MakeTransactionRef(mtx)};
 103      CTransactionRef tx2{foo->passTransaction(tx1)};
 104      BOOST_CHECK(*Assert(tx1) == *Assert(tx2));
 105  
 106      std::vector<CTransactionRef> txs1;
 107      txs1.push_back(tx1);
 108      txs1.push_back(nullptr);
 109      std::vector<CTransactionRef> txs2(foo->passTransactions(txs1));
 110      BOOST_CHECK_EQUAL(txs2.size(), 2);
 111      BOOST_CHECK(*Assert(txs1[0]) == *Assert(txs2[0]));
 112      BOOST_CHECK(!txs2[1]);
 113  
 114      std::vector<char> vec1{'H', 'e', 'l', 'l', 'o'};
 115      std::vector<char> vec2{foo->passVectorChar(vec1)};
 116      BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end()));
 117  
 118      auto script1{CScript() << OP_11};
 119      auto script2{foo->passScript(script1)};
 120      BOOST_CHECK_EQUAL(HexStr(script1), HexStr(script2));
 121  
 122      // Test cleanup: disconnect and join thread
 123      foo.reset();
 124      thread.join();
 125  }
 126  
 127  //! Test ipc::Protocol connect() and serve() methods connecting over a socketpair.
 128  void IpcSocketPairTest()
 129  {
 130      int fds[2];
 131      BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0);
 132      std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()};
 133      std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol()};
 134      std::promise<void> promise;
 135      std::thread thread([&]() {
 136          protocol->serve(fds[0], "test-serve", *init, [&] { promise.set_value(); });
 137      });
 138      promise.get_future().wait();
 139      std::unique_ptr<interfaces::Init> remote_init{protocol->connect(fds[1], "test-connect")};
 140      std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()};
 141      BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test");
 142      remote_echo.reset();
 143      remote_init.reset();
 144      thread.join();
 145  }
 146  
 147  //! Test ipc::Process bind() and connect() methods connecting over a unix socket.
 148  void IpcSocketTest(const fs::path& datadir)
 149  {
 150      std::unique_ptr<interfaces::Init> init{std::make_unique<TestInit>()};
 151      std::unique_ptr<ipc::Protocol> protocol{ipc::capnp::MakeCapnpProtocol()};
 152      std::unique_ptr<ipc::Process> process{ipc::MakeProcess()};
 153  
 154      std::string invalid_bind{"invalid:"};
 155      BOOST_CHECK_THROW(process->bind(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);
 156      BOOST_CHECK_THROW(process->connect(datadir, "test_bitcoin", invalid_bind), std::invalid_argument);
 157  
 158      auto bind_and_listen{[&](const std::string& bind_address) {
 159          std::string address{bind_address};
 160          int serve_fd = process->bind(datadir, "test_bitcoin", address);
 161          BOOST_CHECK_GE(serve_fd, 0);
 162          BOOST_CHECK_EQUAL(address, bind_address);
 163          protocol->listen(serve_fd, "test-serve", *init);
 164      }};
 165  
 166      auto connect_and_test{[&](const std::string& connect_address) {
 167          std::string address{connect_address};
 168          int connect_fd{process->connect(datadir, "test_bitcoin", address)};
 169          BOOST_CHECK_EQUAL(address, connect_address);
 170          std::unique_ptr<interfaces::Init> remote_init{protocol->connect(connect_fd, "test-connect")};
 171          std::unique_ptr<interfaces::Echo> remote_echo{remote_init->makeEcho()};
 172          BOOST_CHECK_EQUAL(remote_echo->echo("echo test"), "echo test");
 173      }};
 174  
 175      // Need to specify explicit socket addresses outside the data directory, because the data
 176      // directory path is so long that the default socket address and any other
 177      // addresses in the data directory would fail with errors like:
 178      //   Address 'unix' path '"/tmp/test_common_Bitcoin Core/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/test_bitcoin.sock"' exceeded maximum socket path length
 179      std::vector<std::string> addresses{
 180          strprintf("unix:%s", TempPath("bitcoin_sock0_XXXXXX")),
 181          strprintf("unix:%s", TempPath("bitcoin_sock1_XXXXXX")),
 182      };
 183  
 184      // Bind and listen on multiple addresses
 185      for (const auto& address : addresses) {
 186          bind_and_listen(address);
 187      }
 188  
 189      // Connect and test each address multiple times.
 190      for (int i : {0, 1, 0, 0, 1}) {
 191          connect_and_test(addresses[i]);
 192      }
 193  }
 194