mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-13 06:04:42 +02:00
Move block template defaulting into helper functions for BlockCreateOptions. FlattenMiningOptions() fills hardcoded defaults and MergeMiningOptions() overlays defaults without replacing caller-provided values. Use the shared option type in BlockAssembler so IPC callers and internal callers can pass through the same options path. This commit does not change behavior, except for dropping the "Specified " prefix from startup option error messages. Keep the -blockmintxfee ParseMoney check in ReadMiningArgs() instead of CheckMiningOptions(), because CheckMiningOptions() only sees the parsed CFeeRate value and not the original string. Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
466 lines
19 KiB
C++
466 lines
19 KiB
C++
// Copyright (c) 2021-present 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 <consensus/validation.h>
|
|
#include <node/context.h>
|
|
#include <node/mempool_args.h>
|
|
#include <node/miner.h>
|
|
#include <policy/truc_policy.h>
|
|
#include <test/fuzz/FuzzedDataProvider.h>
|
|
#include <test/fuzz/fuzz.h>
|
|
#include <test/fuzz/util.h>
|
|
#include <test/fuzz/util/mempool.h>
|
|
#include <test/util/mining.h>
|
|
#include <test/util/script.h>
|
|
#include <test/util/setup_common.h>
|
|
#include <test/util/txmempool.h>
|
|
#include <util/check.h>
|
|
#include <util/rbf.h>
|
|
#include <util/translation.h>
|
|
#include <validation.h>
|
|
#include <validationinterface.h>
|
|
|
|
using node::BlockAssembler;
|
|
using node::BlockCreateOptions;
|
|
using node::NodeContext;
|
|
using util::ToString;
|
|
|
|
namespace {
|
|
|
|
const TestingSetup* g_setup;
|
|
std::vector<COutPoint> g_outpoints_coinbase_init_mature;
|
|
std::vector<COutPoint> g_outpoints_coinbase_init_immature;
|
|
|
|
struct MockedTxPool : public CTxMemPool {
|
|
void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
|
|
{
|
|
LOCK(cs);
|
|
lastRollingFeeUpdate = GetTime();
|
|
blockSinceLastRollingFeeBump = true;
|
|
}
|
|
};
|
|
|
|
void initialize_tx_pool()
|
|
{
|
|
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
|
|
g_setup = testing_setup.get();
|
|
SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));
|
|
|
|
for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
|
|
COutPoint prevout{MineBlock(g_setup->m_node, {
|
|
.coinbase_output_script = P2WSH_OP_TRUE,
|
|
})};
|
|
// Remember the txids to avoid expensive disk access later on
|
|
auto& outpoints = i < COINBASE_MATURITY ?
|
|
g_outpoints_coinbase_init_mature :
|
|
g_outpoints_coinbase_init_immature;
|
|
outpoints.push_back(prevout);
|
|
}
|
|
g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
|
}
|
|
|
|
struct TransactionsDelta final : public CValidationInterface {
|
|
std::set<CTransactionRef>& m_removed;
|
|
std::set<CTransactionRef>& m_added;
|
|
|
|
explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a)
|
|
: m_removed{r}, m_added{a} {}
|
|
|
|
void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
|
|
{
|
|
Assert(m_added.insert(tx.info.m_tx).second);
|
|
}
|
|
|
|
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
|
|
{
|
|
Assert(m_removed.insert(tx).second);
|
|
}
|
|
};
|
|
|
|
void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider)
|
|
{
|
|
args.ForceSetArg("-limitclustercount",
|
|
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 64)));
|
|
args.ForceSetArg("-limitclustersize",
|
|
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 250)));
|
|
args.ForceSetArg("-maxmempool",
|
|
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200)));
|
|
args.ForceSetArg("-mempoolexpiry",
|
|
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)));
|
|
}
|
|
|
|
void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate)
|
|
{
|
|
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
|
|
{
|
|
BlockCreateOptions options{
|
|
.block_min_fee_rate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)},
|
|
.block_max_weight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT),
|
|
};
|
|
auto assembler = BlockAssembler{chainstate, &tx_pool, options};
|
|
auto block_template = assembler.CreateNewBlock();
|
|
Assert(block_template->block.vtx.size() >= 1);
|
|
|
|
// Try updating the mempool for this block, as though it were mined.
|
|
LOCK2(::cs_main, tx_pool.cs);
|
|
tx_pool.removeForBlock(block_template->block.vtx, chainstate.m_chain.Height() + 1);
|
|
|
|
// Now try to add those transactions back, as though a reorg happened.
|
|
std::vector<Txid> hashes_to_update;
|
|
for (const auto& tx : block_template->block.vtx) {
|
|
const auto res = AcceptToMemoryPool(chainstate, tx, GetTime(), true, /*test_accept=*/false);
|
|
if (res.m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
|
hashes_to_update.push_back(tx->GetHash());
|
|
} else {
|
|
tx_pool.removeRecursive(*tx, MemPoolRemovalReason::REORG);
|
|
}
|
|
}
|
|
tx_pool.UpdateTransactionsFromBlock(hashes_to_update);
|
|
}
|
|
const auto info_all = tx_pool.infoAll();
|
|
if (!info_all.empty()) {
|
|
const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
|
|
WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, MemPoolRemovalReason::BLOCK /* dummy */));
|
|
assert(tx_pool.size() < info_all.size());
|
|
}
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
// Try eviction
|
|
LOCK2(::cs_main, tx_pool.cs);
|
|
tx_pool.TrimToSize(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0U, tx_pool.DynamicMemoryUsage() * 2));
|
|
}
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
// Try expiry
|
|
LOCK2(::cs_main, tx_pool.cs);
|
|
tx_pool.Expire(GetMockTime() - std::chrono::seconds(fuzzed_data_provider.ConsumeIntegral<uint32_t>()));
|
|
}
|
|
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
|
|
g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
std::unique_ptr<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.check_ratio = 1;
|
|
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
|
|
|
|
// ...and construct a CTxMemPool from it
|
|
bilingual_str error;
|
|
auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
|
|
// ... ignore the error since it might be beneficial to fuzz even when the
|
|
// mempool size is unreasonably small
|
|
Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
|
|
return mempool;
|
|
}
|
|
|
|
void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, bool wtxid_in_mempool)
|
|
{
|
|
|
|
switch (res.m_result_type) {
|
|
case MempoolAcceptResult::ResultType::VALID:
|
|
{
|
|
Assert(txid_in_mempool);
|
|
Assert(wtxid_in_mempool);
|
|
Assert(res.m_state.IsValid());
|
|
Assert(!res.m_state.IsInvalid());
|
|
Assert(res.m_vsize);
|
|
Assert(res.m_base_fees);
|
|
Assert(res.m_effective_feerate);
|
|
Assert(res.m_wtxids_fee_calculations);
|
|
Assert(!res.m_other_wtxid);
|
|
break;
|
|
}
|
|
case MempoolAcceptResult::ResultType::INVALID:
|
|
{
|
|
// It may be already in the mempool since in ATMP cases we don't set MEMPOOL_ENTRY or DIFFERENT_WITNESS
|
|
Assert(!res.m_state.IsValid());
|
|
Assert(res.m_state.IsInvalid());
|
|
|
|
const bool is_reconsiderable{res.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE};
|
|
Assert(!res.m_vsize);
|
|
Assert(!res.m_base_fees);
|
|
// Fee information is provided if the failure is TX_RECONSIDERABLE.
|
|
// In other cases, validation may be unable or unwilling to calculate the fees.
|
|
Assert(res.m_effective_feerate.has_value() == is_reconsiderable);
|
|
Assert(res.m_wtxids_fee_calculations.has_value() == is_reconsiderable);
|
|
Assert(!res.m_other_wtxid);
|
|
break;
|
|
}
|
|
case MempoolAcceptResult::ResultType::MEMPOOL_ENTRY:
|
|
{
|
|
// ATMP never sets this; only set in package settings
|
|
Assert(false);
|
|
break;
|
|
}
|
|
case MempoolAcceptResult::ResultType::DIFFERENT_WITNESS:
|
|
{
|
|
// ATMP never sets this; only set in package settings
|
|
Assert(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
|
|
{
|
|
SeedRandomStateForTest(SeedRand::ZEROS);
|
|
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
|
|
std::set<COutPoint> outpoints_rbf;
|
|
// All outpoints counting toward the total supply (subset of outpoints_rbf)
|
|
std::set<COutPoint> outpoints_supply;
|
|
for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
|
|
Assert(outpoints_supply.insert(outpoint).second);
|
|
}
|
|
outpoints_rbf = outpoints_supply;
|
|
|
|
// The sum of the values of all spendable outpoints
|
|
constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN};
|
|
|
|
SetMempoolConstraints(*node.args, fuzzed_data_provider);
|
|
auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
|
|
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
|
|
|
|
chainstate.SetMempool(&tx_pool);
|
|
|
|
// Helper to query an amount
|
|
const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
|
|
const auto GetAmount = [&](const COutPoint& outpoint) {
|
|
auto coin{amount_view.GetCoin(outpoint).value()};
|
|
return coin.out.nValue;
|
|
};
|
|
|
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
|
|
{
|
|
{
|
|
// Total supply is the mempool fee + all outpoints
|
|
CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())};
|
|
for (const auto& op : outpoints_supply) {
|
|
supply_now += GetAmount(op);
|
|
}
|
|
Assert(supply_now == SUPPLY_TOTAL);
|
|
}
|
|
Assert(!outpoints_supply.empty());
|
|
|
|
// Create transaction to add to the mempool
|
|
const CTransactionRef tx = [&] {
|
|
CMutableTransaction tx_mut;
|
|
tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
|
|
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
|
|
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
|
|
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
|
|
|
|
CAmount amount_in{0};
|
|
for (int i = 0; i < num_in; ++i) {
|
|
// Pop random outpoint
|
|
auto pop = outpoints_rbf.begin();
|
|
std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1));
|
|
const auto outpoint = *pop;
|
|
outpoints_rbf.erase(pop);
|
|
amount_in += GetAmount(outpoint);
|
|
|
|
// Create input
|
|
const auto sequence = ConsumeSequence(fuzzed_data_provider);
|
|
const auto script_sig = CScript{};
|
|
const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
|
|
CTxIn in;
|
|
in.prevout = outpoint;
|
|
in.nSequence = sequence;
|
|
in.scriptSig = script_sig;
|
|
in.scriptWitness.stack = script_wit_stack;
|
|
|
|
tx_mut.vin.push_back(in);
|
|
}
|
|
|
|
// Check sigops in mempool + block template creation
|
|
bool add_sigops{fuzzed_data_provider.ConsumeBool()};
|
|
|
|
const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in);
|
|
const auto amount_out = (amount_in - amount_fee) / num_out;
|
|
for (int i = 0; i < num_out; ++i) {
|
|
if (i == 0 && add_sigops) {
|
|
tx_mut.vout.emplace_back(amount_out, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
|
|
} else {
|
|
tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
|
|
}
|
|
}
|
|
|
|
auto tx = MakeTransactionRef(tx_mut);
|
|
// Restore previously removed outpoints
|
|
for (const auto& in : tx->vin) {
|
|
Assert(outpoints_rbf.insert(in.prevout).second);
|
|
}
|
|
return tx;
|
|
}();
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
MockTime(fuzzed_data_provider, chainstate);
|
|
}
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
tx_pool.RollingFeeUpdate();
|
|
}
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
const auto& txid = fuzzed_data_provider.ConsumeBool() ?
|
|
tx->GetHash() :
|
|
PickValue(fuzzed_data_provider, outpoints_rbf).hash;
|
|
const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
|
|
tx_pool.PrioritiseTransaction(txid, delta);
|
|
}
|
|
|
|
// Remember all removed and added transactions
|
|
std::set<CTransactionRef> removed;
|
|
std::set<CTransactionRef> added;
|
|
auto txr = std::make_shared<TransactionsDelta>(removed, added);
|
|
node.validation_signals->RegisterSharedValidationInterface(txr);
|
|
|
|
// Make sure ProcessNewPackage on one transaction works.
|
|
// The result is not guaranteed to be the same as what is returned by ATMP.
|
|
const auto result_package = WITH_LOCK(::cs_main,
|
|
return ProcessNewPackage(chainstate, tx_pool, {tx}, true, /*client_maxfeerate=*/{}));
|
|
// If something went wrong due to a package-specific policy, it might not return a
|
|
// validation result for the transaction.
|
|
if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
|
|
auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
|
|
Assert(it != result_package.m_tx_results.end());
|
|
Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
|
|
it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
|
|
}
|
|
|
|
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), /*bypass_limits=*/false, /*test_accept=*/false));
|
|
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
|
|
node.validation_signals->SyncWithValidationInterfaceQueue();
|
|
node.validation_signals->UnregisterSharedValidationInterface(txr);
|
|
|
|
bool txid_in_mempool = tx_pool.exists(tx->GetHash());
|
|
bool wtxid_in_mempool = tx_pool.exists(tx->GetWitnessHash());
|
|
CheckATMPInvariants(res, txid_in_mempool, wtxid_in_mempool);
|
|
|
|
Assert(accepted != added.empty());
|
|
if (accepted) {
|
|
Assert(added.size() == 1); // For now, no package acceptance
|
|
Assert(tx == *added.begin());
|
|
CheckMempoolTRUCInvariants(tx_pool);
|
|
} else {
|
|
// Do not consider rejected transaction removed
|
|
removed.erase(tx);
|
|
}
|
|
|
|
// Helper to insert spent and created outpoints of a tx into collections
|
|
using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>;
|
|
const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) {
|
|
for (size_t i{0}; i < tx.vout.size(); ++i) {
|
|
for (auto& set : created_by_tx) {
|
|
Assert(set.get().emplace(tx.GetHash(), i).second);
|
|
}
|
|
}
|
|
for (const auto& in : tx.vin) {
|
|
for (auto& set : consumed_by_tx) {
|
|
Assert(set.get().insert(in.prevout).second);
|
|
}
|
|
}
|
|
};
|
|
// Add created outpoints, remove spent outpoints
|
|
{
|
|
// Outpoints that no longer exist at all
|
|
std::set<COutPoint> consumed_erased;
|
|
// Outpoints that no longer count toward the total supply
|
|
std::set<COutPoint> consumed_supply;
|
|
for (const auto& removed_tx : removed) {
|
|
insert_tx(/*created_by_tx=*/{consumed_erased}, /*consumed_by_tx=*/{outpoints_supply}, /*tx=*/*removed_tx);
|
|
}
|
|
for (const auto& added_tx : added) {
|
|
insert_tx(/*created_by_tx=*/{outpoints_supply, outpoints_rbf}, /*consumed_by_tx=*/{consumed_supply}, /*tx=*/*added_tx);
|
|
}
|
|
for (const auto& p : consumed_erased) {
|
|
Assert(outpoints_supply.erase(p) == 1);
|
|
Assert(outpoints_rbf.erase(p) == 1);
|
|
}
|
|
for (const auto& p : consumed_supply) {
|
|
Assert(outpoints_supply.erase(p) == 1);
|
|
}
|
|
}
|
|
}
|
|
Finish(fuzzed_data_provider, tx_pool, chainstate);
|
|
}
|
|
|
|
FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
|
|
{
|
|
SeedRandomStateForTest(SeedRand::ZEROS);
|
|
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);
|
|
|
|
std::vector<Txid> txids;
|
|
txids.reserve(g_outpoints_coinbase_init_mature.size());
|
|
for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
|
|
txids.push_back(outpoint.hash);
|
|
}
|
|
for (int i{0}; i <= 3; ++i) {
|
|
// Add some immature and non-existent outpoints
|
|
txids.push_back(g_outpoints_coinbase_init_immature.at(i).hash);
|
|
txids.push_back(Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider)));
|
|
}
|
|
|
|
SetMempoolConstraints(*node.args, fuzzed_data_provider);
|
|
auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
|
|
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
|
|
|
|
chainstate.SetMempool(&tx_pool);
|
|
|
|
// If we ever bypass limits, do not do TRUC invariants checks
|
|
bool ever_bypassed_limits{false};
|
|
|
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
|
|
{
|
|
const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
MockTime(fuzzed_data_provider, chainstate);
|
|
}
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
tx_pool.RollingFeeUpdate();
|
|
}
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
const auto txid = fuzzed_data_provider.ConsumeBool() ?
|
|
mut_tx.GetHash() :
|
|
PickValue(fuzzed_data_provider, txids);
|
|
const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
|
|
tx_pool.PrioritiseTransaction(txid, delta);
|
|
}
|
|
|
|
const bool bypass_limits{fuzzed_data_provider.ConsumeBool()};
|
|
ever_bypassed_limits |= bypass_limits;
|
|
|
|
const auto tx = MakeTransactionRef(mut_tx);
|
|
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
|
|
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
|
|
if (accepted) {
|
|
txids.push_back(tx->GetHash());
|
|
if (!ever_bypassed_limits) {
|
|
CheckMempoolTRUCInvariants(tx_pool);
|
|
}
|
|
}
|
|
}
|
|
Finish(fuzzed_data_provider, tx_pool, chainstate);
|
|
}
|
|
} // namespace
|