Line data Source code
1 : // Copyright (c) 2023 The Bitcoin Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <consensus/validation.h>
6 : #include <node/context.h>
7 : #include <node/mempool_args.h>
8 : #include <node/miner.h>
9 : #include <test/fuzz/FuzzedDataProvider.h>
10 : #include <test/fuzz/fuzz.h>
11 2 : #include <test/fuzz/util.h>
12 2 : #include <test/fuzz/util/mempool.h>
13 2 : #include <test/util/mining.h>
14 2 : #include <test/util/script.h>
15 2 : #include <test/util/setup_common.h>
16 : #include <test/util/txmempool.h>
17 2 : #include <util/rbf.h>
18 2 : #include <validation.h>
19 : #include <validationinterface.h>
20 :
21 : using node::BlockAssembler;
22 : using node::NodeContext;
23 :
24 : namespace {
25 :
26 : const TestingSetup* g_setup;
27 2 : std::vector<COutPoint> g_outpoints_coinbase_init_mature;
28 2 : std::vector<COutPoint> g_outpoints_coinbase_init_immature;
29 :
30 : struct MockedTxPool : public CTxMemPool {
31 3045 : void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
32 : {
33 3045 : LOCK(cs);
34 3045 : lastRollingFeeUpdate = GetTime();
35 3045 : blockSinceLastRollingFeeBump = true;
36 3045 : }
37 : };
38 :
39 1 : void initialize_tx_pool()
40 : {
41 1 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
42 1 : g_setup = testing_setup.get();
43 :
44 201 : for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
45 200 : COutPoint prevout{MineBlock(g_setup->m_node, P2WSH_OP_TRUE)};
46 : // Remember the txids to avoid expensive disk access later on
47 200 : auto& outpoints = i < COINBASE_MATURITY ?
48 : g_outpoints_coinbase_init_mature :
49 : g_outpoints_coinbase_init_immature;
50 200 : outpoints.push_back(prevout);
51 200 : }
52 1 : SyncWithValidationInterfaceQueue();
53 1 : }
54 :
55 : struct TransactionsDelta final : public CValidationInterface {
56 : std::set<CTransactionRef>& m_removed;
57 : std::set<CTransactionRef>& m_added;
58 :
59 7672 : explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a)
60 7672 : : m_removed{r}, m_added{a} {}
61 :
62 2196 : void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override
63 : {
64 : // Transactions may be entered and booted any number of times
65 2196 : m_added.insert(tx);
66 2196 : }
67 :
68 309 : void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
69 : {
70 : // Transactions may be entered and booted any number of times
71 309 : m_removed.insert(tx);
72 309 : }
73 : };
74 2 :
75 4859 : void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate)
76 : {
77 9718 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
78 : {
79 4859 : BlockAssembler::Options options;
80 4859 : options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
81 4859 : options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
82 4859 : auto assembler = BlockAssembler{chainstate, &tx_pool, options};
83 4859 : auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE);
84 4859 : Assert(block_template->block.vtx.size() >= 1);
85 4859 : }
86 4859 : const auto info_all = tx_pool.infoAll();
87 4859 : if (!info_all.empty()) {
88 1568 : const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx;
89 3136 : WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, MemPoolRemovalReason::BLOCK /* dummy */));
90 1568 : std::vector<uint256> all_txids;
91 1568 : tx_pool.queryHashes(all_txids);
92 1568 : assert(all_txids.size() < info_all.size());
93 3136 : WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
94 1568 : }
95 4859 : SyncWithValidationInterfaceQueue();
96 4859 : }
97 :
98 7959 : void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
99 : {
100 15918 : const auto time = ConsumeTime(fuzzed_data_provider,
101 7959 : chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
102 7959 : std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
103 7959 : SetMockTime(time);
104 7959 : }
105 :
106 4859 : CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
107 : {
108 : // Take the default options for tests...
109 4859 : CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
110 :
111 :
112 : // ...override specific options for this specific fuzz suite
113 4859 : mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
114 4859 : mempool_opts.limits.ancestor_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
115 4859 : mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
116 4859 : mempool_opts.limits.descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202) * 1'000;
117 4859 : mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
118 4859 : mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
119 4859 : nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999);
120 :
121 4859 : mempool_opts.estimator = nullptr;
122 4859 : mempool_opts.check_ratio = 1;
123 4859 : mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
124 :
125 : // ...and construct a CTxMemPool from it
126 4859 : return CTxMemPool{mempool_opts};
127 : }
128 :
129 4863 : FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
130 : {
131 4859 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
132 4859 : const auto& node = g_setup->m_node;
133 4859 : auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
134 :
135 4859 : MockTime(fuzzed_data_provider, chainstate);
136 :
137 : // All RBF-spendable outpoints outside of the immediate package
138 4859 : std::set<COutPoint> outpoints_rbf;
139 : // All outpoints counting toward the total supply (subset of outpoints_rbf)
140 4859 : std::set<COutPoint> outpoints_supply;
141 4859 : std::map<COutPoint, CAmount> outpoints_value;
142 490759 : for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
143 485900 : Assert(outpoints_supply.insert(outpoint).second);
144 485900 : outpoints_value[outpoint] = 50 * COIN;
145 : }
146 :
147 : // Seeded by coinbase outputs first
148 4859 : outpoints_rbf = outpoints_supply;
149 :
150 4859 : CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)};
151 4859 : MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
152 :
153 4859 : chainstate.SetMempool(&tx_pool);
154 :
155 12531 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
156 : {
157 7672 : Assert(!outpoints_supply.empty());
158 :
159 7672 : std::vector<CTransactionRef> txs;
160 7672 : std::map<uint256, CTransactionRef> wtxid_to_tx;
161 :
162 :
163 : // Make packages of 1-to-26 transactions
164 7672 : const auto num_txs = (size_t) fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 26);
165 7672 : std::set<COutPoint> package_outpoints;
166 35142 : while (txs.size() < num_txs) {
167 :
168 : // Last transaction in a package needs to be a child of parents to get further in validation
169 : // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
170 : // Note that this test currently only spends unconfirmed outputs in last transaction.
171 27470 : bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
172 :
173 : // Create transaction to add to the mempool
174 54940 : const CTransactionRef tx = [&] {
175 27470 : CMutableTransaction tx_mut;
176 27470 : tx_mut.nVersion = CTransaction::CURRENT_VERSION;
177 27470 : tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
178 : // Last tx will sweep all outpoints in package
179 27470 : const auto num_in = last_tx ? package_outpoints.size() : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size());
180 27470 : const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2);
181 :
182 27470 : auto& outpoints = last_tx ? package_outpoints : outpoints_rbf;
183 :
184 27470 : CAmount amount_in{0};
185 347382 : for (size_t i = 0; i < num_in; ++i) {
186 : // Pop random outpoint
187 319912 : auto pop = outpoints.begin();
188 319912 : std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
189 319912 : const auto outpoint = *pop;
190 319912 : outpoints.erase(pop);
191 319912 : amount_in += outpoints_value.at(outpoint);
192 :
193 : // Create input
194 319912 : const auto sequence = ConsumeSequence(fuzzed_data_provider);
195 319912 : const auto script_sig = CScript{};
196 319912 : const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
197 319912 : CTxIn in;
198 319912 : in.prevout = outpoint;
199 319912 : in.nSequence = sequence;
200 319912 : in.scriptSig = script_sig;
201 319912 : in.scriptWitness.stack = script_wit_stack;
202 :
203 319912 : tx_mut.vin.push_back(in);
204 319912 : }
205 27470 : const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
206 27470 : const auto amount_out = (amount_in - amount_fee) / num_out;
207 404885 : for (int i = 0; i < num_out; ++i) {
208 377415 : tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
209 377415 : }
210 : // TODO vary transaction sizes to catch size-related issues
211 27470 : auto tx = MakeTransactionRef(tx_mut);
212 : // Restore previously removed outpoints, except in-package outpoints
213 27470 : if (!last_tx) {
214 186608 : for (const auto& in : tx->vin) {
215 162857 : Assert(outpoints.insert(in.prevout).second);
216 : }
217 23751 : }
218 : // Cache the in-package outpoints being made
219 404885 : for (size_t i = 0; i < tx->vout.size(); ++i) {
220 377415 : package_outpoints.emplace(tx->GetHash(), i);
221 377415 : outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
222 377415 : }
223 27470 : return tx;
224 27470 : }();
225 27470 : txs.push_back(tx);
226 27470 : wtxid_to_tx[tx->GetWitnessHash()] = tx;
227 27470 : }
228 :
229 7672 : if (fuzzed_data_provider.ConsumeBool()) {
230 3100 : MockTime(fuzzed_data_provider, chainstate);
231 3100 : }
232 7672 : if (fuzzed_data_provider.ConsumeBool()) {
233 3045 : tx_pool.RollingFeeUpdate();
234 3045 : }
235 7672 : if (fuzzed_data_provider.ConsumeBool()) {
236 4648 : const auto& txid = fuzzed_data_provider.ConsumeBool() ?
237 1834 : txs.back()->GetHash() :
238 490 : PickValue(fuzzed_data_provider, outpoints_rbf).hash;
239 2324 : const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
240 2324 : tx_pool.PrioritiseTransaction(txid, delta);
241 2324 : }
242 :
243 : // Remember all removed and added transactions
244 7672 : std::set<CTransactionRef> removed;
245 7672 : std::set<CTransactionRef> added;
246 7672 : auto txr = std::make_shared<TransactionsDelta>(removed, added);
247 7672 : RegisterSharedValidationInterface(txr);
248 7672 : const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
249 :
250 : // Single-tx packages should be rejected, so do that sometimes, and sometimes send it via single submission
251 : // to allow it into the mempool by itself to make more interesting mempool packages
252 11625 : auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
253 7672 : auto package_submit = !single_submit;
254 :
255 15344 : const auto result_package = WITH_LOCK(::cs_main,
256 : return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/!package_submit));
257 : // If something went wrong due to a package-specific policy, it might not return a
258 : // validation result for the transaction.
259 7672 : if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
260 4892 : auto it = result_package.m_tx_results.find(txs.back()->GetWitnessHash());
261 4892 : Assert(it != result_package.m_tx_results.end());
262 4892 : Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
263 : it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID ||
264 : it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
265 4892 : }
266 :
267 15344 : const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), bypass_limits, /*test_accept=*/!single_submit));
268 7672 : const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
269 :
270 7672 : SyncWithValidationInterfaceQueue();
271 7672 : UnregisterSharedValidationInterface(txr);
272 :
273 7672 : if (single_submit) {
274 2481 : Assert(accepted != added.empty());
275 2481 : Assert(accepted == res.m_state.IsValid());
276 2481 : Assert(accepted != res.m_state.IsInvalid());
277 2481 : if (accepted) {
278 990 : Assert(added.size() == 1);
279 990 : Assert(txs.back() == *added.begin());
280 990 : } else {
281 : // Do not consider rejected transaction removed
282 1491 : removed.erase(txs.back());
283 : }
284 2481 : } else {
285 : // This is empty if it fails early checks, or "full" if transactions are looked at deeper
286 5191 : Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
287 5191 : if (result_package.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
288 22922 : for (const auto& tx : txs) {
289 20142 : removed.erase(tx);
290 : }
291 2780 : } else {
292 7258 : for (const auto& [k, v] : result_package.m_tx_results) {
293 4847 : if (v.m_result_type == MempoolAcceptResult::ResultType::INVALID) {
294 3357 : removed.erase(wtxid_to_tx[k]);
295 3357 : }
296 : }
297 : }
298 : }
299 :
300 : // Helper to insert spent and created outpoints of a tx into collections
301 : using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>;
302 2311 : const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) {
303 35303 : for (size_t i{0}; i < tx.vout.size(); ++i) {
304 98603 : for (auto& set : created_by_tx) {
305 65611 : set.get().emplace(tx.GetHash(), i);
306 : }
307 32992 : }
308 7373 : for (const auto& in : tx.vin) {
309 10124 : for (auto& set : consumed_by_tx) {
310 5062 : set.get().insert(in.prevout);
311 : }
312 : }
313 2311 : };
314 :
315 : // Add created outpoints, remove spent outpoints
316 : {
317 : // Outpoints that no longer exist at all
318 7672 : std::set<COutPoint> consumed_erased;
319 : // Outpoints that no longer count toward the total supply
320 7672 : std::set<COutPoint> consumed_supply;
321 7787 : for (const auto& removed_tx : removed) {
322 115 : insert_tx(/*created_by_tx=*/{consumed_erased}, /*consumed_by_tx=*/{outpoints_supply}, /*tx=*/*removed_tx);
323 : }
324 9868 : for (const auto& added_tx : added) {
325 2196 : insert_tx(/*created_by_tx=*/{outpoints_supply, outpoints_rbf}, /*consumed_by_tx=*/{consumed_supply}, /*tx=*/*added_tx);
326 : }
327 8045 : for (const auto& p : consumed_erased) {
328 373 : outpoints_supply.erase(p);
329 373 : outpoints_rbf.erase(p);
330 : }
331 12458 : for (const auto& p : consumed_supply) {
332 4786 : outpoints_supply.erase(p);
333 : }
334 7672 : }
335 7672 : }
336 4859 : Finish(fuzzed_data_provider, tx_pool, chainstate);
337 4859 : }
338 : } // namespace
|