29 std::vector<COutPoint> g_outpoints_coinbase_init_mature;
35 lastRollingFeeUpdate =
GetTime();
36 blockSinceLastRollingFeeBump =
true;
40 void initialize_tx_pool()
42 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
43 g_setup = testing_setup.get();
49 g_outpoints_coinbase_init_mature.push_back(prevout);
52 g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
56 std::set<COutPoint>& m_mempool_outpoints;
58 explicit OutpointsUpdater(std::set<COutPoint>& r)
59 : m_mempool_outpoints{r} {}
66 for (uint32_t index{0}; index < tx.
info.
m_tx->vout.size(); ++index) {
74 for (
const auto& input : tx->vin) {
76 m_mempool_outpoints.insert(input.prevout);
79 for (uint32_t index{0}; index < tx->vout.size(); ++index) {
80 m_mempool_outpoints.erase(
COutPoint{tx->GetHash(), index});
86 std::set<CTransactionRef>& m_added;
88 explicit TransactionsDelta(std::set<CTransactionRef>& a)
106 const auto time =
ConsumeTime(fuzzed_data_provider,
108 std::numeric_limits<decltype(chainstate.
m_chain.
Tip()->
nTime)>::max());
120 mempool_opts.limits.ancestor_size_vbytes = fuzzed_data_provider.
ConsumeIntegralInRange<
unsigned>(0, 202) * 1
'000; 121 mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50); 122 mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
128 mempool_opts.check_ratio = 1; 129 mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool(); 132 // ...and construct a CTxMemPool from it 133 auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)}; 134 // ... ignore the error since it might be beneficial to fuzz even when the 135 // mempool size is unreasonably small 136 Assert(error.empty() || error.original.starts_with("-maxmempool must be at least ")); 140 FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool) 142 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 143 const auto& node = g_setup->m_node; 144 auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())}; 146 MockTime(fuzzed_data_provider, chainstate); 148 // All RBF-spendable outpoints outside of the unsubmitted package 149 std::set<COutPoint> mempool_outpoints; 150 std::map<COutPoint, CAmount> outpoints_value; 151 for (const auto& outpoint : g_outpoints_coinbase_init_mature) { 152 Assert(mempool_outpoints.insert(outpoint).second); 153 outpoints_value[outpoint] = 50 * COIN; 156 auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints); 157 node.validation_signals->RegisterSharedValidationInterface(outpoints_updater); 159 auto tx_pool_{MakeMempool(fuzzed_data_provider, node)}; 160 MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get()); 162 chainstate.SetMempool(&tx_pool); 164 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300) 166 Assert(!mempool_outpoints.empty()); 168 std::vector<CTransactionRef> txs; 170 // Make packages of 1-to-26 transactions 171 const auto num_txs = (size_t) fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 26); 172 std::set<COutPoint> package_outpoints; 173 while (txs.size() < num_txs) { 175 // Last transaction in a package needs to be a child of parents to get further in validation 176 // so the last transaction to be generated(in a >1 package) must spend all package-made outputs 177 // Note that this test currently only spends package outputs in last transaction. 178 bool last_tx = num_txs > 1 && txs.size() == num_txs - 1; 180 // Create transaction to add to the mempool 181 const CTransactionRef tx = [&] { 182 CMutableTransaction tx_mut; 183 tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION; 184 tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); 185 // Last tx will sweep all outpoints in package 186 const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size()); 187 auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2); 189 auto& outpoints = last_tx ? package_outpoints : mempool_outpoints; 191 Assert(!outpoints.empty()); 193 CAmount amount_in{0}; 194 for (size_t i = 0; i < num_in; ++i) { 195 // Pop random outpoint 196 auto pop = outpoints.begin(); 197 std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1)); 198 const auto outpoint = *pop; 199 outpoints.erase(pop); 200 // no need to update or erase from outpoints_value 201 amount_in += outpoints_value.at(outpoint); 204 const auto sequence = ConsumeSequence(fuzzed_data_provider); 205 const auto script_sig = CScript{}; 206 const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK; 209 in.prevout = outpoint; 210 in.nSequence = sequence; 211 in.scriptSig = script_sig; 212 in.scriptWitness.stack = script_wit_stack; 214 tx_mut.vin.push_back(in); 217 // Duplicate an input 218 bool dup_input = fuzzed_data_provider.ConsumeBool(); 220 tx_mut.vin.push_back(tx_mut.vin.back()); 223 // Refer to a non-existent input 224 if (fuzzed_data_provider.ConsumeBool()) { 225 tx_mut.vin.emplace_back(); 228 // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent 229 if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) { 230 tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG); 231 // Don't add any other outputs.
237 const auto amount_out = (amount_in - amount_fee) / num_out;
238 for (
int i = 0; i < num_out; ++i) {
244 for (
const auto& in : tx->vin) {
246 Assert(in ==
CTxIn() || outpoints.insert(in.prevout).second || dup_input);
249 for (
size_t i = 0; i < tx->vout.size(); ++i) {
250 package_outpoints.emplace(tx->GetHash(), i);
254 for (
size_t i = 0; i < tx->vout.size(); ++i) {
255 outpoints_value[
COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
263 MockTime(fuzzed_data_provider, chainstate);
266 tx_pool.RollingFeeUpdate();
269 const auto& txid = fuzzed_data_provider.
ConsumeBool() ?
270 txs.back()->GetHash() :
271 PickValue(fuzzed_data_provider, mempool_outpoints).hash;
273 tx_pool.PrioritiseTransaction(txid.ToUint256(), delta);
277 std::set<CTransactionRef> added;
278 auto txr = std::make_shared<TransactionsDelta>(added);
279 node.validation_signals->RegisterSharedValidationInterface(txr);
284 auto single_submit = txs.size() == 1 && fuzzed_data_provider.
ConsumeBool();
287 std::optional<CFeeRate> client_maxfeerate{};
293 return ProcessNewPackage(chainstate, tx_pool, txs, single_submit, client_maxfeerate));
298 false, !single_submit));
301 node.validation_signals->SyncWithValidationInterfaceQueue();
302 node.validation_signals->UnregisterSharedValidationInterface(txr);
306 Assert(passed != added.empty());
307 Assert(passed == res.m_state.IsValid());
309 Assert(added.size() == 1);
310 Assert(txs.back() == *added.begin());
316 const bool expect_valid{result_package.m_state.IsValid()};
320 Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
326 node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
std::shared_ptr< const CTransaction > CTransactionRef
CCoinsViewCache & CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(
virtual void TransactionRemovedFromMempool(const CTransactionRef &tx, MemPoolRemovalReason reason, uint64_t mempool_sequence)
Notifies listeners of a transaction leaving mempool.
The package itself is invalid (e.g. too many transactions).
MemPoolRemovalReason
Reason why a transaction was removed from the mempool, this is passed to the notification signal...
int Height() const
Return the maximal height in the chain.
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule) ...
MempoolAcceptResult AcceptToMemoryPool(Chainstate &active_chainstate, const CTransactionRef &tx, int64_t accept_time, bool bypass_limits, bool test_accept)
Try to add a transaction to the mempool.
CChain m_chain
The current chain of blockheaders we consult and build on.
virtual void TransactionAddedToMempool(const NewMempoolTransactionInfo &tx, uint64_t mempool_sequence)
Notifies listeners of a transaction having been added to mempool.
Implement this to subscribe to events generated in validation and mempool.
int64_t CAmount
Amount in satoshis (Can be negative)
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
NodeContext struct containing references to chain state and connection state.
Chainstate stores and provides an API to update our local knowledge of the current best chain...
An input of a transaction.
std::optional< std::string > CheckPackageMempoolAcceptResult(const Package &txns, const PackageMempoolAcceptResult &result, bool expect_valid, const CTxMemPool *mempool)
Check expected properties for every PackageMempoolAcceptResult, regardless of value.
An outpoint - a combination of a transaction hash and an index n into its vout.
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
int64_t GetMedianTimePast() const
static CTransactionRef MakeTransactionRef(Tx &&txIn)
COutPoint MineBlock(const NodeContext &node, const CScript &coinbase_scriptPubKey)
Returns the generated coin.
static const CScript P2WSH_EMPTY
#define EXCLUSIVE_LOCKS_REQUIRED(...)
void CheckMempoolTRUCInvariants(const CTxMemPool &tx_pool)
For every transaction in tx_pool, check TRUC invariants:
unsigned int nBytesPerSigOp
int64_t ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
PackageMempoolAcceptResult ProcessNewPackage(Chainstate &active_chainstate, CTxMemPool &pool, const Package &package, bool test_accept, const std::optional< CFeeRate > &client_maxfeerate)
Validate (and maybe submit) a package to the mempool.
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
const CTransactionRef m_tx
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
Options struct containing options for constructing a CTxMemPool.
auto & PickValue(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
int64_t ancestor_count
The maximum allowed number of transactions in a package including the entry and its ancestors...
T ConsumeIntegralInRange(T min, T max)
int64_t GetTime()
DEPRECATED, see GetTime.
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate...
Testing setup that configures a complete environment.
#define Assert(val)
Identity function.
CTxMemPool::Options MemPoolOptionsForTest(const NodeContext &node)
static constexpr CAmount COIN
The amount of satoshis in one BTC.