Branch data Line data Source code
1 : : // Copyright (c) 2020-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 <node/mempool_args.h>
6 : : #include <policy/rbf.h>
7 : : #include <primitives/transaction.h>
8 : : #include <sync.h>
9 : : #include <test/fuzz/FuzzedDataProvider.h>
10 : : #include <test/fuzz/fuzz.h>
11 : : #include <test/fuzz/util.h>
12 : : #include <test/fuzz/util/mempool.h>
13 : : #include <test/util/setup_common.h>
14 : : #include <test/util/txmempool.h>
15 : : #include <txmempool.h>
16 : : #include <util/check.h>
17 : : #include <util/translation.h>
18 : :
19 : : #include <cstdint>
20 : : #include <optional>
21 : : #include <string>
22 : : #include <vector>
23 : :
24 : : namespace {
25 : : const BasicTestingSetup* g_setup;
26 : : } // namespace
27 : :
28 : : const int NUM_ITERS = 10000;
29 : :
30 : : std::vector<COutPoint> g_outpoints;
31 : :
32 : 1 : void initialize_rbf()
33 : : {
34 [ + - + - : 2 : static const auto testing_setup = MakeNoLogFileContext<>();
+ - ]
35 : 1 : g_setup = testing_setup.get();
36 : 1 : }
37 : :
38 : 1 : void initialize_package_rbf()
39 : : {
40 [ + - + - : 2 : static const auto testing_setup = MakeNoLogFileContext<>();
+ - ]
41 : 1 : g_setup = testing_setup.get();
42 : :
43 : : // Create a fixed set of unique "UTXOs" to source parents from
44 : : // to avoid fuzzer giving circular references
45 [ + + ]: 10001 : for (int i = 0; i < NUM_ITERS; ++i) {
46 : 10000 : g_outpoints.emplace_back();
47 : 10000 : g_outpoints.back().n = i;
48 : : }
49 : :
50 : 1 : }
51 : :
52 [ + - ]: 1259 : FUZZ_TARGET(rbf, .init = initialize_rbf)
53 : : {
54 : 803 : SeedRandomStateForTest(SeedRand::ZEROS);
55 : 803 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
56 : 803 : SetMockTime(ConsumeTime(fuzzed_data_provider));
57 : 803 : std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
58 [ + + ]: 803 : if (!mtx) {
59 : 36 : return;
60 : : }
61 : :
62 [ + - ]: 767 : bilingual_str error;
63 [ + - + - ]: 767 : CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
64 [ - + ]: 767 : Assert(error.empty());
65 : :
66 [ + + + - ]: 199065 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
67 : : {
68 : 198489 : const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
69 [ + + ]: 198489 : if (!another_mtx) {
70 : : break;
71 : : }
72 [ + - ]: 198298 : const CTransaction another_tx{*another_mtx};
73 [ + + + + ]: 198298 : if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {
74 : 14595 : mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
75 : : }
76 [ + - + - ]: 198298 : LOCK2(cs_main, pool.cs);
77 [ + - + + ]: 198298 : if (!pool.GetIter(another_tx.GetHash())) {
78 [ + - ]: 190010 : TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
79 : : }
80 [ + - + - ]: 793383 : }
81 [ + - ]: 767 : const CTransaction tx{*mtx};
82 [ + + ]: 767 : if (fuzzed_data_provider.ConsumeBool()) {
83 [ + - + - ]: 320 : LOCK2(cs_main, pool.cs);
84 [ + - + + ]: 320 : if (!pool.GetIter(tx.GetHash())) {
85 [ + - ]: 285 : TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
86 : : }
87 [ + - ]: 640 : }
88 : 767 : {
89 [ + - ]: 767 : LOCK(pool.cs);
90 [ + - ]: 767 : (void)IsRBFOptIn(tx, pool);
91 : 767 : }
92 [ + - ]: 2337 : }
93 : :
94 [ + - ]: 1619 : FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
95 : : {
96 : 1163 : SeedRandomStateForTest(SeedRand::ZEROS);
97 : 1163 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
98 : 1163 : SetMockTime(ConsumeTime(fuzzed_data_provider));
99 : :
100 : : // "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values
101 : : // so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent.
102 : 1163 : CMutableTransaction child;
103 [ + - ]: 1163 : child.vin.resize(1);
104 : :
105 [ + - ]: 1163 : bilingual_str error;
106 [ + - + - ]: 1163 : CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
107 [ - + ]: 1163 : Assert(error.empty());
108 : :
109 : : // Add a bunch of parent-child pairs to the mempool, and remember them.
110 : 1163 : std::vector<CTransaction> mempool_txs;
111 : 1163 : uint32_t iter{0};
112 : :
113 : : // Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
114 : : // Add replacement_vsize since this is added to new diagram during RBF check
115 : 1163 : std::optional<CMutableTransaction> replacement_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
116 [ + + ]: 1163 : if (!replacement_tx) {
117 : 102 : return;
118 : : }
119 [ + - ]: 1061 : replacement_tx->vin.resize(1);
120 [ + - + - ]: 1061 : replacement_tx->vin[0].prevout = g_outpoints.at(iter++);
121 [ + - ]: 1061 : CTransaction replacement_tx_final{*replacement_tx};
122 : 1061 : auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);
123 [ + - ]: 1061 : int32_t replacement_weight = replacement_entry.GetAdjustedWeight();
124 : : // Ensure that we don't hit FeeFrac limits, as we store TxGraph entries in terms of FeePerWeight
125 [ + - ]: 1061 : int64_t running_vsize_total{replacement_entry.GetTxSize()};
126 : :
127 [ + - + - ]: 1061 : LOCK2(cs_main, pool.cs);
128 : :
129 [ + + ]: 458022 : while (fuzzed_data_provider.ConsumeBool()) {
130 [ + - ]: 457043 : if (iter >= NUM_ITERS) break;
131 : :
132 : : // Make sure txns only have one input, and that a unique input is given to avoid circular references
133 [ + - ]: 457043 : CMutableTransaction parent;
134 [ + - ]: 457043 : parent.vin.resize(1);
135 [ + - ]: 457043 : parent.vin[0].prevout = g_outpoints.at(iter++);
136 [ + - ]: 457043 : parent.vout.emplace_back(0, CScript());
137 : :
138 [ + - ]: 457043 : mempool_txs.emplace_back(parent);
139 : 457043 : const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
140 [ + - ]: 457043 : running_vsize_total += parent_entry.GetTxSize();
141 [ + + ]: 457043 : if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {
142 : : // We aren't adding this final tx to mempool, so we don't want to conflict with it
143 : 73 : mempool_txs.pop_back();
144 : 73 : break;
145 : : }
146 [ + - - + ]: 456970 : assert(!pool.GetIter(parent_entry.GetTx().GetHash()));
147 [ + - ]: 456970 : TryAddToMempool(pool, parent_entry);
148 : :
149 : : // It's possible that adding this to the mempool failed due to cluster
150 : : // size limits; if so bail out.
151 [ + - + + ]: 456970 : if(!pool.GetIter(parent_entry.GetTx().GetHash())) {
152 : 285491 : mempool_txs.pop_back();
153 : 285491 : continue;
154 : : }
155 : :
156 [ + - ]: 171479 : child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
157 [ + - ]: 171479 : mempool_txs.emplace_back(child);
158 : 171479 : const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
159 [ + - ]: 171479 : running_vsize_total += child_entry.GetTxSize();
160 [ + + ]: 171479 : if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {
161 : : // We aren't adding this final tx to mempool, so we don't want to conflict with it
162 : 9 : mempool_txs.pop_back();
163 : 9 : break;
164 : : }
165 [ + - + - ]: 171470 : if (!pool.GetIter(child_entry.GetTx().GetHash())) {
166 [ + - ]: 171470 : TryAddToMempool(pool, child_entry);
167 : : // Adding this transaction to the mempool may fail due to cluster
168 : : // size limits; if so bail out.
169 [ + - + + ]: 171470 : if(!pool.GetIter(child_entry.GetTx().GetHash())) {
170 : 106119 : mempool_txs.pop_back();
171 : 106119 : continue;
172 : : }
173 : : }
174 : :
175 [ + + ]: 65351 : if (fuzzed_data_provider.ConsumeBool()) {
176 [ + - ]: 65116 : pool.PrioritiseTransaction(mempool_txs.back().GetHash(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
177 : : }
178 : 914086 : }
179 : :
180 : : // Pick some transactions at random to be the direct conflicts
181 : 1061 : CTxMemPool::setEntries direct_conflicts;
182 [ + + ]: 237891 : for (auto& tx : mempool_txs) {
183 [ + + + - : 236830 : if (fuzzed_data_provider.ConsumeBool() && pool.GetIter(tx.GetHash())) {
+ - ]
184 [ + - + - ]: 201325 : direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
185 : : }
186 : : }
187 : :
188 : : // Calculate all conflicts:
189 : 1061 : CTxMemPool::setEntries all_conflicts;
190 [ + + ]: 202386 : for (auto& txiter : direct_conflicts) {
191 [ + - ]: 201325 : pool.CalculateDescendants(txiter, all_conflicts);
192 : : }
193 : :
194 : 1061 : CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
195 [ + - ]: 1061 : auto changeset = pool.GetChangeSet();
196 [ + + ]: 208190 : for (auto& txiter : all_conflicts) {
197 [ + - ]: 207129 : changeset->StageRemoval(txiter);
198 : : }
199 [ + - + - ]: 2122 : changeset->StageAddition(replacement_entry.GetSharedTx(), replacement_fees,
200 [ + - ]: 1061 : replacement_entry.GetTime().count(), replacement_entry.GetHeight(),
201 [ + - ]: 1061 : replacement_entry.GetSequence(), replacement_entry.GetSpendsCoinbase(),
202 [ + - ]: 1061 : replacement_entry.GetSigOpCost(), replacement_entry.GetLockPoints());
203 : : // Calculate the chunks for a replacement.
204 [ + - ]: 1061 : auto calc_results{changeset->CalculateChunksForRBF()};
205 : :
206 [ + + ]: 1061 : if (calc_results.has_value()) {
207 : : // Sanity checks on the chunks.
208 : :
209 : : // Feerates are monotonically decreasing.
210 : 482 : FeeFrac first_sum;
211 [ - + + + ]: 121269 : for (size_t i = 0; i < calc_results->first.size(); ++i) {
212 [ + + ]: 120787 : first_sum += calc_results->first[i];
213 [ + + - + ]: 120787 : if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
214 : : }
215 : 482 : FeeFrac second_sum;
216 [ - + + + ]: 8858 : for (size_t i = 0; i < calc_results->second.size(); ++i) {
217 [ + + ]: 8376 : second_sum += calc_results->second[i];
218 [ + + - + ]: 8376 : if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
219 : : }
220 : :
221 : 482 : FeeFrac replaced;
222 [ + + ]: 131489 : for (auto txiter : all_conflicts) {
223 [ + - ]: 131007 : replaced.fee += txiter->GetModifiedFee();
224 [ + - ]: 131007 : replaced.size += txiter->GetAdjustedWeight();
225 : : }
226 : : // The total fee & size of the new diagram minus replaced fee & size should be the total
227 : : // fee & size of the old diagram minus replacement fee & size.
228 [ + - ]: 482 : assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_weight}));
229 : : }
230 : :
231 : : // If internals report error, wrapper should too
232 [ + - ]: 1061 : auto err_tuple{ImprovesFeerateDiagram(*changeset)};
233 [ + + ]: 1061 : if (!calc_results.has_value()) {
234 [ + - - + ]: 579 : assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
235 : : } else {
236 : : // Diagram check succeeded
237 : 482 : auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
238 : 482 : auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
239 [ + + ]: 482 : if (!err_tuple.has_value()) {
240 : : // New diagram's final fee should always match or exceed old diagram's
241 [ - + ]: 129 : assert(old_sum.fee <= new_sum.fee);
242 [ + + ]: 353 : } else if (old_sum.fee > new_sum.fee) {
243 : : // Or it failed, and if old diagram had higher fees, it should be a failure
244 [ - + ]: 234 : assert(err_tuple.value().first == DiagramCheckError::FAILURE);
245 : : }
246 : : }
247 [ + - + - : 7733 : }
+ - ]
|