Branch data Line data Source code
1 : : // Copyright (c) 2023 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 : 67456 : void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
34 : : {
35 : 67456 : LOCK(cs);
36 [ + - ]: 67456 : lastRollingFeeUpdate = GetTime();
37 [ + - ]: 67456 : blockSinceLastRollingFeeBump = true;
38 : 67456 : }
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 : :
50 [ + + ]: 402 : for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
51 [ + - ]: 400 : COutPoint prevout{MineBlock(g_setup->m_node, options)};
52 [ + + ]: 400 : if (i < COINBASE_MATURITY) {
53 : : // Remember the txids to avoid expensive disk access later on
54 [ + - ]: 200 : g_outpoints_coinbase_init_mature.push_back(prevout);
55 : : }
56 : : }
57 [ + - ]: 2 : g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
58 : 2 : }
59 : :
60 : : struct OutpointsUpdater final : public CValidationInterface {
61 : : std::set<COutPoint>& m_mempool_outpoints;
62 : :
63 : 4148 : explicit OutpointsUpdater(std::set<COutPoint>& r)
64 : 4148 : : m_mempool_outpoints{r} {}
65 : :
66 : 97695 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
67 : : {
68 : : // for coins spent we always want to be able to rbf so they're not removed
69 : :
70 : : // outputs from this tx can now be spent
71 [ - + + + ]: 412250 : for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {
72 : 314555 : m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});
73 : : }
74 : 97695 : }
75 : :
76 : 37439 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
77 : : {
78 : : // outpoints spent by this tx are now available
79 [ + + ]: 98651 : for (const auto& input : tx->vin) {
80 : : // Could already exist if this was a replacement
81 : 61212 : m_mempool_outpoints.insert(input.prevout);
82 : : }
83 : : // outpoints created by this tx no longer exist
84 [ - + + + ]: 152200 : for (uint32_t index{0}; index < tx->vout.size(); ++index) {
85 : 114761 : m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});
86 : : }
87 : 37439 : }
88 : : };
89 : :
90 : : struct TransactionsDelta final : public CValidationInterface {
91 : : std::set<CTransactionRef>& m_added;
92 : :
93 : 259663 : explicit TransactionsDelta(std::set<CTransactionRef>& a)
94 : 259663 : : m_added{a} {}
95 : :
96 : 29043 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
97 : : {
98 : : // Transactions may be entered and booted any number of times
99 : 29043 : m_added.insert(tx.info.m_tx);
100 : 29043 : }
101 : :
102 : 22900 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
103 : : {
104 : : // Transactions may be entered and booted any number of times
105 : 22900 : m_added.erase(tx);
106 : 22900 : }
107 : : };
108 : :
109 : 92369 : void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
110 : : {
111 : 92369 : const auto time = ConsumeTime(fuzzed_data_provider,
112 : 92369 : chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
113 [ - + ]: 92369 : std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
114 : 92369 : SetMockTime(time);
115 : 92369 : }
116 : :
117 : 2246 : std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
118 : : {
119 : : // Take the default options for tests...
120 : 2246 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
121 : :
122 : :
123 : : // ...override specific options for this specific fuzz suite
124 : 2246 : mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
125 : 2246 : mempool_opts.limits.ancestor_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
126 : 2246 : mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
127 : 2246 : mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
128 : 2246 : mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
129 : 2246 : mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
130 : : // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
131 : 2246 : nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
132 : :
133 : 2246 : mempool_opts.check_ratio = 1;
134 : 2246 : mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
135 : :
136 [ + - ]: 2246 : bilingual_str error;
137 : : // ...and construct a CTxMemPool from it
138 [ + - ]: 2246 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
139 : : // ... ignore the error since it might be beneficial to fuzz even when the
140 : : // mempool size is unreasonably small
141 [ + + + - : 2850 : Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
+ - ]
142 : 2246 : return mempool;
143 : 2246 : }
144 : :
145 : 1902 : std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)
146 : : {
147 : : // Take the default options for tests...
148 : 1902 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
149 : :
150 : 1902 : mempool_opts.check_ratio = 1;
151 : :
152 : : // Require standardness rules otherwise ephemeral dust is no-op
153 : 1902 : mempool_opts.require_standard = true;
154 : :
155 : : // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC
156 [ + - ]: 1902 : mempool_opts.min_relay_feerate = CFeeRate(0);
157 : :
158 [ + - ]: 1902 : bilingual_str error;
159 : : // ...and construct a CTxMemPool from it
160 [ + - ]: 1902 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
161 [ + - ]: 1902 : Assert(error.empty());
162 : 1902 : return mempool;
163 : 1902 : }
164 : :
165 : : // Scan mempool for a tx that has spent dust and return a
166 : : // prevout of the child that isn't the dusty parent itself.
167 : : // This is used to double-spend the child out of the mempool,
168 : : // leaving the parent childless.
169 : : // This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.
170 : 153891 : std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)
171 : : {
172 : 153891 : LOCK(tx_pool.cs);
173 [ + - + + ]: 3890424 : for (const auto& tx_info : tx_pool.infoAll()) {
174 [ + - + - ]: 3748481 : const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
175 [ + - ]: 3748481 : std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};
176 [ + + ]: 3748481 : if (!dust_indexes.empty()) {
177 [ + + ]: 31088 : const auto& children = entry.GetMemPoolChildrenConst();
178 [ + + ]: 31088 : if (!children.empty()) {
179 [ + - ]: 18782 : Assert(children.size() == 1);
180 : : // Find an input that doesn't spend from parent's txid
181 : 18782 : const auto& only_child = children.begin()->get().GetTx();
182 [ + + ]: 47022 : for (const auto& tx_input : only_child.vin) {
183 [ + + ]: 40188 : if (tx_input.prevout.hash != tx_info.tx->GetHash()) {
184 : 11948 : return tx_input.prevout;
185 : : }
186 : : }
187 : : }
188 : : }
189 : 3890424 : }
190 : :
191 : 141943 : return std::nullopt;
192 : 153891 : }
193 : :
194 [ + - ]: 2358 : FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
195 : : {
196 : 1902 : SeedRandomStateForTest(SeedRand::ZEROS);
197 : 1902 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
198 : 1902 : const auto& node = g_setup->m_node;
199 : 1902 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
200 : :
201 : 1902 : MockTime(fuzzed_data_provider, chainstate);
202 : :
203 : : // All RBF-spendable outpoints outside of the unsubmitted package
204 [ + - ]: 1902 : std::set<COutPoint> mempool_outpoints;
205 [ + - ]: 1902 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
206 [ + + ]: 192102 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
207 [ + - + - ]: 190200 : Assert(mempool_outpoints.insert(outpoint).second);
208 [ + - ]: 190200 : outpoints_value[outpoint] = 50 * COIN;
209 : : }
210 : :
211 [ + - ]: 1902 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
212 [ + - + - ]: 3804 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
213 : :
214 [ + - ]: 1902 : auto tx_pool_{MakeEphemeralMempool(node)};
215 : 1902 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
216 : :
217 : 1902 : chainstate.SetMempool(&tx_pool);
218 : :
219 [ + + + + ]: 277524 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
220 : : {
221 [ + - ]: 275622 : Assert(!mempool_outpoints.empty());
222 : :
223 : 275622 : std::vector<CTransactionRef> txs;
224 : :
225 : : // Find something we may want to double-spend with two input single tx
226 [ + + + - ]: 275622 : std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};
227 : :
228 : : // Make small packages
229 [ + + ]: 275622 : const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
230 : :
231 : 275622 : std::set<COutPoint> package_outpoints;
232 [ - + + + ]: 869718 : while (txs.size() < num_txs) {
233 : : // Create transaction to add to the mempool
234 [ + - ]: 1188192 : txs.emplace_back([&] {
235 : 594096 : CMutableTransaction tx_mut;
236 : 594096 : tx_mut.version = CTransaction::CURRENT_VERSION;
237 : 594096 : tx_mut.nLockTime = 0;
238 : : // Last transaction in a package needs to be a child of parents to get further in validation
239 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
240 : : // Note that this test currently only spends package outputs in last transaction.
241 [ + + - + : 594096 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
242 [ + + ]: 594096 : const auto num_in = outpoint_to_rbf ? 2 :
243 [ + + ]: 582148 : last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) :
244 : 408041 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
245 [ + + ]: 594096 : const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
246 : :
247 [ + + ]: 594096 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
248 : :
249 [ + - - + : 594096 : Assert((int)outpoints.size() >= num_in && num_in > 0);
+ - ]
250 : :
251 : : CAmount amount_in{0};
252 [ + + ]: 2268186 : for (int i = 0; i < num_in; ++i) {
253 : : // Pop random outpoint. We erase them to avoid double-spending
254 : : // while in this loop, but later add them back (unless last_tx).
255 : 1674090 : auto pop = outpoints.begin();
256 : 1674090 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
257 [ + + ]: 1674090 : auto outpoint = *pop;
258 : :
259 [ + + + + ]: 1674090 : if (i == 0 && outpoint_to_rbf) {
260 : 11948 : outpoint = *outpoint_to_rbf;
261 : 11948 : outpoints.erase(outpoint);
262 : : } else {
263 : 1662142 : outpoints.erase(pop);
264 : : }
265 : : // no need to update or erase from outpoints_value
266 [ + - ]: 1674090 : amount_in += outpoints_value.at(outpoint);
267 : :
268 : : // Create input
269 : 1674090 : CTxIn in;
270 : 1674090 : in.prevout = outpoint;
271 [ + - ]: 1674090 : in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK;
272 : :
273 [ + - ]: 1674090 : tx_mut.vin.push_back(in);
274 : 1674090 : }
275 : :
276 : 594096 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
277 : 594096 : const auto amount_out = (amount_in - amount_fee) / num_out;
278 [ + + ]: 1980638 : for (int i = 0; i < num_out; ++i) {
279 [ + - ]: 1386542 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
280 : : }
281 : :
282 : : // Note output amounts can naturally drop to dust on their own.
283 [ + + + + ]: 594096 : if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) {
284 : 215619 : uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out);
285 [ + - + - ]: 215619 : tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY));
286 : : }
287 : :
288 [ + - ]: 594096 : auto tx = MakeTransactionRef(tx_mut);
289 : : // Restore previously removed outpoints, except in-package outpoints (to allow RBF)
290 [ + + ]: 594096 : if (!last_tx) {
291 [ + + ]: 1383822 : for (const auto& in : tx->vin) {
292 [ + - + - ]: 963833 : Assert(outpoints.insert(in.prevout).second);
293 : : }
294 : : // Cache the in-package outpoints being made
295 [ - + + + ]: 1530253 : for (size_t i = 0; i < tx->vout.size(); ++i) {
296 [ + - ]: 1110264 : package_outpoints.emplace(tx->GetHash(), i);
297 : : }
298 : : }
299 : : // We need newly-created values for the duration of this run
300 [ - + + + ]: 2196257 : for (size_t i = 0; i < tx->vout.size(); ++i) {
301 [ + - ]: 1602161 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
302 : : }
303 : 594096 : return tx;
304 [ + - ]: 1782288 : }());
305 : : }
306 : :
307 [ + + ]: 275622 : if (fuzzed_data_provider.ConsumeBool()) {
308 [ + + ]: 105572 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
309 : 61753 : txs.back()->GetHash() :
310 : 43819 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
311 : 105572 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
312 : : // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't
313 : : // filter for ephemeral dust
314 [ + - + + ]: 105572 : if (tx_pool.exists(txid)) {
315 [ + - ]: 20578 : const auto tx_info{tx_pool.info(txid)};
316 [ + - + + ]: 20578 : if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) {
317 [ + - ]: 19822 : tx_pool.PrioritiseTransaction(txid, delta);
318 : : }
319 : 20578 : }
320 : : }
321 : :
322 [ - + ]: 275622 : auto single_submit = txs.size() == 1;
323 : :
324 [ + - ]: 826866 : const auto result_package = WITH_LOCK(::cs_main,
325 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
326 : :
327 [ + - + - ]: 826866 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
328 : : /*bypass_limits=*/fuzzed_data_provider.ConsumeBool(), /*test_accept=*/!single_submit));
329 : :
330 [ + + + + ]: 275622 : if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
331 : : // We don't know anything about the validity since transactions were randomly generated, so
332 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
333 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
334 [ + - ]: 103107 : const bool expect_valid{result_package.m_state.IsValid()};
335 [ + - + - ]: 206214 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
336 : : }
337 : :
338 [ + - ]: 275622 : node.validation_signals->SyncWithValidationInterfaceQueue();
339 : :
340 [ + - ]: 275622 : CheckMempoolEphemeralInvariants(tx_pool);
341 : 275622 : }
342 : :
343 [ + - + - ]: 3804 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
344 : :
345 [ + + + - : 5706 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
346 [ + - ]: 3804 : }
347 : :
348 : :
349 [ + - ]: 2702 : FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
350 : : {
351 : 2246 : SeedRandomStateForTest(SeedRand::ZEROS);
352 : 2246 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
353 : 2246 : const auto& node = g_setup->m_node;
354 : 2246 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
355 : :
356 : 2246 : MockTime(fuzzed_data_provider, chainstate);
357 : :
358 : : // All RBF-spendable outpoints outside of the unsubmitted package
359 [ + - ]: 2246 : std::set<COutPoint> mempool_outpoints;
360 [ + - ]: 2246 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
361 [ + + ]: 226846 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
362 [ + - + - ]: 224600 : Assert(mempool_outpoints.insert(outpoint).second);
363 [ + - ]: 224600 : outpoints_value[outpoint] = 50 * COIN;
364 : : }
365 : :
366 [ + - ]: 2246 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
367 [ + - + - ]: 4492 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
368 : :
369 [ + - ]: 2246 : auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
370 : 2246 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
371 : :
372 : 2246 : chainstate.SetMempool(&tx_pool);
373 : :
374 [ + + + + ]: 261909 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
375 : : {
376 [ + - ]: 259663 : Assert(!mempool_outpoints.empty());
377 : :
378 : 259663 : std::vector<CTransactionRef> txs;
379 : :
380 : : // Make packages of 1-to-26 transactions
381 : 259663 : const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26);
382 : 259663 : std::set<COutPoint> package_outpoints;
383 [ - + + + ]: 1170554 : while (txs.size() < num_txs) {
384 : : // Create transaction to add to the mempool
385 [ + - ]: 1821782 : txs.emplace_back([&] {
386 : 910891 : CMutableTransaction tx_mut;
387 [ + + ]: 910891 : tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
388 [ + + ]: 910891 : tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
389 : : // Last transaction in a package needs to be a child of parents to get further in validation
390 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
391 : : // Note that this test currently only spends package outputs in last transaction.
392 [ + + - + : 910891 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
393 : 910891 : const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
394 : 910891 : auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
395 : :
396 [ + + ]: 910891 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
397 : :
398 [ + - ]: 910891 : Assert(!outpoints.empty());
399 : :
400 : : CAmount amount_in{0};
401 [ + + ]: 16390995 : for (size_t i = 0; i < num_in; ++i) {
402 : : // Pop random outpoint. We erase them to avoid double-spending
403 : : // while in this loop, but later add them back (unless last_tx).
404 : 15480104 : auto pop = outpoints.begin();
405 : 15480104 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
406 : 15480104 : const auto outpoint = *pop;
407 : 15480104 : outpoints.erase(pop);
408 : : // no need to update or erase from outpoints_value
409 [ + - ]: 15480104 : amount_in += outpoints_value.at(outpoint);
410 : :
411 : : // Create input
412 : 15480104 : const auto sequence = ConsumeSequence(fuzzed_data_provider);
413 : 15480104 : const auto script_sig = CScript{};
414 [ + + + - ]: 23402587 : const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
415 : :
416 : 15480104 : CTxIn in;
417 : 15480104 : in.prevout = outpoint;
418 : 15480104 : in.nSequence = sequence;
419 : 15480104 : in.scriptSig = script_sig;
420 [ + - ]: 15480104 : in.scriptWitness.stack = script_wit_stack;
421 : :
422 [ + - ]: 15480104 : tx_mut.vin.push_back(in);
423 : 15480104 : }
424 : :
425 : : // Duplicate an input
426 : 910891 : bool dup_input = fuzzed_data_provider.ConsumeBool();
427 [ + + ]: 910891 : if (dup_input) {
428 [ + - ]: 397608 : tx_mut.vin.push_back(tx_mut.vin.back());
429 : : }
430 : :
431 : : // Refer to a non-existent input
432 [ + + ]: 910891 : if (fuzzed_data_provider.ConsumeBool()) {
433 [ + - ]: 305993 : tx_mut.vin.emplace_back();
434 : : }
435 : :
436 : : // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent
437 [ + + + + ]: 910891 : if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
438 [ + - + - : 161955 : tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
+ - ]
439 : : // Don't add any other outputs.
440 : 53985 : num_out = 1;
441 : 53985 : amount_in -= 1000;
442 : : }
443 : :
444 : 910891 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
445 : 910891 : const auto amount_out = (amount_in - amount_fee) / num_out;
446 [ + + ]: 15635030 : for (int i = 0; i < num_out; ++i) {
447 [ + - ]: 14724139 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
448 : : }
449 [ + - ]: 910891 : auto tx = MakeTransactionRef(tx_mut);
450 : : // Restore previously removed outpoints, except in-package outpoints
451 [ + + ]: 910891 : if (!last_tx) {
452 [ + + ]: 9147312 : for (const auto& in : tx->vin) {
453 : : // It's a fake input, or a new input, or a duplicate
454 [ + + + - : 17080745 : Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
+ + + - +
- ]
455 : : }
456 : : // Cache the in-package outpoints being made
457 [ - + + + ]: 14180311 : for (size_t i = 0; i < tx->vout.size(); ++i) {
458 [ + - ]: 13394879 : package_outpoints.emplace(tx->GetHash(), i);
459 : : }
460 : : }
461 : : // We need newly-created values for the duration of this run
462 [ - + + + ]: 15689015 : for (size_t i = 0; i < tx->vout.size(); ++i) {
463 [ + - ]: 14778124 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
464 : : }
465 : 910891 : return tx;
466 [ + - ]: 2732673 : }());
467 : : }
468 : :
469 [ + + ]: 259663 : if (fuzzed_data_provider.ConsumeBool()) {
470 [ + - ]: 88221 : MockTime(fuzzed_data_provider, chainstate);
471 : : }
472 [ + + ]: 259663 : if (fuzzed_data_provider.ConsumeBool()) {
473 [ + - ]: 67456 : tx_pool.RollingFeeUpdate();
474 : : }
475 [ + + ]: 259663 : if (fuzzed_data_provider.ConsumeBool()) {
476 [ + + ]: 105153 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
477 : 43200 : txs.back()->GetHash() :
478 : 61953 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
479 : 105153 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
480 [ + - ]: 105153 : tx_pool.PrioritiseTransaction(txid, delta);
481 : : }
482 : :
483 : : // Remember all added transactions
484 [ + - ]: 259663 : std::set<CTransactionRef> added;
485 [ + - ]: 259663 : auto txr = std::make_shared<TransactionsDelta>(added);
486 [ + - + - ]: 519326 : node.validation_signals->RegisterSharedValidationInterface(txr);
487 : :
488 : : // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
489 : : // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
490 : : // (the package is a test accept and ATMP is a submission).
491 [ - + + + : 393867 : auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
+ + ]
492 : :
493 : : // Exercise client_maxfeerate logic
494 : 259663 : std::optional<CFeeRate> client_maxfeerate{};
495 [ + + ]: 259663 : if (fuzzed_data_provider.ConsumeBool()) {
496 [ + - ]: 63049 : client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
497 : : }
498 : :
499 [ + - ]: 778989 : const auto result_package = WITH_LOCK(::cs_main,
500 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
501 : :
502 : : // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
503 : : // can be a source of divergence.
504 [ + - + - ]: 778989 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
505 : : /*bypass_limits=*/false, /*test_accept=*/!single_submit));
506 : 259663 : const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
507 : :
508 [ + - ]: 259663 : node.validation_signals->SyncWithValidationInterfaceQueue();
509 [ + - + - ]: 519326 : node.validation_signals->UnregisterSharedValidationInterface(txr);
510 : :
511 : : // There is only 1 transaction in the package. We did a test-package-accept and a ATMP
512 [ + + ]: 259663 : if (single_submit) {
513 [ + - ]: 21811 : Assert(passed != added.empty());
514 [ + - ]: 21811 : Assert(passed == res.m_state.IsValid());
515 [ + + ]: 21811 : if (passed) {
516 [ + - ]: 3016 : Assert(added.size() == 1);
517 [ + - ]: 3016 : Assert(txs.back() == *added.begin());
518 : : }
519 [ + + ]: 237852 : } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
520 : : // We don't know anything about the validity since transactions were randomly generated, so
521 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
522 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
523 [ + - ]: 177159 : const bool expect_valid{result_package.m_state.IsValid()};
524 [ + - + - ]: 354318 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
525 : : } else {
526 : : // This is empty if it fails early checks, or "full" if transactions are looked at deeper
527 [ - + + + : 117683 : Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
+ - + - ]
528 : : }
529 : :
530 [ + - ]: 259663 : CheckMempoolTRUCInvariants(tx_pool);
531 : :
532 : : // Dust checks only make sense when dust is enforced
533 [ + + ]: 259663 : if (tx_pool.m_opts.require_standard) {
534 [ + - ]: 111167 : CheckMempoolEphemeralInvariants(tx_pool);
535 : : }
536 [ + - ]: 519326 : }
537 : :
538 [ + - + - ]: 4492 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
539 : :
540 [ + + + - : 6738 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
541 [ + - ]: 4492 : }
542 : : } // namespace
|