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