test: add NetTestingSetup that starts connman with mocked sockets

This commit is contained in:
Vasil Dimov 2022-12-07 11:16:10 +01:00
parent 79f02d56ef
commit ce0c7c7071
No known key found for this signature in database
GPG key ID: 54DF06F64B55CBBF
2 changed files with 138 additions and 0 deletions

View file

@ -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<DynSock::Pipes>();
// 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<std::chrono::seconds>()),
/*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<DynSock::Queue>()};
return std::make_unique<DynSock>(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<CAddress>{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<DynSock::Pipes> 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<DynSock::Pipes> pipes)
{
LOCK(m_mutex);
m_list.push_back(pipes);
m_cond.notify_one();
}
std::shared_ptr<DynSock::Pipes> 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<DynSock::Pipes> ret{m_list.front()};
m_list.pop_front();
return ret;
}
/**
* @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
* with 9 txs.

View file

@ -16,6 +16,7 @@
#include <primitives/transaction.h>
#include <pubkey.h>
#include <stdexcept>
#include <test/util/net.h>
#include <test/util/random.h>
#include <util/chaintype.h> // IWYU pragma: export
#include <util/check.h>
@ -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<DynSock::Pipes>.
* 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<DynSock::Pipes> pipes) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/**
* Remove the front of the list and return it.
*/
std::shared_ptr<DynSock::Pipes> PopFront(bool wait = true) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
private:
Mutex m_mutex;
std::condition_variable m_cond;
std::list<std::shared_ptr<DynSock::Pipes>> m_list GUARDED_BY(m_mutex);
} m_sockets_pipes;
};
class CBlock;
struct CMutableTransaction;
class CScript;