Skip to content

Commit a97f43d

Browse files
committed
fuzz: Add harness for p2p headers sync
1 parent a0eaa47 commit a97f43d

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

src/test/fuzz/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ add_executable(fuzz
7272
netbase_dns_lookup.cpp
7373
node_eviction.cpp
7474
p2p_handshake.cpp
75+
p2p_headers_presync.cpp
7576
p2p_transport_serialization.cpp
7677
package_eval.cpp
7778
parse_hd_keypath.cpp

src/test/fuzz/p2p_headers_presync.cpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#include <blockencodings.h>
2+
#include <net.h>
3+
#include <net_processing.h>
4+
#include <netmessagemaker.h>
5+
#include <node/peerman_args.h>
6+
#include <pow.h>
7+
#include <test/fuzz/FuzzedDataProvider.h>
8+
#include <test/fuzz/fuzz.h>
9+
#include <test/fuzz/util.h>
10+
#include <test/util/net.h>
11+
#include <test/util/script.h>
12+
#include <test/util/setup_common.h>
13+
#include <validation.h>
14+
15+
namespace {
16+
constexpr uint32_t FUZZ_MAX_HEADERS_RESULTS{16};
17+
18+
class HeadersSyncSetup : public TestingSetup
19+
{
20+
std::vector<CNode*> m_connections;
21+
22+
public:
23+
HeadersSyncSetup(const ChainType chain_type = ChainType::MAIN,
24+
TestOpts opts = {})
25+
: TestingSetup(chain_type, opts)
26+
{
27+
PeerManager::Options peerman_opts;
28+
node::ApplyArgsManOptions(*m_node.args, peerman_opts);
29+
peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
30+
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
31+
m_node.banman.get(), *m_node.chainman,
32+
*m_node.mempool, *m_node.warnings, peerman_opts);
33+
34+
CConnman::Options options;
35+
options.m_msgproc = m_node.peerman.get();
36+
m_node.connman->Init(options);
37+
}
38+
39+
void ResetAndInitialize() EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
40+
void SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
41+
EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
42+
};
43+
44+
void HeadersSyncSetup::ResetAndInitialize()
45+
{
46+
m_connections.clear();
47+
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
48+
connman.StopNodes();
49+
50+
NodeId id{0};
51+
std::vector<ConnectionType> conn_types = {
52+
ConnectionType::OUTBOUND_FULL_RELAY,
53+
ConnectionType::BLOCK_RELAY,
54+
ConnectionType::INBOUND
55+
};
56+
57+
for (auto conn_type : conn_types) {
58+
CAddress addr{};
59+
m_connections.push_back(new CNode(id++, nullptr, addr, 0, 0, addr, "", conn_type, false));
60+
CNode& p2p_node = *m_connections.back();
61+
62+
connman.Handshake(
63+
/*node=*/p2p_node,
64+
/*successfully_connected=*/true,
65+
/*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
66+
/*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
67+
/*version=*/PROTOCOL_VERSION,
68+
/*relay_txs=*/true);
69+
70+
connman.AddTestNode(p2p_node);
71+
}
72+
}
73+
74+
void HeadersSyncSetup::SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
75+
{
76+
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
77+
CNode& connection = *PickValue(fuzzed_data_provider, m_connections);
78+
79+
connman.FlushSendBuffer(connection);
80+
(void)connman.ReceiveMsgFrom(connection, std::move(msg));
81+
connection.fPauseSend = false;
82+
try {
83+
connman.ProcessMessagesOnce(connection);
84+
} catch (const std::ios_base::failure&) {
85+
}
86+
m_node.peerman->SendMessages(&connection);
87+
}
88+
89+
CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
90+
{
91+
CBlockHeader header;
92+
header.nNonce = 0;
93+
// Either use the previous difficulty or let the fuzzer choose
94+
header.nBits = fuzzed_data_provider.ConsumeBool() ?
95+
prev_nbits :
96+
fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0x17058EBE, 0x1D00FFFF);
97+
header.nTime = ConsumeTime(fuzzed_data_provider);
98+
header.hashPrevBlock = prev_hash;
99+
header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
100+
return header;
101+
}
102+
103+
CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
104+
{
105+
auto header = ConsumeHeader(fuzzed_data_provider, prev_hash, prev_nbits);
106+
// In order to reach the headers acceptance logic, the block is
107+
// constructed in a way that will pass the mutation checks.
108+
CBlock block{header};
109+
CMutableTransaction tx;
110+
tx.vin.resize(1);
111+
tx.vout.resize(1);
112+
tx.vout[0].nValue = 0;
113+
tx.vin[0].scriptSig.resize(2);
114+
block.vtx.push_back(MakeTransactionRef(tx));
115+
block.hashMerkleRoot = block.vtx[0]->GetHash();
116+
return block;
117+
}
118+
119+
void FinalizeHeader(CBlockHeader& header)
120+
{
121+
while (!CheckProofOfWork(header.GetHash(), header.nBits, Params().GetConsensus())) {
122+
++(header.nNonce);
123+
}
124+
}
125+
126+
// Global setup works for this test as state modification (specifically in the
127+
// block index) would indicate a bug.
128+
HeadersSyncSetup* g_testing_setup;
129+
130+
void initialize()
131+
{
132+
static auto setup = MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN, {.extra_args = {"-checkpoints=0"}});
133+
g_testing_setup = setup.get();
134+
}
135+
} // namespace
136+
137+
FUZZ_TARGET(p2p_headers_presync, .init = initialize)
138+
{
139+
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
140+
141+
LOCK(NetEventsInterface::g_msgproc_mutex);
142+
143+
g_testing_setup->ResetAndInitialize();
144+
145+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
146+
147+
CBlockHeader base{Params().GenesisBlock()};
148+
SetMockTime(base.nTime);
149+
150+
// The chain is just a single block, so this is equal to 1
151+
size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
152+
153+
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
154+
{
155+
auto finalized_block = [&]() {
156+
CBlock block = ConsumeBlock(fuzzed_data_provider, base.GetHash(), base.nBits);
157+
FinalizeHeader(block);
158+
return block;
159+
};
160+
161+
// Send low-work headers, compact blocks, and blocks
162+
CallOneOf(
163+
fuzzed_data_provider,
164+
[&]() NO_THREAD_SAFETY_ANALYSIS {
165+
// Send FUZZ_MAX_HEADERS_RESULTS headers
166+
std::vector<CBlock> headers;
167+
headers.resize(FUZZ_MAX_HEADERS_RESULTS);
168+
for (CBlock& header : headers) {
169+
header = ConsumeHeader(fuzzed_data_provider, base.GetHash(), base.nBits);
170+
FinalizeHeader(header);
171+
base = header;
172+
}
173+
174+
auto headers_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
175+
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
176+
},
177+
[&]() NO_THREAD_SAFETY_ANALYSIS {
178+
// Send a compact block
179+
auto block = finalized_block();
180+
CBlockHeaderAndShortTxIDs cmpct_block{block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
181+
182+
auto headers_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, TX_WITH_WITNESS(cmpct_block));
183+
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
184+
},
185+
[&]() NO_THREAD_SAFETY_ANALYSIS {
186+
// Send a block
187+
auto block = finalized_block();
188+
189+
auto headers_msg = NetMsg::Make(NetMsgType::BLOCK, TX_WITH_WITNESS(block));
190+
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
191+
});
192+
}
193+
194+
// The headers/blocks sent in this test should never be stored, as the chains don't have the work required
195+
// to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
196+
// in the headers pre-sync logic.
197+
assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
198+
199+
g_testing_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
200+
}

0 commit comments

Comments
 (0)