From ce0c7c7071cba5543da905832de1fa07c78521c9 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Wed, 7 Dec 2022 11:16:10 +0100 Subject: [PATCH] test: add NetTestingSetup that starts connman with mocked sockets --- src/test/util/setup_common.cpp | 101 +++++++++++++++++++++++++++++++++ src/test/util/setup_common.h | 37 ++++++++++++ 2 files changed, 138 insertions(+) diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index bf26997c076..b5f0fee7c48 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -586,6 +586,107 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate) m_node.mempool->TrimToSize(0); assert(m_node.mempool->GetMinFee() == target_feerate); } + +NetTestingSetup::NetTestingSetup(ChainType chain_type, const TestOpts& test_opts) + : TestingSetup{chain_type, test_opts} +{ + CreateSock = [this](int, int, int) { + auto pipes = std::make_shared(); + + // Schedule VERSION, VERACK and PING to be returned by Recv() from the socket (initial handshake). + pipes->recv.PushNetMsg(NetMsgType::VERSION, + /*version=*/PROTOCOL_VERSION, + /*services=*/uint64_t{NODE_NETWORK | NODE_WITNESS}, + /*timestamp=*/count_seconds(GetTime()), + /*addr_recv services=*/uint64_t{NODE_NETWORK | NODE_WITNESS}, + /*addr_recv=*/CAddress::V2_NETWORK(CService{}), + /*addr_trans services=*/uint64_t{NODE_NETWORK | NODE_WITNESS}, + /*addr_trans=*/CAddress::V2_NETWORK(CService{}), + /*nonce=*/uint64_t{0}, + /*user_agent=*/std::string{}, + /*start_height=*/1, + /*relay=*/false); + pipes->recv.PushNetMsg(NetMsgType::VERACK); + pipes->recv.PushNetMsg(NetMsgType::PING, /*nonce=*/uint64_t{123}); + + m_sockets_pipes.PushBack(pipes); + + // An empty queue means that the Accept() method on the sockets created + // here will always return nullptr (mimicking an error). + auto accept_sockets{std::make_shared()}; + + return std::make_unique(pipes, accept_sockets); + }; + + fListen = false; + fNameLookup = false; + m_node.args->ForceSetArg("-dnsseed", "0"); + m_node.args->ForceSetArg("-fixedseeds", "0"); + + // Add 1.2.3.4:8333 to addrman, sent to us by 5.6.7.8. + in_addr service_in_addr; + service_in_addr.s_addr = htonl(0x01020304); + const CService service{service_in_addr, 8333}; + const CAddress addr{service, ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; + in_addr source_in_addr; + source_in_addr.s_addr = htonl(0x05060708); + m_node.addrman->Add(std::vector{addr}, CNetAddr{source_in_addr}); + + CConnman::Options opts; + opts.m_max_automatic_connections = 1; + opts.nSendBufferMaxSize = 1000 * m_node.args->GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); + opts.nReceiveFloodSize = 1000 * m_node.args->GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); + opts.m_banman = m_node.banman.get(); + opts.m_msgproc = m_node.peerman.get(); + opts.m_use_addrman_outgoing = true; + + SetMockTime(GetTime()); + + if (!m_node.connman->Start(*m_node.scheduler, opts)) { + throw std::runtime_error{"Cannot start connman"}; + } +} + +NetTestingSetup::~NetTestingSetup() +{ + std::shared_ptr pipes; + while ((pipes = m_sockets_pipes.PopFront(false)).get() != nullptr) { + pipes->recv.Eof(); + } + m_node.connman->Interrupt(); + + SetMockTime(0s); + + m_node.args->ForceSetArg("-fixedseeds", DEFAULT_FIXEDSEEDS ? "1" : "0"); + m_node.args->ForceSetArg("-dnsseed", DEFAULT_DNSSEED ? "1" : "0"); + fNameLookup = DEFAULT_NAME_LOOKUP; + fListen = true; + CreateSock = CreateSockOS; +} + +void NetTestingSetup::PipesFifo::PushBack(std::shared_ptr pipes) +{ + LOCK(m_mutex); + m_list.push_back(pipes); + m_cond.notify_one(); +} + +std::shared_ptr NetTestingSetup::PipesFifo::PopFront(bool wait) +{ + WAIT_LOCK(m_mutex, lock); + if (wait) { + m_cond.wait(lock, [this]() EXCLUSIVE_LOCKS_REQUIRED(m_mutex) { + AssertLockHeld(m_mutex); + return !m_list.empty(); + }); + } else if (m_list.empty()) { + return nullptr; + } + std::shared_ptr ret{m_list.front()}; + m_list.pop_front(); + return ret; +} + /** * @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) * with 9 txs. diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 33ad2584573..eda12462194 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include // IWYU pragma: export #include @@ -130,6 +131,42 @@ struct RegTestingSetup : public TestingSetup { : TestingSetup{ChainType::REGTEST} {} }; +/** + * TestingSetup + start connman at the beginning of each test and stop it when it ends. + * Changes `CreateSock()` to create `DynSock` sockets, so that the connman functions do not create + * a real socket and do not open real network connections. The mocked sockets' data can be + * controlled via the `m_sockets_pipes` member, available in each unit test. + * The connman is configured in such a way that it would try to open one p2p connection. + */ +struct NetTestingSetup : public TestingSetup { + explicit NetTestingSetup(ChainType chain_type = ChainType::REGTEST, const TestOpts& test_opts = {}); + + ~NetTestingSetup(); + + /** + * Thread safe FIFO of std::shared_ptr. + * Used for pushing the pipes of newly created sockets (by CConnman threads) and + * getting and controlling them from tests. + */ + class PipesFifo { + public: + /** + * Append a new pair of pipes to the list. + */ + void PushBack(std::shared_ptr pipes) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + + /** + * Remove the front of the list and return it. + */ + std::shared_ptr PopFront(bool wait = true) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + + private: + Mutex m_mutex; + std::condition_variable m_cond; + std::list> m_list GUARDED_BY(m_mutex); + } m_sockets_pipes; +}; + class CBlock; struct CMutableTransaction; class CScript;