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 <chain.h>
6 : : #include <consensus/amount.h>
7 : : #include <consensus/consensus.h>
8 : : #include <consensus/validation.h>
9 : : #include <node/mining_types.h>
10 : : #include <policy/feerate.h>
11 : : #include <policy/packages.h>
12 : : #include <policy/policy.h>
13 : : #include <policy/settings.h>
14 : : #include <policy/truc_policy.h>
15 : : #include <primitives/transaction.h>
16 : : #include <script/script.h>
17 : : #include <sync.h>
18 : : #include <test/fuzz/FuzzedDataProvider.h>
19 : : #include <test/fuzz/fuzz.h>
20 : : #include <test/fuzz/util.h>
21 : : #include <test/fuzz/util/mempool.h>
22 : : #include <test/util/mining.h>
23 : : #include <test/util/random.h>
24 : : #include <test/util/script.h>
25 : : #include <test/util/setup_common.h>
26 : : #include <test/util/txmempool.h>
27 : : #include <txmempool.h>
28 : : #include <util/check.h>
29 : : #include <util/hasher.h>
30 : : #include <util/time.h>
31 : : #include <util/translation.h>
32 : : #include <validation.h>
33 : : #include <validationinterface.h>
34 : :
35 : : #include <cstddef>
36 : : #include <cstdint>
37 : : #include <functional>
38 : : #include <iterator>
39 : : #include <limits>
40 : : #include <map>
41 : : #include <memory>
42 : : #include <optional>
43 : : #include <set>
44 : : #include <span>
45 : : #include <string>
46 : : #include <unordered_map>
47 : : #include <utility>
48 : : #include <vector>
49 : : using node::NodeContext;
50 : :
51 : : namespace {
52 : :
53 : : const TestingSetup* g_setup;
54 : : std::vector<COutPoint> g_outpoints_coinbase_init_mature;
55 : :
56 : : struct MockedTxPool : public CTxMemPool {
57 : 54886 : void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
58 : : {
59 : 54886 : LOCK(cs);
60 [ + - ]: 54886 : lastRollingFeeUpdate = GetTime();
61 [ + - ]: 54886 : blockSinceLastRollingFeeBump = true;
62 : 54886 : }
63 : : };
64 : :
65 : 2 : void initialize_tx_pool()
66 : : {
67 [ + - + - : 2 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
+ - ]
68 : 2 : g_setup = testing_setup.get();
69 [ + - + - ]: 6 : SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));
70 : :
71 [ + + ]: 402 : for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
72 : 1200 : COutPoint prevout{MineBlock(g_setup->m_node, {
73 : : .coinbase_output_script = P2WSH_EMPTY,
74 : : })};
75 [ + + ]: 400 : if (i < COINBASE_MATURITY) {
76 : : // Remember the txids to avoid expensive disk access later on
77 : 200 : g_outpoints_coinbase_init_mature.push_back(prevout);
78 : : }
79 : : }
80 : 2 : g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
81 [ + - ]: 402 : }
82 : :
83 : : struct OutpointsUpdater final : public CValidationInterface {
84 : : std::set<COutPoint>& m_mempool_outpoints;
85 : :
86 : 3786 : explicit OutpointsUpdater(std::set<COutPoint>& r)
87 : 3786 : : m_mempool_outpoints{r} {}
88 : :
89 : 135721 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
90 : : {
91 : : // for coins spent we always want to be able to rbf so they're not removed
92 : :
93 : : // outputs from this tx can now be spent
94 [ - + + + ]: 587440 : for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {
95 : 451719 : m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});
96 : : }
97 : 135721 : }
98 : :
99 : 52177 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
100 : : {
101 : : // outpoints spent by this tx are now available
102 [ + + ]: 145836 : for (const auto& input : tx->vin) {
103 : : // Could already exist if this was a replacement
104 : 93659 : m_mempool_outpoints.insert(input.prevout);
105 : : }
106 : : // outpoints created by this tx no longer exist
107 [ - + + + ]: 201508 : for (uint32_t index{0}; index < tx->vout.size(); ++index) {
108 : 149331 : m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});
109 : : }
110 : 52177 : }
111 : : };
112 : :
113 : : struct TransactionsDelta final : public CValidationInterface {
114 : : std::set<CTransactionRef>& m_added;
115 : :
116 : 221247 : explicit TransactionsDelta(std::set<CTransactionRef>& a)
117 : 221247 : : m_added{a} {}
118 : :
119 : 38699 : void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
120 : : {
121 : : // Transactions may be entered and booted any number of times
122 : 38699 : m_added.insert(tx.info.m_tx);
123 : 38699 : }
124 : :
125 : 28955 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
126 : : {
127 : : // Transactions may be entered and booted any number of times
128 : 28955 : m_added.erase(tx);
129 : 28955 : }
130 : : };
131 : :
132 : 110951 : void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
133 : : {
134 : 110951 : const auto time = ConsumeTime(fuzzed_data_provider,
135 : 110951 : chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
136 [ - + ]: 110951 : std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
137 : 110951 : SetMockTime(time);
138 : 110951 : }
139 : :
140 : 2115 : std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
141 : : {
142 : : // Take the default options for tests...
143 : 2115 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
144 : :
145 : :
146 : : // ...override specific options for this specific fuzz suite
147 : 2115 : mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
148 : 2115 : mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
149 : 2115 : mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
150 : 2115 : mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
151 : : // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
152 : 2115 : nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
153 : :
154 : 2115 : mempool_opts.check_ratio = 1;
155 : 2115 : mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
156 : :
157 [ + - ]: 2115 : bilingual_str error;
158 : : // ...and construct a CTxMemPool from it
159 [ + - ]: 2115 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
160 : : // ... ignore the error since it might be beneficial to fuzz even when the
161 : : // mempool size is unreasonably small
162 [ + + + - : 2873 : Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
- + ]
163 : 2115 : return mempool;
164 : 2115 : }
165 : :
166 : 1671 : std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)
167 : : {
168 : : // Take the default options for tests...
169 : 1671 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
170 : :
171 : 1671 : mempool_opts.check_ratio = 1;
172 : :
173 : : // Require standardness rules otherwise ephemeral dust is no-op
174 : 1671 : mempool_opts.require_standard = true;
175 : :
176 : : // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC
177 [ + - ]: 1671 : mempool_opts.min_relay_feerate = CFeeRate(0);
178 : :
179 [ + - ]: 1671 : bilingual_str error;
180 : : // ...and construct a CTxMemPool from it
181 [ + - ]: 1671 : auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
182 [ - + ]: 1671 : Assert(error.empty());
183 : 1671 : return mempool;
184 : 1671 : }
185 : :
186 : : // Scan mempool for a tx that has spent dust and return a
187 : : // prevout of the child that isn't the dusty parent itself.
188 : : // This is used to double-spend the child out of the mempool,
189 : : // leaving the parent childless.
190 : : // This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.
191 : 140418 : std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)
192 : : {
193 : 140418 : LOCK(tx_pool.cs);
194 [ + - + + ]: 5463303 : for (const auto& tx_info : tx_pool.infoAll()) {
195 [ + - - + ]: 5327142 : const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
196 [ + - ]: 5327142 : std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};
197 [ + + ]: 5327142 : if (!dust_indexes.empty()) {
198 [ + - ]: 8639 : const auto& children = tx_pool.GetChildren(entry);
199 [ + + ]: 8639 : if (!children.empty()) {
200 [ - + - + ]: 4730 : Assert(children.size() == 1);
201 : : // Find an input that doesn't spend from parent's txid
202 : 4730 : const auto& only_child = children.begin()->get().GetTx();
203 [ + + ]: 8443 : for (const auto& tx_input : only_child.vin) {
204 [ + + ]: 7970 : if (tx_input.prevout.hash != tx_info.tx->GetHash()) {
205 : 4257 : return tx_input.prevout;
206 : : }
207 : : }
208 : : }
209 : 8639 : }
210 : 5463303 : }
211 : :
212 : 136161 : return std::nullopt;
213 : 140418 : }
214 : :
215 [ + - ]: 2135 : FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
216 : : {
217 : 1671 : SeedRandomStateForTest(SeedRand::ZEROS);
218 : 1671 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
219 : 1671 : const auto& node = g_setup->m_node;
220 : 1671 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
221 : :
222 : 1671 : MockTime(fuzzed_data_provider, chainstate);
223 : :
224 : : // All RBF-spendable outpoints outside of the unsubmitted package
225 [ + - ]: 1671 : std::set<COutPoint> mempool_outpoints;
226 [ + - ]: 1671 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
227 [ + + ]: 168771 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
228 [ + - ]: 167100 : Assert(mempool_outpoints.insert(outpoint).second);
229 [ + - ]: 167100 : outpoints_value[outpoint] = 50 * COIN;
230 : : }
231 : :
232 [ + - ]: 1671 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
233 [ + - + - ]: 3342 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
234 : :
235 [ + - ]: 1671 : auto tx_pool_{MakeEphemeralMempool(node)};
236 : 1671 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
237 : :
238 : 1671 : chainstate.SetMempool(&tx_pool);
239 : :
240 [ + + + + ]: 303866 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
241 : : {
242 [ - + ]: 302195 : Assert(!mempool_outpoints.empty());
243 : :
244 : 302195 : std::vector<CTransactionRef> txs;
245 : :
246 : : // Find something we may want to double-spend with two input single tx
247 [ + + + - ]: 302195 : std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};
248 : :
249 : : // Make small packages
250 [ + + ]: 302195 : const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
251 : :
252 : 302195 : std::set<COutPoint> package_outpoints;
253 [ - + + + ]: 985675 : while (txs.size() < num_txs) {
254 : : // Create transaction to add to the mempool
255 [ + - ]: 1366960 : txs.emplace_back([&] {
256 : 683480 : CMutableTransaction tx_mut;
257 : 683480 : tx_mut.version = CTransaction::CURRENT_VERSION;
258 : 683480 : tx_mut.nLockTime = 0;
259 : : // Last transaction in a package needs to be a child of parents to get further in validation
260 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
261 : : // Note that this test currently only spends package outputs in last transaction.
262 [ + + - + : 683480 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
263 [ + + ]: 683480 : const auto num_in = outpoint_to_rbf ? 2 :
264 [ + + ]: 679223 : last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) :
265 : 479144 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
266 [ + + ]: 683480 : const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
267 : :
268 [ + + ]: 683480 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
269 : :
270 [ + - - + : 683480 : Assert((int)outpoints.size() >= num_in && num_in > 0);
- + ]
271 : :
272 : : CAmount amount_in{0};
273 [ + + ]: 2549560 : for (int i = 0; i < num_in; ++i) {
274 : : // Pop random outpoint. We erase them to avoid double-spending
275 : : // while in this loop, but later add them back (unless last_tx).
276 : 1866080 : auto pop = outpoints.begin();
277 : 1866080 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
278 [ + + ]: 1866080 : auto outpoint = *pop;
279 : :
280 [ + + + + ]: 1866080 : if (i == 0 && outpoint_to_rbf) {
281 : 4257 : outpoint = *outpoint_to_rbf;
282 : 4257 : outpoints.erase(outpoint);
283 : : } else {
284 : 1861823 : outpoints.erase(pop);
285 : : }
286 : : // no need to update or erase from outpoints_value
287 [ + - ]: 1866080 : amount_in += outpoints_value.at(outpoint);
288 : :
289 : : // Create input
290 : 1866080 : CTxIn in;
291 : 1866080 : in.prevout = outpoint;
292 [ + - ]: 1866080 : in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK;
293 : :
294 [ + - ]: 1866080 : tx_mut.vin.push_back(in);
295 : 1866080 : }
296 : :
297 : 683480 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
298 : 683480 : const auto amount_out = (amount_in - amount_fee) / num_out;
299 [ + + ]: 2263044 : for (int i = 0; i < num_out; ++i) {
300 [ + - ]: 1579564 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
301 : : }
302 : :
303 : : // Note output amounts can naturally drop to dust on their own.
304 [ + + + + ]: 683480 : if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) {
305 : 222247 : uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out);
306 [ + - + - ]: 222247 : tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY));
307 : : }
308 : :
309 [ + - ]: 683480 : auto tx = MakeTransactionRef(tx_mut);
310 : : // Restore previously removed outpoints, except in-package outpoints (to allow RBF)
311 [ + + ]: 683480 : if (!last_tx) {
312 [ + + ]: 1568844 : for (const auto& in : tx->vin) {
313 [ + - ]: 2170886 : Assert(outpoints.insert(in.prevout).second);
314 : : }
315 : : // Cache the in-package outpoints being made
316 [ - + + + ]: 1704189 : for (size_t i = 0; i < tx->vout.size(); ++i) {
317 [ + - ]: 1220788 : package_outpoints.emplace(tx->GetHash(), i);
318 : : }
319 : : }
320 : : // We need newly-created values for the duration of this run
321 [ - + + + ]: 2485291 : for (size_t i = 0; i < tx->vout.size(); ++i) {
322 [ + - ]: 1801811 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
323 : : }
324 : 683480 : return tx;
325 [ + - ]: 2050440 : }());
326 : : }
327 : :
328 [ + + ]: 302195 : if (fuzzed_data_provider.ConsumeBool()) {
329 [ + + ]: 123453 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
330 : 39788 : txs.back()->GetHash() :
331 : 83665 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
332 : 123453 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
333 : : // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't
334 : : // filter for ephemeral dust
335 [ + - + + ]: 123453 : if (tx_pool.exists(txid)) {
336 [ + - ]: 34213 : const auto tx_info{tx_pool.info(txid)};
337 [ + - + + ]: 34213 : if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) {
338 [ + - ]: 34137 : tx_pool.PrioritiseTransaction(txid, delta);
339 : : }
340 : 34213 : }
341 : : }
342 : :
343 [ - + ]: 302195 : auto single_submit = txs.size() == 1;
344 : :
345 [ + - ]: 906585 : const auto result_package = WITH_LOCK(::cs_main,
346 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
347 : :
348 [ + - + - ]: 906585 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
349 : : /*bypass_limits=*/false, /*test_accept=*/!single_submit));
350 : :
351 [ + + + + ]: 302195 : if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
352 : : // We don't know anything about the validity since transactions were randomly generated, so
353 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
354 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
355 [ + - ]: 116246 : const bool expect_valid{result_package.m_state.IsValid()};
356 [ + - - + ]: 116246 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
357 : : }
358 : :
359 [ + - ]: 302195 : node.validation_signals->SyncWithValidationInterfaceQueue();
360 : :
361 [ + - ]: 302195 : CheckMempoolEphemeralInvariants(tx_pool);
362 : 302195 : }
363 : :
364 [ + - + - ]: 3342 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
365 : :
366 [ + + + - : 5013 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
367 [ + - ]: 3342 : }
368 : :
369 : :
370 [ + - ]: 2579 : FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
371 : : {
372 : 2115 : SeedRandomStateForTest(SeedRand::ZEROS);
373 : 2115 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
374 : 2115 : const auto& node = g_setup->m_node;
375 : 2115 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
376 : :
377 : 2115 : MockTime(fuzzed_data_provider, chainstate);
378 : :
379 : : // All RBF-spendable outpoints outside of the unsubmitted package
380 [ + - ]: 2115 : std::set<COutPoint> mempool_outpoints;
381 [ + - ]: 2115 : std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
382 [ + + ]: 213615 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
383 [ + - ]: 211500 : Assert(mempool_outpoints.insert(outpoint).second);
384 [ + - ]: 211500 : outpoints_value[outpoint] = 50 * COIN;
385 : : }
386 : :
387 [ + - ]: 2115 : auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
388 [ + - + - ]: 4230 : node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
389 : :
390 [ + - ]: 2115 : auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
391 : 2115 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
392 : :
393 : 2115 : chainstate.SetMempool(&tx_pool);
394 : :
395 [ + + + + ]: 223362 : LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
396 : : {
397 [ - + ]: 221247 : Assert(!mempool_outpoints.empty());
398 : :
399 : 221247 : std::vector<CTransactionRef> txs;
400 : :
401 : : // Make packages of 1-to-26 transactions
402 : 221247 : const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26);
403 : 221247 : std::set<COutPoint> package_outpoints;
404 [ - + + + ]: 791539 : while (txs.size() < num_txs) {
405 : : // Create transaction to add to the mempool
406 [ + - ]: 1140584 : txs.emplace_back([&] {
407 : 570292 : CMutableTransaction tx_mut;
408 [ + + ]: 570292 : tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
409 [ + + ]: 570292 : tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
410 : : // Last transaction in a package needs to be a child of parents to get further in validation
411 : : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
412 : : // Note that this test currently only spends package outputs in last transaction.
413 [ + + - + : 570292 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
+ + ]
414 : 570292 : const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
415 : 570292 : auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
416 : :
417 [ + + ]: 570292 : auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
418 : :
419 [ - + ]: 570292 : Assert(!outpoints.empty());
420 : :
421 : : CAmount amount_in{0};
422 [ + + ]: 11497162 : for (size_t i = 0; i < num_in; ++i) {
423 : : // Pop random outpoint. We erase them to avoid double-spending
424 : : // while in this loop, but later add them back (unless last_tx).
425 : 10926870 : auto pop = outpoints.begin();
426 : 10926870 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
427 : 10926870 : const auto outpoint = *pop;
428 : 10926870 : outpoints.erase(pop);
429 : : // no need to update or erase from outpoints_value
430 [ + - ]: 10926870 : amount_in += outpoints_value.at(outpoint);
431 : :
432 : : // Create input
433 : 10926870 : const auto sequence = ConsumeSequence(fuzzed_data_provider);
434 : 10926870 : const auto script_sig = CScript{};
435 [ + + + - ]: 17298462 : const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
436 : :
437 : 10926870 : CTxIn in;
438 : 10926870 : in.prevout = outpoint;
439 : 10926870 : in.nSequence = sequence;
440 : 10926870 : in.scriptSig = script_sig;
441 [ + - ]: 10926870 : in.scriptWitness.stack = script_wit_stack;
442 : :
443 [ + - ]: 10926870 : tx_mut.vin.push_back(in);
444 : 10926870 : }
445 : :
446 : : // Duplicate an input
447 : 570292 : bool dup_input = fuzzed_data_provider.ConsumeBool();
448 [ + + ]: 570292 : if (dup_input) {
449 [ + - ]: 194721 : tx_mut.vin.push_back(tx_mut.vin.back());
450 : : }
451 : :
452 : : // Refer to a non-existent input
453 [ + + ]: 570292 : if (fuzzed_data_provider.ConsumeBool()) {
454 [ + - ]: 174720 : tx_mut.vin.emplace_back();
455 : : }
456 : :
457 : : // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent
458 [ + + + + ]: 570292 : if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
459 [ + - + - : 83100 : tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
+ - ]
460 : : // Don't add any other outputs.
461 : 27700 : num_out = 1;
462 : 27700 : amount_in -= 1000;
463 : : }
464 : :
465 : 570292 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
466 : 570292 : const auto amount_out = (amount_in - amount_fee) / num_out;
467 [ + + ]: 11996819 : for (int i = 0; i < num_out; ++i) {
468 [ + - ]: 11426527 : tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
469 : : }
470 [ + - ]: 570292 : auto tx = MakeTransactionRef(tx_mut);
471 : : // Restore previously removed outpoints, except in-package outpoints
472 [ + + ]: 570292 : if (!last_tx) {
473 [ + + ]: 5969145 : for (const auto& in : tx->vin) {
474 : : // It's a fake input, or a new input, or a duplicate
475 [ + + + - : 11106816 : Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
+ + + - -
+ ]
476 : : }
477 : : // Cache the in-package outpoints being made
478 [ - + + + ]: 11043887 : for (size_t i = 0; i < tx->vout.size(); ++i) {
479 [ + - ]: 10542272 : package_outpoints.emplace(tx->GetHash(), i);
480 : : }
481 : : }
482 : : // We need newly-created values for the duration of this run
483 [ - + + + ]: 12024519 : for (size_t i = 0; i < tx->vout.size(); ++i) {
484 [ + - ]: 11454227 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
485 : : }
486 : 570292 : return tx;
487 [ + - ]: 1710876 : }());
488 : : }
489 : :
490 [ + + ]: 221247 : if (fuzzed_data_provider.ConsumeBool()) {
491 [ + - ]: 107165 : MockTime(fuzzed_data_provider, chainstate);
492 : : }
493 [ + + ]: 221247 : if (fuzzed_data_provider.ConsumeBool()) {
494 [ + - ]: 54886 : tx_pool.RollingFeeUpdate();
495 : : }
496 [ + + ]: 221247 : if (fuzzed_data_provider.ConsumeBool()) {
497 [ + + ]: 72419 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
498 : 35452 : txs.back()->GetHash() :
499 : 36967 : PickValue(fuzzed_data_provider, mempool_outpoints).hash;
500 : 72419 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
501 [ + - ]: 72419 : tx_pool.PrioritiseTransaction(txid, delta);
502 : : }
503 : :
504 : : // Remember all added transactions
505 [ + - ]: 221247 : std::set<CTransactionRef> added;
506 [ + - ]: 221247 : auto txr = std::make_shared<TransactionsDelta>(added);
507 [ + - + - ]: 442494 : node.validation_signals->RegisterSharedValidationInterface(txr);
508 : :
509 : : // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
510 : : // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
511 : : // (the package is a test accept and ATMP is a submission).
512 [ - + + + : 373817 : auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
+ + ]
513 : :
514 : : // Exercise client_maxfeerate logic
515 : 221247 : std::optional<CFeeRate> client_maxfeerate{};
516 [ + + ]: 221247 : if (fuzzed_data_provider.ConsumeBool()) {
517 [ + - ]: 50262 : client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
518 : : }
519 : :
520 [ + - ]: 663741 : const auto result_package = WITH_LOCK(::cs_main,
521 : : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
522 : :
523 : : // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
524 : : // can be a source of divergence.
525 [ + - + - ]: 663741 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
526 : : /*bypass_limits=*/false, /*test_accept=*/!single_submit));
527 : 221247 : const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
528 : :
529 [ + - ]: 221247 : node.validation_signals->SyncWithValidationInterfaceQueue();
530 [ + - + - ]: 442494 : node.validation_signals->UnregisterSharedValidationInterface(txr);
531 : :
532 : : // There is only 1 transaction in the package. We did a test-package-accept and a ATMP
533 [ + + ]: 221247 : if (single_submit) {
534 [ - + ]: 22772 : Assert(passed != added.empty());
535 [ - + ]: 22772 : Assert(passed == res.m_state.IsValid());
536 [ + + ]: 22772 : if (passed) {
537 [ - + ]: 2680 : Assert(added.size() == 1);
538 [ - + ]: 2680 : Assert(txs.back() == *added.begin());
539 : : }
540 [ + + ]: 198475 : } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
541 : : // We don't know anything about the validity since transactions were randomly generated, so
542 : : // just use result_package.m_state here. This makes the expect_valid check meaningless, but
543 : : // we can still verify that the contents of m_tx_results are consistent with m_state.
544 [ + - ]: 163570 : const bool expect_valid{result_package.m_state.IsValid()};
545 [ + - - + ]: 163570 : Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
546 : : } else {
547 : : // This is empty if it fails early checks, or "full" if transactions are looked at deeper
548 [ - + + + : 66433 : Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
+ - - + ]
549 : : }
550 : :
551 [ + - ]: 221247 : CheckMempoolTRUCInvariants(tx_pool);
552 : :
553 : : // Dust checks only make sense when dust is enforced
554 [ + + ]: 221247 : if (tx_pool.m_opts.require_standard) {
555 [ + - ]: 129434 : CheckMempoolEphemeralInvariants(tx_pool);
556 : : }
557 [ + - ]: 442494 : }
558 : :
559 [ + - + - ]: 4230 : node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
560 : :
561 [ + + + - : 6345 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
+ - ]
562 [ + - ]: 4230 : }
563 : : } // namespace
|