Skip to content

Instantly share code, notes, and snippets.

@instagibbs
Created December 15, 2023 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save instagibbs/c5cb0796ceec81f0374ae614f8cdab7f to your computer and use it in GitHub Desktop.
Save instagibbs/c5cb0796ceec81f0374ae614f8cdab7f to your computer and use it in GitHub Desktop.
diff --git a/src/test/fuzz/package_eval.cpp b/src/test/fuzz/package_eval.cpp
index 9c64c39699..989373b044 100644
--- a/src/test/fuzz/package_eval.cpp
+++ b/src/test/fuzz/package_eval.cpp
@@ -98,51 +98,52 @@ struct TransactionsDelta final : public CValidationInterface {
m_added.erase(tx);
}
};
void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
{
const auto time = ConsumeTime(fuzzed_data_provider,
chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
SetMockTime(time);
}
CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
{
// Take the default options for tests...
CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
// ...override specific options for this specific fuzz suite
mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
mempool_opts.limits.ancestor_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
- nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999);
+ // Make them free or cost a single legacy sigop >> 1kVB
+ nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
mempool_opts.check_ratio = 1;
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
// ...and construct a CTxMemPool from it
return CTxMemPool{mempool_opts};
}
FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto& node = g_setup->m_node;
auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
MockTime(fuzzed_data_provider, chainstate);
// All RBF-spendable outpoints outside of the unsubmitted package
std::set<COutPoint> mempool_outpoints;
std::map<COutPoint, CAmount> outpoints_value;
for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
Assert(mempool_outpoints.insert(outpoint).second);
outpoints_value[outpoint] = 50 * COIN;
}
auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
@@ -195,56 +196,62 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
// Create input
const auto sequence = ConsumeSequence(fuzzed_data_provider);
const auto script_sig = CScript{};
const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
CTxIn in;
in.prevout = outpoint;
in.nSequence = sequence;
in.scriptSig = script_sig;
in.scriptWitness.stack = script_wit_stack;
tx_mut.vin.push_back(in);
}
// Duplicate an input
bool dup_input = fuzzed_data_provider.ConsumeBool();
if (dup_input) {
tx_mut.vin.push_back(tx_mut.vin.back());
}
// Refer to a non-existent input
if (fuzzed_data_provider.ConsumeBool()) {
tx_mut.vin.emplace_back();
}
+ // Make a p2pk output to make sigops adjusted vsize to violate v3, potentially, which is never spent
+ if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
+ //num_out = 1; // let the vsize hit come in input non-adjusted size
+ tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
+ amount_in -= 1000;
+ }
+
const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
const auto amount_out = (amount_in - amount_fee) / num_out;
for (int i = 0; i < num_out; ++i) {
tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
}
- // TODO vary transaction sizes to catch size-related issues
auto tx = MakeTransactionRef(tx_mut);
// Restore previously removed outpoints, except in-package outpoints
if (!last_tx) {
for (const auto& in : tx->vin) {
// It's a fake input, or a new input, or a duplicate
Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
}
// Cache the in-package outpoints being made
for (size_t i = 0; i < tx->vout.size(); ++i) {
package_outpoints.emplace(tx->GetHash(), i);
}
}
// We need newly-created values for the duration of this run
for (size_t i = 0; i < tx->vout.size(); ++i) {
outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
}
return tx;
}();
txs.push_back(tx);
}
if (fuzzed_data_provider.ConsumeBool()) {
MockTime(fuzzed_data_provider, chainstate);
}
if (fuzzed_data_provider.ConsumeBool()) {
diff --git a/src/validation.cpp b/src/validation.cpp
index 9120f96eb9..dbf1769927 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -944,51 +944,51 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// This allows protocols which rely on distrusting counterparties
// being able to broadcast descendants of an unconfirmed transaction
// to be secure by simply only having two immediately-spendable
// outputs - one for each counterparty. For more info on the uses for
// this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
CTxMemPool::Limits cpfp_carve_out_limits{
.ancestor_count = 2,
.ancestor_size_vbytes = maybe_rbf_limits.ancestor_size_vbytes,
.descendant_count = maybe_rbf_limits.descendant_count + 1,
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
};
const auto error_message{util::ErrorString(ancestors).original};
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits);
if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
ws.m_ancestors = *ancestors;
// Even though just checking direct mempool parents for inheritance would be sufficient, we
// check using the full ancestor set here because it's more convenient to use what we have
// already calculated.
CTxMemPool::setEntries collective_ancestors = *ancestors;
collective_ancestors.merge(ws.m_ancestors_of_in_package_ancestors);
- if (const auto err_string{ApplyV3Rules(ws.m_ptx, collective_ancestors, ws.m_num_in_package_ancestors, ws.m_conflicts, ws.m_vsize)}) {
+ if (const auto err_string{ApplyV3Rules(ws.m_ptx, collective_ancestors, ws.m_num_in_package_ancestors, ws.m_conflicts, entry->GetTxWeight()/4)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "v3-rule-violation", *err_string);
}
// A transaction that spends outputs that would be replaced by it is invalid. Now
// that we have the set of all ancestors we can detect this
// pathological case by making sure ws.m_conflicts and ws.m_ancestors don't
// intersect.
if (const auto err_string{EntriesAndTxidsDisjoint(ws.m_ancestors, ws.m_conflicts, hash)}) {
// We classify this as a consensus error because a transaction depending on something it
// conflicts with would be inconsistent.
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string);
}
m_rbf = !ws.m_conflicts.empty();
return true;
}
bool MemPoolAccept::ReplacementChecks(Workspace& ws)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
const CTransaction& tx = *ws.m_ptx;
const uint256& hash = ws.m_hash;
TxValidationState& state = ws.m_state;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment