mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-03 09:43:55 +02:00
Merge bitcoin/bitcoin#30661: fuzz: Test headers pre-sync through p2p
a97f43d63afuzz: Add harness for p2p headers sync (marcofleon)a0eaa4749fAdd FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION in PoW check (marcofleon)a3f6f5acd8build: Automatically define FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION for fuzz builds (marcofleon)0c02d4b2bdnet_processing: Make MAX_HEADERS_RESULTS a PeerManager option (marcofleon) Pull request description: This PR reopens https://github.com/bitcoin/bitcoin/pull/28043. It's a regression fuzz test for https://github.com/bitcoin/bitcoin/pull/26355 and [a couple bugs](ed6cddd98e) that were addressed in https://github.com/bitcoin/bitcoin/pull/25717. This should help us move forward with the [removal of mainnet checkpoints](https://github.com/bitcoin/bitcoin/pull/25725). It seems like the main concern in https://github.com/bitcoin/bitcoin/pull/28043 was the global mock function for proof of work. This PR aims to be an improvement by replacing the previous approach with a fuzz build configured using `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`. This ensures that the simplified test code will never be in a release binary. If we agree this is the way to go, there are some other places (for future targets) where this method could be used. In this target, PoW isn't being tested, so the goal is to bypass the check and let the fuzzer do its thing. In the other harnesses where PoW is actually being fuzzed, `CheckProofOfWork` is now `CheckProofOfWorkImpl`. So, the only change to that function is in the name. More about `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` can be found at https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode and https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/fuzzing_in_depth.md#d-modifying-the-target. ACKs for top commit: naumenkogs: ACKa97f43d63adergoegge: reACKa97f43d63ainstagibbs: tested ACKa97f43d63abrunoerg: ACKa97f43d63aTree-SHA512: 60b0bc6aadd8ca4c39db9cbba2da2debaaf68afcb6a8dd75c1ce48ca9e3996948fda8020930b6771a424e0f7c41b0b1068db4aa7dbe517f8fc152f1f712058ad
This commit is contained in:
@@ -72,6 +72,7 @@ add_executable(fuzz
|
||||
netbase_dns_lookup.cpp
|
||||
node_eviction.cpp
|
||||
p2p_handshake.cpp
|
||||
p2p_headers_presync.cpp
|
||||
p2p_transport_serialization.cpp
|
||||
package_eval.cpp
|
||||
parse_hd_keypath.cpp
|
||||
|
||||
@@ -69,7 +69,7 @@ FUZZ_TARGET(integer, .init = initialize_integer)
|
||||
const bool b = fuzzed_data_provider.ConsumeBool();
|
||||
|
||||
const Consensus::Params& consensus_params = Params().GetConsensus();
|
||||
(void)CheckProofOfWork(u256, u32, consensus_params);
|
||||
(void)CheckProofOfWorkImpl(u256, u32, consensus_params);
|
||||
if (u64 <= MAX_MONEY) {
|
||||
const uint64_t compressed_money_amount = CompressAmount(u64);
|
||||
assert(u64 == DecompressAmount(compressed_money_amount));
|
||||
|
||||
200
src/test/fuzz/p2p_headers_presync.cpp
Normal file
200
src/test/fuzz/p2p_headers_presync.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include <blockencodings.h>
|
||||
#include <net.h>
|
||||
#include <net_processing.h>
|
||||
#include <netmessagemaker.h>
|
||||
#include <node/peerman_args.h>
|
||||
#include <pow.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <test/util/net.h>
|
||||
#include <test/util/script.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <validation.h>
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t FUZZ_MAX_HEADERS_RESULTS{16};
|
||||
|
||||
class HeadersSyncSetup : public TestingSetup
|
||||
{
|
||||
std::vector<CNode*> m_connections;
|
||||
|
||||
public:
|
||||
HeadersSyncSetup(const ChainType chain_type = ChainType::MAIN,
|
||||
TestOpts opts = {})
|
||||
: TestingSetup(chain_type, opts)
|
||||
{
|
||||
PeerManager::Options peerman_opts;
|
||||
node::ApplyArgsManOptions(*m_node.args, peerman_opts);
|
||||
peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
|
||||
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
|
||||
m_node.banman.get(), *m_node.chainman,
|
||||
*m_node.mempool, *m_node.warnings, peerman_opts);
|
||||
|
||||
CConnman::Options options;
|
||||
options.m_msgproc = m_node.peerman.get();
|
||||
m_node.connman->Init(options);
|
||||
}
|
||||
|
||||
void ResetAndInitialize() EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
|
||||
void SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
|
||||
};
|
||||
|
||||
void HeadersSyncSetup::ResetAndInitialize()
|
||||
{
|
||||
m_connections.clear();
|
||||
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
|
||||
connman.StopNodes();
|
||||
|
||||
NodeId id{0};
|
||||
std::vector<ConnectionType> conn_types = {
|
||||
ConnectionType::OUTBOUND_FULL_RELAY,
|
||||
ConnectionType::BLOCK_RELAY,
|
||||
ConnectionType::INBOUND
|
||||
};
|
||||
|
||||
for (auto conn_type : conn_types) {
|
||||
CAddress addr{};
|
||||
m_connections.push_back(new CNode(id++, nullptr, addr, 0, 0, addr, "", conn_type, false));
|
||||
CNode& p2p_node = *m_connections.back();
|
||||
|
||||
connman.Handshake(
|
||||
/*node=*/p2p_node,
|
||||
/*successfully_connected=*/true,
|
||||
/*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
|
||||
/*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
|
||||
/*version=*/PROTOCOL_VERSION,
|
||||
/*relay_txs=*/true);
|
||||
|
||||
connman.AddTestNode(p2p_node);
|
||||
}
|
||||
}
|
||||
|
||||
void HeadersSyncSetup::SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
|
||||
{
|
||||
auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
|
||||
CNode& connection = *PickValue(fuzzed_data_provider, m_connections);
|
||||
|
||||
connman.FlushSendBuffer(connection);
|
||||
(void)connman.ReceiveMsgFrom(connection, std::move(msg));
|
||||
connection.fPauseSend = false;
|
||||
try {
|
||||
connman.ProcessMessagesOnce(connection);
|
||||
} catch (const std::ios_base::failure&) {
|
||||
}
|
||||
m_node.peerman->SendMessages(&connection);
|
||||
}
|
||||
|
||||
CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
|
||||
{
|
||||
CBlockHeader header;
|
||||
header.nNonce = 0;
|
||||
// Either use the previous difficulty or let the fuzzer choose
|
||||
header.nBits = fuzzed_data_provider.ConsumeBool() ?
|
||||
prev_nbits :
|
||||
fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0x17058EBE, 0x1D00FFFF);
|
||||
header.nTime = ConsumeTime(fuzzed_data_provider);
|
||||
header.hashPrevBlock = prev_hash;
|
||||
header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
|
||||
return header;
|
||||
}
|
||||
|
||||
CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
|
||||
{
|
||||
auto header = ConsumeHeader(fuzzed_data_provider, prev_hash, prev_nbits);
|
||||
// In order to reach the headers acceptance logic, the block is
|
||||
// constructed in a way that will pass the mutation checks.
|
||||
CBlock block{header};
|
||||
CMutableTransaction tx;
|
||||
tx.vin.resize(1);
|
||||
tx.vout.resize(1);
|
||||
tx.vout[0].nValue = 0;
|
||||
tx.vin[0].scriptSig.resize(2);
|
||||
block.vtx.push_back(MakeTransactionRef(tx));
|
||||
block.hashMerkleRoot = block.vtx[0]->GetHash();
|
||||
return block;
|
||||
}
|
||||
|
||||
void FinalizeHeader(CBlockHeader& header)
|
||||
{
|
||||
while (!CheckProofOfWork(header.GetHash(), header.nBits, Params().GetConsensus())) {
|
||||
++(header.nNonce);
|
||||
}
|
||||
}
|
||||
|
||||
// Global setup works for this test as state modification (specifically in the
|
||||
// block index) would indicate a bug.
|
||||
HeadersSyncSetup* g_testing_setup;
|
||||
|
||||
void initialize()
|
||||
{
|
||||
static auto setup = MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN, {.extra_args = {"-checkpoints=0"}});
|
||||
g_testing_setup = setup.get();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FUZZ_TARGET(p2p_headers_presync, .init = initialize)
|
||||
{
|
||||
ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
|
||||
|
||||
LOCK(NetEventsInterface::g_msgproc_mutex);
|
||||
|
||||
g_testing_setup->ResetAndInitialize();
|
||||
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
|
||||
CBlockHeader base{Params().GenesisBlock()};
|
||||
SetMockTime(base.nTime);
|
||||
|
||||
// The chain is just a single block, so this is equal to 1
|
||||
size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
|
||||
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
|
||||
{
|
||||
auto finalized_block = [&]() {
|
||||
CBlock block = ConsumeBlock(fuzzed_data_provider, base.GetHash(), base.nBits);
|
||||
FinalizeHeader(block);
|
||||
return block;
|
||||
};
|
||||
|
||||
// Send low-work headers, compact blocks, and blocks
|
||||
CallOneOf(
|
||||
fuzzed_data_provider,
|
||||
[&]() NO_THREAD_SAFETY_ANALYSIS {
|
||||
// Send FUZZ_MAX_HEADERS_RESULTS headers
|
||||
std::vector<CBlock> headers;
|
||||
headers.resize(FUZZ_MAX_HEADERS_RESULTS);
|
||||
for (CBlock& header : headers) {
|
||||
header = ConsumeHeader(fuzzed_data_provider, base.GetHash(), base.nBits);
|
||||
FinalizeHeader(header);
|
||||
base = header;
|
||||
}
|
||||
|
||||
auto headers_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
|
||||
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
|
||||
},
|
||||
[&]() NO_THREAD_SAFETY_ANALYSIS {
|
||||
// Send a compact block
|
||||
auto block = finalized_block();
|
||||
CBlockHeaderAndShortTxIDs cmpct_block{block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
|
||||
|
||||
auto headers_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, TX_WITH_WITNESS(cmpct_block));
|
||||
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
|
||||
},
|
||||
[&]() NO_THREAD_SAFETY_ANALYSIS {
|
||||
// Send a block
|
||||
auto block = finalized_block();
|
||||
|
||||
auto headers_msg = NetMsg::Make(NetMsgType::BLOCK, TX_WITH_WITNESS(block));
|
||||
g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
|
||||
});
|
||||
}
|
||||
|
||||
// The headers/blocks sent in this test should never be stored, as the chains don't have the work required
|
||||
// to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
|
||||
// in the headers pre-sync logic.
|
||||
assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
|
||||
|
||||
g_testing_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
||||
}
|
||||
@@ -80,7 +80,7 @@ FUZZ_TARGET(pow, .init = initialize_pow)
|
||||
{
|
||||
const std::optional<uint256> hash = ConsumeDeserializable<uint256>(fuzzed_data_provider);
|
||||
if (hash) {
|
||||
(void)CheckProofOfWork(*hash, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), consensus_params);
|
||||
(void)CheckProofOfWorkImpl(*hash, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), consensus_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user