/root/bitcoin/src/test/fuzz/package_eval.cpp
Line | Count | Source |
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 | | void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs) |
58 | 89.9k | { |
59 | 89.9k | LOCK(cs); |
60 | 89.9k | lastRollingFeeUpdate = GetTime(); |
61 | 89.9k | blockSinceLastRollingFeeBump = true; |
62 | 89.9k | } |
63 | | }; |
64 | | |
65 | | void initialize_tx_pool() |
66 | 0 | { |
67 | 0 | static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); |
68 | 0 | g_setup = testing_setup.get(); |
69 | 0 | SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time())); |
70 | |
|
71 | 0 | for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) { Branch (71:21): [True: 0, False: 0]
|
72 | 0 | COutPoint prevout{MineBlock(g_setup->m_node, { |
73 | 0 | .coinbase_output_script = P2WSH_EMPTY, |
74 | 0 | })}; |
75 | 0 | if (i < COINBASE_MATURITY) { Branch (75:13): [True: 0, False: 0]
|
76 | | // Remember the txids to avoid expensive disk access later on |
77 | 0 | g_outpoints_coinbase_init_mature.push_back(prevout); |
78 | 0 | } |
79 | 0 | } |
80 | 0 | g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue(); |
81 | 0 | } |
82 | | |
83 | | struct OutpointsUpdater final : public CValidationInterface { |
84 | | std::set<COutPoint>& m_mempool_outpoints; |
85 | | |
86 | | explicit OutpointsUpdater(std::set<COutPoint>& r) |
87 | 6.17k | : m_mempool_outpoints{r} {} |
88 | | |
89 | | void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override |
90 | 250k | { |
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 | 1.17M | for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) { Branch (94:33): [True: 927k, False: 250k]
|
95 | 927k | m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index}); |
96 | 927k | } |
97 | 250k | } |
98 | | |
99 | | void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override |
100 | 94.1k | { |
101 | | // outpoints spent by this tx are now available |
102 | 163k | for (const auto& input : tx->vin) { Branch (102:32): [True: 163k, False: 94.1k]
|
103 | | // Could already exist if this was a replacement |
104 | 163k | m_mempool_outpoints.insert(input.prevout); |
105 | 163k | } |
106 | | // outpoints created by this tx no longer exist |
107 | 360k | for (uint32_t index{0}; index < tx->vout.size(); ++index) { Branch (107:33): [True: 266k, False: 94.1k]
|
108 | 266k | m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index}); |
109 | 266k | } |
110 | 94.1k | } |
111 | | }; |
112 | | |
113 | | struct TransactionsDelta final : public CValidationInterface { |
114 | | std::set<CTransactionRef>& m_added; |
115 | | |
116 | | explicit TransactionsDelta(std::set<CTransactionRef>& a) |
117 | 353k | : m_added{a} {} |
118 | | |
119 | | void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override |
120 | 70.9k | { |
121 | | // Transactions may be entered and booted any number of times |
122 | 70.9k | m_added.insert(tx.info.m_tx); |
123 | 70.9k | } |
124 | | |
125 | | void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override |
126 | 51.7k | { |
127 | | // Transactions may be entered and booted any number of times |
128 | 51.7k | m_added.erase(tx); |
129 | 51.7k | } |
130 | | }; |
131 | | |
132 | | void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) |
133 | 184k | { |
134 | 184k | const auto time = ConsumeTime(fuzzed_data_provider, |
135 | 184k | chainstate.m_chain.Tip()->GetMedianTimePast() + 1, |
136 | 184k | std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max()); |
137 | 184k | SetMockTime(time); |
138 | 184k | } |
139 | | |
140 | | std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node) |
141 | 3.19k | { |
142 | | // Take the default options for tests... |
143 | 3.19k | CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)}; |
144 | | |
145 | | |
146 | | // ...override specific options for this specific fuzz suite |
147 | 3.19k | mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50); |
148 | 3.19k | mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50); |
149 | 3.19k | mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000; |
150 | 3.19k | 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 | 3.19k | nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000; |
153 | | |
154 | 3.19k | mempool_opts.check_ratio = 1; |
155 | 3.19k | mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool(); |
156 | | |
157 | 3.19k | bilingual_str error; |
158 | | // ...and construct a CTxMemPool from it |
159 | 3.19k | 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 | 3.19k | Assert(error.empty() || error.original.starts_with("-maxmempool must be at least ")); |
163 | 3.19k | return mempool; |
164 | 3.19k | } |
165 | | |
166 | | std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node) |
167 | 2.98k | { |
168 | | // Take the default options for tests... |
169 | 2.98k | CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)}; |
170 | | |
171 | 2.98k | mempool_opts.check_ratio = 1; |
172 | | |
173 | | // Require standardness rules otherwise ephemeral dust is no-op |
174 | 2.98k | mempool_opts.require_standard = true; |
175 | | |
176 | | // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC |
177 | 2.98k | mempool_opts.min_relay_feerate = CFeeRate(0); |
178 | | |
179 | 2.98k | bilingual_str error; |
180 | | // ...and construct a CTxMemPool from it |
181 | 2.98k | auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)}; |
182 | 2.98k | Assert(error.empty()); |
183 | 2.98k | return mempool; |
184 | 2.98k | } |
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 | | std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool) |
192 | 243k | { |
193 | 243k | LOCK(tx_pool.cs); |
194 | 9.59M | for (const auto& tx_info : tx_pool.infoAll()) { Branch (194:30): [True: 9.59M, False: 235k]
|
195 | 9.59M | const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash())); |
196 | 9.59M | std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)}; |
197 | 9.59M | if (!dust_indexes.empty()) { Branch (197:13): [True: 23.1k, False: 9.57M]
|
198 | 23.1k | const auto& children = tx_pool.GetChildren(entry); |
199 | 23.1k | if (!children.empty()) { Branch (199:17): [True: 11.4k, False: 11.7k]
|
200 | 11.4k | Assert(children.size() == 1); |
201 | | // Find an input that doesn't spend from parent's txid |
202 | 11.4k | const auto& only_child = children.begin()->get().GetTx(); |
203 | 18.3k | for (const auto& tx_input : only_child.vin) { Branch (203:43): [True: 18.3k, False: 3.36k]
|
204 | 18.3k | if (tx_input.prevout.hash != tx_info.tx->GetHash()) { Branch (204:25): [True: 8.04k, False: 10.3k]
|
205 | 8.04k | return tx_input.prevout; |
206 | 8.04k | } |
207 | 18.3k | } |
208 | 11.4k | } |
209 | 23.1k | } |
210 | 9.59M | } |
211 | | |
212 | 235k | return std::nullopt; |
213 | 243k | } |
214 | | |
215 | | FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool) |
216 | 2.98k | { |
217 | 2.98k | SeedRandomStateForTest(SeedRand::ZEROS); |
218 | 2.98k | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
219 | 2.98k | const auto& node = g_setup->m_node; |
220 | 2.98k | auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())}; |
221 | | |
222 | 2.98k | MockTime(fuzzed_data_provider, chainstate); |
223 | | |
224 | | // All RBF-spendable outpoints outside of the unsubmitted package |
225 | 2.98k | std::set<COutPoint> mempool_outpoints; |
226 | 2.98k | std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value; |
227 | 298k | for (const auto& outpoint : g_outpoints_coinbase_init_mature) { Branch (227:31): [True: 298k, False: 2.98k]
|
228 | 298k | Assert(mempool_outpoints.insert(outpoint).second); |
229 | 298k | outpoints_value[outpoint] = 50 * COIN; |
230 | 298k | } |
231 | | |
232 | 2.98k | auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints); |
233 | 2.98k | node.validation_signals->RegisterSharedValidationInterface(outpoints_updater); |
234 | | |
235 | 2.98k | auto tx_pool_{MakeEphemeralMempool(node)}; |
236 | 2.98k | MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get()); |
237 | | |
238 | 2.98k | chainstate.SetMempool(&tx_pool); |
239 | | |
240 | 533k | LIMITED_WHILE (fuzzed_data_provider.remaining_bytes() > 0, 300) { |
241 | 533k | Assert(!mempool_outpoints.empty()); |
242 | | |
243 | 533k | std::vector<CTransactionRef> txs; |
244 | | |
245 | | // Find something we may want to double-spend with two input single tx |
246 | 533k | std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt}; Branch (246:50): [True: 243k, False: 290k]
|
247 | | |
248 | | // Make small packages |
249 | 533k | const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4); Branch (249:30): [True: 8.04k, False: 525k]
|
250 | | |
251 | 533k | std::set<COutPoint> package_outpoints; |
252 | 1.75M | while (txs.size() < num_txs) { Branch (252:16): [True: 1.22M, False: 533k]
|
253 | | // Create transaction to add to the mempool |
254 | 1.22M | txs.emplace_back([&] { |
255 | 1.22M | CMutableTransaction tx_mut; |
256 | 1.22M | tx_mut.version = CTransaction::CURRENT_VERSION; |
257 | 1.22M | tx_mut.nLockTime = 0; |
258 | | // Last transaction in a package needs to be a child of parents to get further in validation |
259 | | // so the last transaction to be generated(in a >1 package) must spend all package-made outputs |
260 | | // Note that this test currently only spends package outputs in last transaction. |
261 | 1.22M | bool last_tx = num_txs > 1 && txs.size() == num_txs - 1; Branch (261:32): [True: 1.03M, False: 181k]
Branch (261:47): [True: 351k, False: 687k]
|
262 | 1.22M | const auto num_in = outpoint_to_rbf ? 2 : Branch (262:37): [True: 8.04k, False: 1.21M]
|
263 | 1.22M | last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) : Branch (263:21): [True: 351k, False: 861k]
|
264 | 1.21M | fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4); |
265 | 1.22M | const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4); Branch (265:38): [True: 8.04k, False: 1.21M]
|
266 | | |
267 | 1.22M | auto& outpoints = last_tx ? package_outpoints : mempool_outpoints; Branch (267:35): [True: 351k, False: 869k]
|
268 | | |
269 | 1.22M | Assert((int)outpoints.size() >= num_in && num_in > 0); |
270 | | |
271 | 1.22M | CAmount amount_in{0}; |
272 | 4.55M | for (int i = 0; i < num_in; ++i) { Branch (272:33): [True: 3.33M, False: 1.22M]
|
273 | | // Pop random outpoint. We erase them to avoid double-spending |
274 | | // while in this loop, but later add them back (unless last_tx). |
275 | 3.33M | auto pop = outpoints.begin(); |
276 | 3.33M | std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1)); |
277 | 3.33M | auto outpoint = *pop; |
278 | | |
279 | 3.33M | if (i == 0 && outpoint_to_rbf) { Branch (279:25): [True: 1.22M, False: 2.11M]
Branch (279:35): [True: 8.04k, False: 1.21M]
|
280 | 8.04k | outpoint = *outpoint_to_rbf; |
281 | 8.04k | outpoints.erase(outpoint); |
282 | 3.32M | } else { |
283 | 3.32M | outpoints.erase(pop); |
284 | 3.32M | } |
285 | | // no need to update or erase from outpoints_value |
286 | 3.33M | amount_in += outpoints_value.at(outpoint); |
287 | | |
288 | | // Create input |
289 | 3.33M | CTxIn in; |
290 | 3.33M | in.prevout = outpoint; |
291 | 3.33M | in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK; |
292 | | |
293 | 3.33M | tx_mut.vin.push_back(in); |
294 | 3.33M | } |
295 | | |
296 | 1.22M | const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in); |
297 | 1.22M | const auto amount_out = (amount_in - amount_fee) / num_out; |
298 | 4.04M | for (int i = 0; i < num_out; ++i) { Branch (298:33): [True: 2.82M, False: 1.22M]
|
299 | 2.82M | tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY); |
300 | 2.82M | } |
301 | | |
302 | | // Note output amounts can naturally drop to dust on their own. |
303 | 1.22M | if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) { Branch (303:21): [True: 1.21M, False: 8.04k]
Branch (303:41): [True: 380k, False: 831k]
|
304 | 380k | uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out); |
305 | 380k | tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY)); |
306 | 380k | } |
307 | | |
308 | 1.22M | auto tx = MakeTransactionRef(tx_mut); |
309 | | // Restore previously removed outpoints, except in-package outpoints (to allow RBF) |
310 | 1.22M | if (!last_tx) { Branch (310:21): [True: 869k, False: 351k]
|
311 | 1.93M | for (const auto& in : tx->vin) { Branch (311:41): [True: 1.93M, False: 869k]
|
312 | 1.93M | Assert(outpoints.insert(in.prevout).second); |
313 | 1.93M | } |
314 | | // Cache the in-package outpoints being made |
315 | 3.04M | for (size_t i = 0; i < tx->vout.size(); ++i) { Branch (315:40): [True: 2.17M, False: 869k]
|
316 | 2.17M | package_outpoints.emplace(tx->GetHash(), i); |
317 | 2.17M | } |
318 | 869k | } |
319 | | // We need newly-created values for the duration of this run |
320 | 4.42M | for (size_t i = 0; i < tx->vout.size(); ++i) { Branch (320:36): [True: 3.20M, False: 1.22M]
|
321 | 3.20M | outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue; |
322 | 3.20M | } |
323 | 1.22M | return tx; |
324 | 1.22M | }()); |
325 | 1.22M | } |
326 | | |
327 | 533k | if (fuzzed_data_provider.ConsumeBool()) { Branch (327:13): [True: 212k, False: 321k]
|
328 | 212k | const auto& txid = fuzzed_data_provider.ConsumeBool() ? Branch (328:32): [True: 69.4k, False: 142k]
|
329 | 69.4k | txs.back()->GetHash() : |
330 | 212k | PickValue(fuzzed_data_provider, mempool_outpoints).hash; |
331 | 212k | const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); |
332 | | // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't |
333 | | // filter for ephemeral dust |
334 | 212k | if (tx_pool.exists(txid)) { Branch (334:17): [True: 64.4k, False: 147k]
|
335 | 64.4k | const auto tx_info{tx_pool.info(txid)}; |
336 | 64.4k | if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) { Branch (336:21): [True: 63.7k, False: 714]
|
337 | 63.7k | tx_pool.PrioritiseTransaction(txid, delta); |
338 | 63.7k | } |
339 | 64.4k | } |
340 | 212k | } |
341 | | |
342 | 533k | auto single_submit = txs.size() == 1; |
343 | | |
344 | 533k | const auto result_package = WITH_LOCK(::cs_main, |
345 | 533k | return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{})); |
346 | | |
347 | 533k | const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), |
348 | 533k | /*bypass_limits=*/false, /*test_accept=*/!single_submit)); |
349 | | |
350 | 533k | if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) { Branch (350:13): [True: 351k, False: 181k]
Branch (350:31): [True: 201k, False: 150k]
|
351 | | // We don't know anything about the validity since transactions were randomly generated, so |
352 | | // just use result_package.m_state here. This makes the expect_valid check meaningless, but |
353 | | // we can still verify that the contents of m_tx_results are consistent with m_state. |
354 | 201k | const bool expect_valid{result_package.m_state.IsValid()}; |
355 | 201k | Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool)); |
356 | 201k | } |
357 | | |
358 | 533k | node.validation_signals->SyncWithValidationInterfaceQueue(); |
359 | | |
360 | 533k | CheckMempoolEphemeralInvariants(tx_pool); |
361 | 533k | } |
362 | | |
363 | 2.98k | node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater); |
364 | | |
365 | 2.98k | WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); |
366 | 2.98k | } |
367 | | |
368 | | |
369 | | FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool) |
370 | 3.19k | { |
371 | 3.19k | SeedRandomStateForTest(SeedRand::ZEROS); |
372 | 3.19k | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
373 | 3.19k | const auto& node = g_setup->m_node; |
374 | 3.19k | auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())}; |
375 | | |
376 | 3.19k | MockTime(fuzzed_data_provider, chainstate); |
377 | | |
378 | | // All RBF-spendable outpoints outside of the unsubmitted package |
379 | 3.19k | std::set<COutPoint> mempool_outpoints; |
380 | 3.19k | std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value; |
381 | 319k | for (const auto& outpoint : g_outpoints_coinbase_init_mature) { Branch (381:31): [True: 319k, False: 3.19k]
|
382 | 319k | Assert(mempool_outpoints.insert(outpoint).second); |
383 | 319k | outpoints_value[outpoint] = 50 * COIN; |
384 | 319k | } |
385 | | |
386 | 3.19k | auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints); |
387 | 3.19k | node.validation_signals->RegisterSharedValidationInterface(outpoints_updater); |
388 | | |
389 | 3.19k | auto tx_pool_{MakeMempool(fuzzed_data_provider, node)}; |
390 | 3.19k | MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get()); |
391 | | |
392 | 3.19k | chainstate.SetMempool(&tx_pool); |
393 | | |
394 | 353k | LIMITED_WHILE (fuzzed_data_provider.remaining_bytes() > 0, 300) { |
395 | 353k | Assert(!mempool_outpoints.empty()); |
396 | | |
397 | 353k | std::vector<CTransactionRef> txs; |
398 | | |
399 | | // Make packages of 1-to-26 transactions |
400 | 353k | const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26); |
401 | 353k | std::set<COutPoint> package_outpoints; |
402 | 1.20M | while (txs.size() < num_txs) { Branch (402:16): [True: 852k, False: 353k]
|
403 | | // Create transaction to add to the mempool |
404 | 852k | txs.emplace_back([&] { |
405 | 852k | CMutableTransaction tx_mut; |
406 | 852k | tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION; Branch (406:34): [True: 242k, False: 609k]
|
407 | 852k | tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); Branch (407:36): [True: 302k, False: 550k]
|
408 | | // Last transaction in a package needs to be a child of parents to get further in validation |
409 | | // so the last transaction to be generated(in a >1 package) must spend all package-made outputs |
410 | | // Note that this test currently only spends package outputs in last transaction. |
411 | 852k | bool last_tx = num_txs > 1 && txs.size() == num_txs - 1; Branch (411:32): [True: 606k, False: 246k]
Branch (411:47): [True: 107k, False: 498k]
|
412 | 852k | const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size()); Branch (412:37): [True: 107k, False: 744k]
|
413 | 852k | auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2); |
414 | | |
415 | 852k | auto& outpoints = last_tx ? package_outpoints : mempool_outpoints; Branch (415:35): [True: 107k, False: 744k]
|
416 | | |
417 | 852k | Assert(!outpoints.empty()); |
418 | | |
419 | 852k | CAmount amount_in{0}; |
420 | 16.3M | for (size_t i = 0; i < num_in; ++i) { Branch (420:36): [True: 15.5M, False: 852k]
|
421 | | // Pop random outpoint. We erase them to avoid double-spending |
422 | | // while in this loop, but later add them back (unless last_tx). |
423 | 15.5M | auto pop = outpoints.begin(); |
424 | 15.5M | std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1)); |
425 | 15.5M | const auto outpoint = *pop; |
426 | 15.5M | outpoints.erase(pop); |
427 | | // no need to update or erase from outpoints_value |
428 | 15.5M | amount_in += outpoints_value.at(outpoint); |
429 | | |
430 | | // Create input |
431 | 15.5M | const auto sequence = ConsumeSequence(fuzzed_data_provider); |
432 | 15.5M | const auto script_sig = CScript{}; |
433 | 15.5M | const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK; Branch (433:51): [True: 6.30M, False: 9.24M]
|
434 | | |
435 | 15.5M | CTxIn in; |
436 | 15.5M | in.prevout = outpoint; |
437 | 15.5M | in.nSequence = sequence; |
438 | 15.5M | in.scriptSig = script_sig; |
439 | 15.5M | in.scriptWitness.stack = script_wit_stack; |
440 | | |
441 | 15.5M | tx_mut.vin.push_back(in); |
442 | 15.5M | } |
443 | | |
444 | | // Duplicate an input |
445 | 852k | bool dup_input = fuzzed_data_provider.ConsumeBool(); |
446 | 852k | if (dup_input) { Branch (446:21): [True: 266k, False: 585k]
|
447 | 266k | tx_mut.vin.push_back(tx_mut.vin.back()); |
448 | 266k | } |
449 | | |
450 | | // Refer to a non-existent input |
451 | 852k | if (fuzzed_data_provider.ConsumeBool()) { Branch (451:21): [True: 244k, False: 607k]
|
452 | 244k | tx_mut.vin.emplace_back(); |
453 | 244k | } |
454 | | |
455 | | // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent |
456 | 852k | if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) { Branch (456:21): [True: 107k, False: 744k]
Branch (456:32): [True: 107k, False: 146]
Branch (456:52): [True: 42.0k, False: 65.2k]
|
457 | 42.0k | tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG); |
458 | | // Don't add any other outputs. |
459 | 42.0k | num_out = 1; |
460 | 42.0k | amount_in -= 1000; |
461 | 42.0k | } |
462 | | |
463 | 852k | const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in); |
464 | 852k | const auto amount_out = (amount_in - amount_fee) / num_out; |
465 | 21.3M | for (int i = 0; i < num_out; ++i) { Branch (465:33): [True: 20.5M, False: 852k]
|
466 | 20.5M | tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY); |
467 | 20.5M | } |
468 | 852k | auto tx = MakeTransactionRef(tx_mut); |
469 | | // Restore previously removed outpoints, except in-package outpoints |
470 | 852k | if (!last_tx) { Branch (470:21): [True: 744k, False: 107k]
|
471 | 7.74M | for (const auto& in : tx->vin) { Branch (471:41): [True: 7.74M, False: 744k]
|
472 | | // It's a fake input, or a new input, or a duplicate |
473 | 7.74M | Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input); |
474 | 7.74M | } |
475 | | // Cache the in-package outpoints being made |
476 | 19.8M | for (size_t i = 0; i < tx->vout.size(); ++i) { Branch (476:40): [True: 19.0M, False: 744k]
|
477 | 19.0M | package_outpoints.emplace(tx->GetHash(), i); |
478 | 19.0M | } |
479 | 744k | } |
480 | | // We need newly-created values for the duration of this run |
481 | 21.4M | for (size_t i = 0; i < tx->vout.size(); ++i) { Branch (481:36): [True: 20.5M, False: 852k]
|
482 | 20.5M | outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue; |
483 | 20.5M | } |
484 | 852k | return tx; |
485 | 852k | }()); |
486 | 852k | } |
487 | | |
488 | 353k | if (fuzzed_data_provider.ConsumeBool()) { Branch (488:13): [True: 178k, False: 175k]
|
489 | 178k | MockTime(fuzzed_data_provider, chainstate); |
490 | 178k | } |
491 | 353k | if (fuzzed_data_provider.ConsumeBool()) { Branch (491:13): [True: 89.9k, False: 263k]
|
492 | 89.9k | tx_pool.RollingFeeUpdate(); |
493 | 89.9k | } |
494 | 353k | if (fuzzed_data_provider.ConsumeBool()) { Branch (494:13): [True: 112k, False: 241k]
|
495 | 112k | const auto& txid = fuzzed_data_provider.ConsumeBool() ? Branch (495:32): [True: 55.7k, False: 56.8k]
|
496 | 55.7k | txs.back()->GetHash() : |
497 | 112k | PickValue(fuzzed_data_provider, mempool_outpoints).hash; |
498 | 112k | const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); |
499 | 112k | tx_pool.PrioritiseTransaction(txid, delta); |
500 | 112k | } |
501 | | |
502 | | // Remember all added transactions |
503 | 353k | std::set<CTransactionRef> added; |
504 | 353k | auto txr = std::make_shared<TransactionsDelta>(added); |
505 | 353k | node.validation_signals->RegisterSharedValidationInterface(txr); |
506 | | |
507 | | // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false) |
508 | | // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it |
509 | | // (the package is a test accept and ATMP is a submission). |
510 | 353k | auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool(); Branch (510:30): [True: 246k, False: 107k]
Branch (510:49): [True: 36.4k, False: 209k]
|
511 | | |
512 | | // Exercise client_maxfeerate logic |
513 | 353k | std::optional<CFeeRate> client_maxfeerate{}; |
514 | 353k | if (fuzzed_data_provider.ConsumeBool()) { Branch (514:13): [True: 82.1k, False: 271k]
|
515 | 82.1k | client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100); |
516 | 82.1k | } |
517 | | |
518 | 353k | const auto result_package = WITH_LOCK(::cs_main, |
519 | 353k | return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate)); |
520 | | |
521 | | // Always set bypass_limits to false because it is not supported in ProcessNewPackage and |
522 | | // can be a source of divergence. |
523 | 353k | const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), |
524 | 353k | /*bypass_limits=*/false, /*test_accept=*/!single_submit)); |
525 | 353k | const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID; |
526 | | |
527 | 353k | node.validation_signals->SyncWithValidationInterfaceQueue(); |
528 | 353k | node.validation_signals->UnregisterSharedValidationInterface(txr); |
529 | | |
530 | | // There is only 1 transaction in the package. We did a test-package-accept and a ATMP |
531 | 353k | if (single_submit) { Branch (531:13): [True: 36.4k, False: 317k]
|
532 | 36.4k | Assert(passed != added.empty()); |
533 | 36.4k | Assert(passed == res.m_state.IsValid()); |
534 | 36.4k | if (passed) { Branch (534:17): [True: 4.43k, False: 32.0k]
|
535 | 4.43k | Assert(added.size() == 1); |
536 | 4.43k | Assert(txs.back() == *added.begin()); |
537 | 4.43k | } |
538 | 317k | } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) { Branch (538:20): [True: 264k, False: 52.5k]
|
539 | | // We don't know anything about the validity since transactions were randomly generated, so |
540 | | // just use result_package.m_state here. This makes the expect_valid check meaningless, but |
541 | | // we can still verify that the contents of m_tx_results are consistent with m_state. |
542 | 264k | const bool expect_valid{result_package.m_state.IsValid()}; |
543 | 264k | Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool)); |
544 | 264k | } else { |
545 | | // This is empty if it fails early checks, or "full" if transactions are looked at deeper |
546 | 52.5k | Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty()); |
547 | 52.5k | } |
548 | | |
549 | 353k | CheckMempoolTRUCInvariants(tx_pool); |
550 | | |
551 | | // Dust checks only make sense when dust is enforced |
552 | 353k | if (tx_pool.m_opts.require_standard) { Branch (552:13): [True: 205k, False: 147k]
|
553 | 205k | CheckMempoolEphemeralInvariants(tx_pool); |
554 | 205k | } |
555 | 353k | } |
556 | | |
557 | 3.19k | node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater); |
558 | | |
559 | 3.19k | WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); |
560 | 3.19k | } |
561 | | } // namespace |