Skip to content

Commit

Permalink
Add timewarp fuzz test
Browse files Browse the repository at this point in the history
  • Loading branch information
instagibbs committed Aug 22, 2024
1 parent 4e87dcc commit c808f97
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/strprintf.cpp \
test/fuzz/system.cpp \
test/fuzz/timeoffsets.cpp \
test/fuzz/timewarp.cpp \
test/fuzz/torcontrol.cpp \
test/fuzz/transaction.cpp \
test/fuzz/tx_in.cpp \
Expand Down
4 changes: 2 additions & 2 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,11 @@ class CRegTestParams : public CChainParams
consensus.SegwitHeight = 0; // Always active unless overridden
consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"};
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetTimespan = 60 * 60; // one hour (for timewarp testing)
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
consensus.enforce_BIP94 = false;
consensus.fPowNoRetargeting = true;
consensus.fPowNoRetargeting = false; // for (timewarp attacks)
consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains
consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016)

Expand Down
3 changes: 2 additions & 1 deletion src/pow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
return (hash.data()[31] & 0x80) == 0;
return true; // For this fuzz test we don't care about PoW at all
// return (hash.data()[31] & 0x80) == 0;
#else
return CheckProofOfWorkImpl(hash, nBits, params);
#endif
Expand Down
80 changes: 80 additions & 0 deletions src/test/fuzz/timewarp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2021-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <consensus/validation.h>
#include <node/utxo_snapshot.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <util/chaintype.h>
#include <util/fs.h>
#include <validation.h>
#include <validationinterface.h>
#include <pow.h>

using node::SnapshotMetadata;

namespace {

const std::vector<std::shared_ptr<CBlock>>* g_chain;

void initialize_chain()
{
const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
// Generate 5 retarget periods worth of blockheaders even though
// we only should see 3 periods worth of blocks accepted
std::vector<int64_t> block_times(6 * 5);
std::iota(block_times.begin(), block_times.end(), 1);
for (size_t i = 0; i < block_times.size(); i++) {
block_times[i] += params->GenesisBlock().nTime;
}
static const auto chain{CreateBlockChainDiff(block_times, *params)};
g_chain = &chain;
}

FUZZ_TARGET(timewarp, .init = initialize_chain)
{
// FIXME how to model a timewarp?

FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
std::unique_ptr<const TestingSetup> setup{
MakeNoLogFileContext<const TestingSetup>(
ChainType::REGTEST,
TestOpts{
.setup_net = false,
.setup_validation_interface = false,
})};
const auto& node = setup->m_node;
auto& chainman{*node.chainman};
const Consensus::Params& consensusParams = chainman.GetConsensus();
const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};

int blocks_accepted{0};
CBlockIndex* index{nullptr};
for (const auto& block : *g_chain) {
if (index != nullptr) {
const auto next_nBits = GetNextWorkRequired(index, &(*block), consensusParams);
if (next_nBits != block->nBits) {
// Need to recompute new block since we changed nBits
block->nBits = next_nBits;
block->hashPrevBlock = (blocks_accepted >= 1 ? *g_chain->at(blocks_accepted - 1) : params->GenesisBlock()).GetHash();
}
}
BlockValidationState dummy;
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
// Rest of chain doesn't work for whatever reason
if (!processed) {
break;
}
blocks_accepted++;
index = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()));
Assert(index);
}
// Should accept until nBits changes
Assert(blocks_accepted == 30);
}
} // namespace
26 changes: 26 additions & 0 deletions src/test/util/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ COutPoint generatetoaddress(const NodeContext& node, const std::string& address)
return MineBlock(node, coinbase_script);
}

std::vector<std::shared_ptr<CBlock>> CreateBlockChainDiff(std::vector<int64_t> block_times, const CChainParams& params)
{
size_t total_height = block_times.size();
std::vector<std::shared_ptr<CBlock>> ret{total_height};
for (size_t height{0}; height < total_height; ++height) {
CBlock& block{*(ret.at(height) = std::make_shared<CBlock>())};

CMutableTransaction coinbase_tx;
coinbase_tx.vin.resize(1);
coinbase_tx.vin[0].prevout.SetNull();
coinbase_tx.vout.resize(1);
coinbase_tx.vout[0].scriptPubKey = P2WSH_OP_TRUE;
coinbase_tx.vout[0].nValue = GetBlockSubsidy(height + 1, params.GetConsensus());
coinbase_tx.vin[0].scriptSig = CScript() << (height + 1) << OP_0;
block.vtx = {MakeTransactionRef(std::move(coinbase_tx))};

block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION;
block.hashPrevBlock = (height >= 1 ? *ret.at(height - 1) : params.GenesisBlock()).GetHash();
block.hashMerkleRoot = BlockMerkleRoot(block);
block.nTime = block_times[height];
block.nBits = params.GenesisBlock().nBits;
block.nNonce = 0;
}
return ret;
}

std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params)
{
std::vector<std::shared_ptr<CBlock>> ret{total_height};
Expand Down
3 changes: 3 additions & 0 deletions src/test/util/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ struct NodeContext;
/** Create a blockchain, starting from genesis */
std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params);

/** Create a blockchain with proper nBits, starting from genesis. Block time at beginning is always 0 */
std::vector<std::shared_ptr<CBlock>> CreateBlockChainDiff(std::vector<int64_t> block_times, const CChainParams& params);

/** Returns the generated coin */
COutPoint MineBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);

Expand Down

0 comments on commit c808f97

Please sign in to comment.