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