Branch data Line data Source code
1 : : // Copyright (c) 2023-present The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <consensus/validation.h>
6 : : #include <node/context.h>
7 : : #include <node/mempool_args.h>
8 : : #include <node/miner.h>
9 : : #include <policy/truc_policy.h>
10 : : #include <test/fuzz/FuzzedDataProvider.h>
11 : : #include <test/fuzz/fuzz.h>
12 : : #include <test/fuzz/util.h>
13 : : #include <test/fuzz/util/mempool.h>
14 : : #include <test/util/mining.h>
15 : : #include <test/util/script.h>
16 : : #include <test/util/setup_common.h>
17 : : #include <test/util/txmempool.h>
18 : : #include <util/check.h>
19 : : #include <util/rbf.h>
20 : : #include <util/translation.h>
21 : : #include <validation.h>
22 : : #include <validationinterface.h>
23 : :
24 : : using node::BlockAssembler;
25 : : using node::NodeContext;
26 : :
27 : : namespace {
28 : :
29 : : const TestingSetup* g_setup;
30 : : std::vector<COutPoint> g_outpoints_coinbase_init_mature;
31 : :
32 : : struct MockedTxPool : public CTxMemPool {
33 : 84810 : void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
34 : : {
35 : 84810 : LOCK(cs);
36 [ + - ]: 84810 : lastRollingFeeUpdate = GetTime();
37 [ + - ]: 84810 : blockSinceLastRollingFeeBump = true;
38 : 84810 : }
39 : : };
40 : :
41 : 2 : void initialize_tx_pool()
42 : : {
43 [ + - + - : 2 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
+ - ]
44 : 2 : g_setup = testing_setup.get();
45 [ + - + - ]: 6 : SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));
46 : :
47 : 2 : BlockAssembler::Options options;
48 : 2 : options.coinbase_output_script = P2WSH_EMPTY;
49 : 2 : options.include_dummy_extranonce = true;
50 : :
51 [ + + ]: 402 : for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
52 [ + - ]: 400 : COutPoint prevout{MineBlock(g_setup->m_node, options)};
53 [ + + ]: 400 : if (i < COINBASE_MATURITY) {
54 : : // Remember the txids to avoid expensive disk access later on
55 [ + - ]: 200 : g_outpoints_coinbase_init_mature.push_back(prevout);
56 : : }
57 : : }
58 [ + - ]: 2 : g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
59 : 2 : }
60 : :
61 : : struct OutpointsUpdater final : public CValidationInterface {
62 : : std::set<COutPoint>& m_mempool_outpoints;
63 : :
64 : 5924 : explicit OutpointsUpdater(std::set<COutPoint>& r)
65 : 5924 : : m_mempool_outpoints{r} {}
66 : :
67 : 227898 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
68 : : {
69 : : // for coins spent we always want to be able to rbf so they're not removed
70 : :
71 : : // outputs from this tx can now be spent
72 [ - + + + ]: 845426 : for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {
73 : 617528 : m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});
74 : : }
75 : 227898 : }
76 : :
77 : 84347 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
78 : : {
79 : : // outpoints spent by this tx are now available
80 [ + + ]: 228667 : for (const auto& input : tx->vin) {
81 : : // Could already exist if this was a replacement
82 : 144320 : m_mempool_outpoints.insert(input.prevout);
83 : : }
84 : : // outpoints created by this tx no longer exist
85 [ - + + + ]: 298831 : for (uint32_t index{0}; index < tx->vout.size(); ++index) {
86 : 214484 : m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});
87 : : }
88 : 84347 : }
89 : : };
90 : :
91 : : struct TransactionsDelta final : public CValidationInterface {
92 : : std::set<CTransactionRef>& m_added;
93 : :
94 : 335016 : explicit TransactionsDelta(std::set<CTransactionRef>& a)
95 : 335016 : : m_added{a} {}
96 : :
97 : 56284 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
98 : : {
99 : : // Transactions may be entered and booted any number of times
100 : 56284 : m_added.insert(tx.info.m_tx);
101 : 56284 : }
102 : :
103 : 45018 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
104 : : {
105 : : // Transactions may be entered and booted any number of times
106 : 45018 : m_added.erase(tx);
107 : 45018 : }
108 : : };
109 : :
110 : 146071 : void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
111 : : {
112 : 146071 : const auto time = ConsumeTime(fuzzed_data_provider,
113 : 146071 : chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
114 [ - + ]: 146071 : std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
115 : 146071 : SetMockTime(time);
116 : 146071 : }
117 : :
118 : 2894 : std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
119 : : {
120 : : // Take the default options for tests...
121 : 2894 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
122 : :
123 : :
124 : : // ...override specific options for this specific fuzz suite
125 : 2894 : mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
126 : 2894 : mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
127 : 2894 : mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
128 : 2894 : mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
129 : : // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
130 : 2894 : nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
131 : :
132 : 2894 : mempool_opts.check_ratio = 1;
133 : 2894 : mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
134 : :
135 [ + - ]: 2894 : bilingual_str error;
136 : : // ...and construct a CTxMemPool from it
137 [ + - ]: 2894 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
138 : : // ... ignore the error since it might be beneficial to fuzz even when the
139 : : // mempool size is unreasonably small
140 [ + + + - : 3552 : Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
- + ]
141 : 2894 : return mempool;
142 : 2894 : }
143 : :
144 : 3030 : std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)
145 : : {
146 : : // Take the default options for tests...
147 : 3030 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
148 : :
149 : 3030 : mempool_opts.check_ratio = 1;
150 : :
151 : : // Require standardness rules otherwise ephemeral dust is no-op
152 : 3030 : mempool_opts.require_standard = true;
153 : :
154 : : // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC
155 [ + - ]: 3030 : mempool_opts.min_relay_feerate = CFeeRate(0);
156 : :
157 [ + - ]: 3030 : bilingual_str error;
158 : : // ...and construct a CTxMemPool from it
159 [ + - ]: 3030 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
160 [ - + ]: 3030 : Assert(error.empty());
161 : 3030 : return mempool;
162 : 3030 : }
163 : :
164 : : // Scan mempool for a tx that has spent dust and return a
165 : : // prevout of the child that isn't the dusty parent itself.
166 : : // This is used to double-spend the child out of the mempool,
167 : : // leaving the parent childless.
168 : : // This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.
169 : 240336 : std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)
170 : : {
171 : 240336 : LOCK(tx_pool.cs);
172 [ + - + + ]: 9108941 : for (const auto& tx_info : tx_pool.infoAll()) {
173 [ + - - + ]: 8883398 : const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
174 [ + - ]: 8883398 : std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};
175 [ + + ]: 8883398 : if (!dust_indexes.empty()) {
176 [ + - ]: 37836 : const auto& children = tx_pool.GetChildren(entry);
177 [ + + ]: 37836 : if (!children.empty()) {
178 [ - + - + ]: 23144 : Assert(children.size() == 1);
179 : : // Find an input that doesn't spend from parent's txid
180 : 23144 : const auto& only_child = children.begin()->get().GetTx();
181 [ + + ]: 45361 : for (const auto& tx_input : only_child.vin) {
182 [ + + ]: 37010 : if (tx_input.prevout.hash != tx_info.tx->GetHash()) {
183 : 14793 : return tx_input.prevout;
184 : : }
185 : : }
186 : : }
187 : 37836 : }
188 : 9108941 : }
189 : :
190 : 225543 : return std::nullopt;
191 : 240336 : }
192 : :
193 [ + - ]: 3486 : FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
194 : : {
195 : 3030 : SeedRandomStateForTest(SeedRand::ZEROS);
196 : 3030 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
197 : 3030 : const auto& node = g_setup->m_node;
198 : 3030 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
199 : :
200 : 3030 : MockTime(fuzzed_data_provider, chainstate);
201 : :
202 : : // All RBF-spendable outpoints outside of the unsubmitted package
203 [ + - ]: 3030 : std::set<COutPoint> mempool_outpoints;
204 [ + - ]: 3030 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
205 [ + + ]: 306030 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
206 [ + - ]: 303000 : Assert(mempool_outpoints.insert(outpoint).second);
207 [ + - ]: 303000 : outpoints_value[outpoint] = 50 * COIN;
208 : : }
209 : :
210 [ + - ]: 3030 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
211 [ + - + - ]: 6060 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
212 : :
213 [ + - ]: 3030 : auto tx_pool_{MakeEphemeralMempool(node)};
214 : 3030 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
215 : :
216 : 3030 : chainstate.SetMempool(&tx_pool);
217 : :
218 [ + + + + ]: 545531 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
219 : : {
220 [ - + ]: 542501 : Assert(!mempool_outpoints.empty());
221 : :
222 : 542501 : std::vector<CTransactionRef> txs;
223 : :
224 : : // Find something we may want to double-spend with two input single tx
225 [ + + + - ]: 542501 : std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};
226 : :
227 : : // Make small packages
228 [ + + ]: 542501 : const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
229 : :
230 : 542501 : std::set<COutPoint> package_outpoints;
231 [ - + + + ]: 1746078 : while (txs.size() < num_txs) {
232 : : // Create transaction to add to the mempool
233 [ + - ]: 2407154 : txs.emplace_back([&] {
234 : 1203577 : CMutableTransaction tx_mut;
235 : 1203577 : tx_mut.version = CTransaction::CURRENT_VERSION;
236 : 1203577 : tx_mut.nLockTime = 0;
237 : : // Last transaction in a package needs to be a child of parents to get further in validation
238 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
239 : : // Note that this test currently only spends package outputs in last transaction.
240 [ + + - + : 1203577 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
241 [ + + ]: 1203577 : const auto num_in = outpoint_to_rbf ? 2 :
242 [ + + ]: 1188784 : last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) :
243 : 840348 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
244 [ + + ]: 1203577 : const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
245 : :
246 [ + + ]: 1203577 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
247 : :
248 [ + - - + : 1203577 : Assert((int)outpoints.size() >= num_in && num_in > 0);
- + ]
249 : :
250 : : CAmount amount_in{0};
251 [ + + ]: 4514545 : for (int i = 0; i < num_in; ++i) {
252 : : // Pop random outpoint. We erase them to avoid double-spending
253 : : // while in this loop, but later add them back (unless last_tx).
254 : 3310968 : auto pop = outpoints.begin();
255 : 3310968 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
256 [ + + ]: 3310968 : auto outpoint = *pop;
257 : :
258 [ + + + + ]: 3310968 : if (i == 0 && outpoint_to_rbf) {
259 : 14793 : outpoint = *outpoint_to_rbf;
260 : 14793 : outpoints.erase(outpoint);
261 : : } else {
262 : 3296175 : outpoints.erase(pop);
263 : : }
264 : : // no need to update or erase from outpoints_value
265 [ + - ]: 3310968 : amount_in += outpoints_value.at(outpoint);
266 : :
267 : : // Create input
268 : 3310968 : CTxIn in;
269 : 3310968 : in.prevout = outpoint;
270 [ + - ]: 3310968 : in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK;
271 : :
272 [ + - ]: 3310968 : tx_mut.vin.push_back(in);
273 : 3310968 : }
274 : :
275 : 1203577 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
276 : 1203577 : const auto amount_out = (amount_in - amount_fee) / num_out;
277 [ + + ]: 3955022 : for (int i = 0; i < num_out; ++i) {
278 [ + - ]: 2751445 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
279 : : }
280 : :
281 : : // Note output amounts can naturally drop to dust on their own.
282 [ + + + + ]: 1203577 : if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) {
283 : 411678 : uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out);
284 [ + - + - ]: 411678 : tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY));
285 : : }
286 : :
287 [ + - ]: 1203577 : auto tx = MakeTransactionRef(tx_mut);
288 : : // Restore previously removed outpoints, except in-package outpoints (to allow RBF)
289 [ + + ]: 1203577 : if (!last_tx) {
290 [ + + ]: 2786088 : for (const auto& in : tx->vin) {
291 [ + - ]: 3861894 : Assert(outpoints.insert(in.prevout).second);
292 : : }
293 : : // Cache the in-package outpoints being made
294 [ - + + + ]: 3025184 : for (size_t i = 0; i < tx->vout.size(); ++i) {
295 [ + - ]: 2170043 : package_outpoints.emplace(tx->GetHash(), i);
296 : : }
297 : : }
298 : : // We need newly-created values for the duration of this run
299 [ - + + + ]: 4366700 : for (size_t i = 0; i < tx->vout.size(); ++i) {
300 [ + - ]: 3163123 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
301 : : }
302 : 1203577 : return tx;
303 [ + - ]: 3610731 : }());
304 : : }
305 : :
306 [ + + ]: 542501 : if (fuzzed_data_provider.ConsumeBool()) {
307 [ + + ]: 225454 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
308 : 83319 : txs.back()->GetHash() :
309 : 142135 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
310 : 225454 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
311 : : // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't
312 : : // filter for ephemeral dust
313 [ + - + + ]: 225454 : if (tx_pool.exists(txid)) {
314 [ + - ]: 81897 : const auto tx_info{tx_pool.info(txid)};
315 [ + - + + ]: 81897 : if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) {
316 [ + - ]: 78417 : tx_pool.PrioritiseTransaction(txid, delta);
317 : : }
318 : 81897 : }
319 : : }
320 : :
321 [ - + ]: 542501 : auto single_submit = txs.size() == 1;
322 : :
323 [ + - ]: 1627503 : const auto result_package = WITH_LOCK(::cs_main,
324 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
325 : :
326 [ + - + - ]: 1627503 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
327 : : /*bypass_limits=*/false, /*test_accept=*/!single_submit));
328 : :
329 [ + + + + ]: 542501 : if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
330 : : // We don't know anything about the validity since transactions were randomly generated, so
331 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
332 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
333 [ + - ]: 211247 : const bool expect_valid{result_package.m_state.IsValid()};
334 [ + - - + ]: 211247 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
335 : : }
336 : :
337 [ + - ]: 542501 : node.validation_signals->SyncWithValidationInterfaceQueue();
338 : :
339 [ + - ]: 542501 : CheckMempoolEphemeralInvariants(tx_pool);
340 : 542501 : }
341 : :
342 [ + - + - ]: 6060 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
343 : :
344 [ + + + - : 9090 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
345 [ + - ]: 6060 : }
346 : :
347 : :
348 [ + - ]: 3350 : FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
349 : : {
350 : 2894 : SeedRandomStateForTest(SeedRand::ZEROS);
351 : 2894 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
352 : 2894 : const auto& node = g_setup->m_node;
353 : 2894 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
354 : :
355 : 2894 : MockTime(fuzzed_data_provider, chainstate);
356 : :
357 : : // All RBF-spendable outpoints outside of the unsubmitted package
358 [ + - ]: 2894 : std::set<COutPoint> mempool_outpoints;
359 [ + - ]: 2894 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
360 [ + + ]: 292294 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
361 [ + - ]: 289400 : Assert(mempool_outpoints.insert(outpoint).second);
362 [ + - ]: 289400 : outpoints_value[outpoint] = 50 * COIN;
363 : : }
364 : :
365 [ + - ]: 2894 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
366 [ + - + - ]: 5788 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
367 : :
368 [ + - ]: 2894 : auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
369 : 2894 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
370 : :
371 : 2894 : chainstate.SetMempool(&tx_pool);
372 : :
373 [ + + + + ]: 337910 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
374 : : {
375 [ - + ]: 335016 : Assert(!mempool_outpoints.empty());
376 : :
377 : 335016 : std::vector<CTransactionRef> txs;
378 : :
379 : : // Make packages of 1-to-26 transactions
380 : 335016 : const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26);
381 : 335016 : std::set<COutPoint> package_outpoints;
382 [ - + + + ]: 1341775 : while (txs.size() < num_txs) {
383 : : // Create transaction to add to the mempool
384 [ + - ]: 2013518 : txs.emplace_back([&] {
385 : 1006759 : CMutableTransaction tx_mut;
386 [ + + ]: 1006759 : tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
387 [ + + ]: 1006759 : tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
388 : : // Last transaction in a package needs to be a child of parents to get further in validation
389 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
390 : : // Note that this test currently only spends package outputs in last transaction.
391 [ + + - + : 1006759 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
392 : 1006759 : const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
393 : 1006759 : auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
394 : :
395 [ + + ]: 1006759 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
396 : :
397 [ - + ]: 1006759 : Assert(!outpoints.empty());
398 : :
399 : : CAmount amount_in{0};
400 [ + + ]: 19302949 : for (size_t i = 0; i < num_in; ++i) {
401 : : // Pop random outpoint. We erase them to avoid double-spending
402 : : // while in this loop, but later add them back (unless last_tx).
403 : 18296190 : auto pop = outpoints.begin();
404 : 18296190 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
405 : 18296190 : const auto outpoint = *pop;
406 : 18296190 : outpoints.erase(pop);
407 : : // no need to update or erase from outpoints_value
408 [ + - ]: 18296190 : amount_in += outpoints_value.at(outpoint);
409 : :
410 : : // Create input
411 : 18296190 : const auto sequence = ConsumeSequence(fuzzed_data_provider);
412 : 18296190 : const auto script_sig = CScript{};
413 [ + + + - ]: 28989530 : const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
414 : :
415 : 18296190 : CTxIn in;
416 : 18296190 : in.prevout = outpoint;
417 : 18296190 : in.nSequence = sequence;
418 : 18296190 : in.scriptSig = script_sig;
419 [ + - ]: 18296190 : in.scriptWitness.stack = script_wit_stack;
420 : :
421 [ + - ]: 18296190 : tx_mut.vin.push_back(in);
422 : 18296190 : }
423 : :
424 : : // Duplicate an input
425 : 1006759 : bool dup_input = fuzzed_data_provider.ConsumeBool();
426 [ + + ]: 1006759 : if (dup_input) {
427 [ + - ]: 356638 : tx_mut.vin.push_back(tx_mut.vin.back());
428 : : }
429 : :
430 : : // Refer to a non-existent input
431 [ + + ]: 1006759 : if (fuzzed_data_provider.ConsumeBool()) {
432 [ + - ]: 309115 : tx_mut.vin.emplace_back();
433 : : }
434 : :
435 : : // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent
436 [ + + + + ]: 1006759 : if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
437 [ + - + - : 153486 : tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
+ - ]
438 : : // Don't add any other outputs.
439 : 51162 : num_out = 1;
440 : 51162 : amount_in -= 1000;
441 : : }
442 : :
443 : 1006759 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
444 : 1006759 : const auto amount_out = (amount_in - amount_fee) / num_out;
445 [ + + ]: 18906102 : for (int i = 0; i < num_out; ++i) {
446 [ + - ]: 17899343 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
447 : : }
448 [ + - ]: 1006759 : auto tx = MakeTransactionRef(tx_mut);
449 : : // Restore previously removed outpoints, except in-package outpoints
450 [ + + ]: 1006759 : if (!last_tx) {
451 [ + + ]: 10378945 : for (const auto& in : tx->vin) {
452 : : // It's a fake input, or a new input, or a duplicate
453 [ + + + - : 19314754 : Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
+ + + - -
+ ]
454 : : }
455 : : // Cache the in-package outpoints being made
456 [ - + + + ]: 17384586 : for (size_t i = 0; i < tx->vout.size(); ++i) {
457 [ + - ]: 16503772 : package_outpoints.emplace(tx->GetHash(), i);
458 : : }
459 : : }
460 : : // We need newly-created values for the duration of this run
461 [ - + + + ]: 18957264 : for (size_t i = 0; i < tx->vout.size(); ++i) {
462 [ + - ]: 17950505 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
463 : : }
464 : 1006759 : return tx;
465 [ + - ]: 3020277 : }());
466 : : }
467 : :
468 [ + + ]: 335016 : if (fuzzed_data_provider.ConsumeBool()) {
469 [ + - ]: 140147 : MockTime(fuzzed_data_provider, chainstate);
470 : : }
471 [ + + ]: 335016 : if (fuzzed_data_provider.ConsumeBool()) {
472 [ + - ]: 84810 : tx_pool.RollingFeeUpdate();
473 : : }
474 [ + + ]: 335016 : if (fuzzed_data_provider.ConsumeBool()) {
475 [ + + ]: 118872 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
476 : 54419 : txs.back()->GetHash() :
477 : 64453 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
478 : 118872 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
479 [ + - ]: 118872 : tx_pool.PrioritiseTransaction(txid, delta);
480 : : }
481 : :
482 : : // Remember all added transactions
483 [ + - ]: 335016 : std::set<CTransactionRef> added;
484 [ + - ]: 335016 : auto txr = std::make_shared<TransactionsDelta>(added);
485 [ + - + - ]: 670032 : node.validation_signals->RegisterSharedValidationInterface(txr);
486 : :
487 : : // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
488 : : // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
489 : : // (the package is a test accept and ATMP is a submission).
490 [ - + + + : 544087 : auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
+ + ]
491 : :
492 : : // Exercise client_maxfeerate logic
493 : 335016 : std::optional<CFeeRate> client_maxfeerate{};
494 [ + + ]: 335016 : if (fuzzed_data_provider.ConsumeBool()) {
495 [ + - ]: 77472 : client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
496 : : }
497 : :
498 [ + - ]: 1005048 : const auto result_package = WITH_LOCK(::cs_main,
499 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
500 : :
501 : : // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
502 : : // can be a source of divergence.
503 [ + - + - ]: 1005048 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
504 : : /*bypass_limits=*/false, /*test_accept=*/!single_submit));
505 : 335016 : const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
506 : :
507 [ + - ]: 335016 : node.validation_signals->SyncWithValidationInterfaceQueue();
508 [ + - + - ]: 670032 : node.validation_signals->UnregisterSharedValidationInterface(txr);
509 : :
510 : : // There is only 1 transaction in the package. We did a test-package-accept and a ATMP
511 [ + + ]: 335016 : if (single_submit) {
512 [ - + ]: 37150 : Assert(passed != added.empty());
513 [ - + ]: 37150 : Assert(passed == res.m_state.IsValid());
514 [ + + ]: 37150 : if (passed) {
515 [ - + ]: 4148 : Assert(added.size() == 1);
516 [ - + ]: 4148 : Assert(txs.back() == *added.begin());
517 : : }
518 [ + + ]: 297866 : } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
519 : : // We don't know anything about the validity since transactions were randomly generated, so
520 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
521 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
522 [ + - ]: 233400 : const bool expect_valid{result_package.m_state.IsValid()};
523 [ + - - + ]: 233400 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
524 : : } else {
525 : : // This is empty if it fails early checks, or "full" if transactions are looked at deeper
526 [ - + + + : 122089 : Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
+ - - + ]
527 : : }
528 : :
529 [ + - ]: 335016 : CheckMempoolTRUCInvariants(tx_pool);
530 : :
531 : : // Dust checks only make sense when dust is enforced
532 [ + + ]: 335016 : if (tx_pool.m_opts.require_standard) {
533 [ + - ]: 172370 : CheckMempoolEphemeralInvariants(tx_pool);
534 : : }
535 [ + - ]: 670032 : }
536 : :
537 [ + - + - ]: 5788 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
538 : :
539 [ + + + - : 8682 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
540 [ + - ]: 5788 : }
541 : : } // namespace
|