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