aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>2023-05-02 17:34:12 +0200
committerMarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>2023-05-05 13:31:01 +0200
commitfa2d8b61f9343d350b67357a12f39b613c8ee8ad (patch)
tree340f22ee5846390d8267944e5fc3c0e522417f45 /src
parentfaae7d5c00c99b0f3e99a1fbffbf369645716dd1 (diff)
fuzz: BIP 42, BIP 30, CVE-2018-17144
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/test/fuzz/utxo_total_supply.cpp165
2 files changed, 166 insertions, 0 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 69965ed1b8..461022bbfc 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -344,6 +344,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/txorphan.cpp \
test/fuzz/txrequest.cpp \
test/fuzz/utxo_snapshot.cpp \
+ test/fuzz/utxo_total_supply.cpp \
test/fuzz/validation_load_mempool.cpp \
test/fuzz/versionbits.cpp
endif # ENABLE_FUZZ_BINARY
diff --git a/src/test/fuzz/utxo_total_supply.cpp b/src/test/fuzz/utxo_total_supply.cpp
new file mode 100644
index 0000000000..7813b7854b
--- /dev/null
+++ b/src/test/fuzz/utxo_total_supply.cpp
@@ -0,0 +1,165 @@
+// Copyright (c) 2020 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/consensus.h>
+#include <consensus/merkle.h>
+#include <kernel/coinstats.h>
+#include <node/miner.h>
+#include <script/interpreter.h>
+#include <streams.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 <validation.h>
+#include <version.h>
+
+FUZZ_TARGET(utxo_total_supply)
+{
+ /** The testing setup that creates a chainman only (no chainstate) */
+ ChainTestingSetup test_setup{
+ CBaseChainParams::REGTEST,
+ {
+ "-testactivationheight=bip34@2",
+ },
+ };
+ // Create chainstate
+ test_setup.LoadVerifyActivateChainstate();
+ auto& node{test_setup.m_node};
+ auto& chainman{*Assert(test_setup.m_node.chainman)};
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+
+ const auto ActiveHeight = [&]() {
+ LOCK(chainman.GetMutex());
+ return chainman.ActiveHeight();
+ };
+ const auto PrepareNextBlock = [&]() {
+ // Use OP_FALSE to avoid BIP30 check from hitting early
+ auto block = PrepareBlock(node, CScript{} << OP_FALSE);
+ // Replace OP_FALSE with OP_TRUE
+ {
+ CMutableTransaction tx{*block->vtx.back()};
+ tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
+ block->vtx.back() = MakeTransactionRef(tx);
+ }
+ return block;
+ };
+
+ /** The block template this fuzzer is working on */
+ auto current_block = PrepareNextBlock();
+ /** Append-only set of tx outpoints, entries are not removed when spent */
+ std::vector<std::pair<COutPoint, CTxOut>> txos;
+ /** The utxo stats at the chain tip */
+ kernel::CCoinsStats utxo_stats;
+ /** The total amount of coins in the utxo set */
+ CAmount circulation{0};
+
+
+ // Store the tx out in the txo map
+ const auto StoreLastTxo = [&]() {
+ // get last tx
+ const CTransaction& tx = *current_block->vtx.back();
+ // get last out
+ const uint32_t i = tx.vout.size() - 1;
+ // store it
+ txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
+ if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
+ // also store coinbase
+ const uint32_t i = tx.vout.size() - 2;
+ txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
+ }
+ };
+ const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
+ const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
+ tx.vin.emplace_back(txo.first);
+ tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
+ };
+ const auto UpdateUtxoStats = [&]() {
+ LOCK(chainman.GetMutex());
+ chainman.ActiveChainstate().ForceFlushStateToDisk();
+ utxo_stats = std::move(
+ *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
+ // Check that miner can't print more money than they are allowed to
+ assert(circulation == utxo_stats.total_amount);
+ };
+
+
+ // Update internal state to chain tip
+ StoreLastTxo();
+ UpdateUtxoStats();
+ assert(ActiveHeight() == 0);
+ // Get at which height we duplicate the coinbase
+ // Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
+ // Up to 2000 seems reasonable.
+ int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 20 * COINBASE_MATURITY);
+ // Always pad with OP_0 at the end to avoid bad-cb-length error
+ const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
+ // Mine the first block with this duplicate
+ current_block = PrepareNextBlock();
+ StoreLastTxo();
+
+ {
+ // Create duplicate (CScript should match exact format as in CreateNewBlock)
+ CMutableTransaction tx{*current_block->vtx.front()};
+ tx.vin.at(0).scriptSig = duplicate_coinbase_script;
+
+ // Mine block and create next block template
+ current_block->vtx.front() = MakeTransactionRef(tx);
+ }
+ current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
+ assert(!MineBlock(node, current_block).IsNull());
+ circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
+
+ assert(ActiveHeight() == 1);
+ UpdateUtxoStats();
+ current_block = PrepareNextBlock();
+ StoreLastTxo();
+
+ LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 100'000)
+ {
+ CallOneOf(
+ fuzzed_data_provider,
+ [&] {
+ // Append an input-output pair to the last tx in the current block
+ CMutableTransaction tx{*current_block->vtx.back()};
+ AppendRandomTxo(tx);
+ current_block->vtx.back() = MakeTransactionRef(tx);
+ StoreLastTxo();
+ },
+ [&] {
+ // Append a tx to the list of txs in the current block
+ CMutableTransaction tx{};
+ AppendRandomTxo(tx);
+ current_block->vtx.push_back(MakeTransactionRef(tx));
+ StoreLastTxo();
+ },
+ [&] {
+ // Append the current block to the active chain
+ node::RegenerateCommitments(*current_block, chainman);
+ const bool was_valid = !MineBlock(node, current_block).IsNull();
+
+ const auto prev_utxo_stats = utxo_stats;
+ if (was_valid) {
+ circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
+
+ if (duplicate_coinbase_height == ActiveHeight()) {
+ // we mined the duplicate coinbase
+ assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
+ }
+ }
+
+ UpdateUtxoStats();
+
+ if (!was_valid) {
+ // utxo stats must not change
+ assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
+ }
+
+ current_block = PrepareNextBlock();
+ StoreLastTxo();
+ });
+ }
+}