Branch data Line data Source code
1 : : // Copyright (c) 2020-2022 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 : 0 : void initialize_rbf()
33 : : {
34 [ # # # # : 0 : static const auto testing_setup = MakeNoLogFileContext<>();
# # ]
35 : 0 : g_setup = testing_setup.get();
36 : 0 : }
37 : :
38 : 0 : void initialize_package_rbf()
39 : : {
40 [ # # # # : 0 : static const auto testing_setup = MakeNoLogFileContext<>();
# # ]
41 : 0 : 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 [ # # ]: 0 : for (int i = 0; i < NUM_ITERS; ++i) {
46 : 0 : g_outpoints.emplace_back();
47 : 0 : g_outpoints.back().n = i;
48 : 0 : }
49 : :
50 : 0 : }
51 : :
52 [ + - ]: 1030 : FUZZ_TARGET(rbf, .init = initialize_rbf)
53 : : {
54 : 1028 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
55 : 1028 : SetMockTime(ConsumeTime(fuzzed_data_provider));
56 : 1028 : std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
57 [ + + ]: 1028 : if (!mtx) {
58 : 56 : return;
59 : : }
60 : :
61 : 972 : bilingual_str error;
62 [ + - + - ]: 972 : CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
63 [ + - + - ]: 972 : Assert(error.empty());
64 : :
65 [ - + + + : 326597 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
+ + ]
66 : : {
67 : 325625 : const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
68 [ + + ]: 325625 : if (!another_mtx) {
69 : 310 : break;
70 : : }
71 [ + - ]: 325315 : const CTransaction another_tx{*another_mtx};
72 [ - + + + : 325315 : if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {
+ + ]
73 [ + - + - ]: 8165 : mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
74 : 8165 : }
75 [ + - + - : 325315 : LOCK2(cs_main, pool.cs);
+ - + - ]
76 [ + - ]: 325315 : pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
77 [ + + ]: 325625 : }
78 [ + - ]: 972 : const CTransaction tx{*mtx};
79 [ + - + + ]: 972 : if (fuzzed_data_provider.ConsumeBool()) {
80 [ + - + - : 332 : LOCK2(cs_main, pool.cs);
+ - + - ]
81 [ + - ]: 332 : pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
82 : 332 : }
83 : : {
84 [ + - + - ]: 972 : LOCK(pool.cs);
85 [ + - ]: 972 : (void)IsRBFOptIn(tx, pool);
86 : 972 : }
87 [ - + ]: 1028 : }
88 : :
89 [ + - ]: 1640 : FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
90 : : {
91 : 1638 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
92 : 1638 : SetMockTime(ConsumeTime(fuzzed_data_provider));
93 : :
94 : : // "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values
95 : : // so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent.
96 : 1638 : CMutableTransaction child;
97 [ + - ]: 1638 : child.vin.resize(1);
98 : :
99 : 1638 : bilingual_str error;
100 [ + - + - ]: 1638 : CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
101 [ + - + - ]: 1638 : Assert(error.empty());
102 : :
103 : : // Add a bunch of parent-child pairs to the mempool, and remember them.
104 : 1638 : std::vector<CTransaction> mempool_txs;
105 : 1638 : size_t iter{0};
106 : :
107 [ + - ]: 1638 : int32_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000);
108 : :
109 : : // Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
110 : : // Add replacement_vsize since this is added to new diagram during RBF check
111 : 1638 : int64_t running_vsize_total{replacement_vsize};
112 : :
113 [ + - + - : 1638 : LOCK2(cs_main, pool.cs);
+ - + - ]
114 : :
115 [ + - + + : 544605 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
+ + ]
116 : : {
117 : : // Make sure txns only have one input, and that a unique input is given to avoid circular references
118 [ - + ]: 542967 : CMutableTransaction parent;
119 [ - + ]: 542967 : assert(iter <= g_outpoints.size());
120 [ + - ]: 542967 : parent.vin.resize(1);
121 : 542967 : parent.vin[0].prevout = g_outpoints[iter++];
122 [ + - ]: 542967 : parent.vout.emplace_back(0, CScript());
123 : :
124 [ + - ]: 542967 : mempool_txs.emplace_back(parent);
125 : 542967 : const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
126 [ + - ]: 542967 : running_vsize_total += parent_entry.GetTxSize();
127 [ + + ]: 542967 : if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
128 : : // We aren't adding this final tx to mempool, so we don't want to conflict with it
129 : 22 : mempool_txs.pop_back();
130 : 22 : break;
131 : : }
132 [ + - ]: 542945 : pool.addUnchecked(parent_entry);
133 [ + - + + ]: 542945 : if (fuzzed_data_provider.ConsumeBool()) {
134 [ + - + - ]: 527669 : child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
135 : 527669 : }
136 [ + - ]: 542945 : mempool_txs.emplace_back(child);
137 : 542945 : const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
138 [ + - ]: 542945 : running_vsize_total += child_entry.GetTxSize();
139 [ + + ]: 542945 : if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
140 : : // We aren't adding this final tx to mempool, so we don't want to conflict with it
141 : 30 : mempool_txs.pop_back();
142 : 30 : break;
143 : : }
144 [ + - ]: 542915 : pool.addUnchecked(child_entry);
145 : :
146 [ + - + + ]: 542915 : if (fuzzed_data_provider.ConsumeBool()) {
147 [ + - + - : 540826 : pool.PrioritiseTransaction(mempool_txs.back().GetHash().ToUint256(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
+ - + - ]
148 : 540826 : }
149 [ + + ]: 542967 : }
150 : :
151 : : // Pick some transactions at random to be the direct conflicts
152 : 1638 : CTxMemPool::setEntries direct_conflicts;
153 [ + + ]: 1087498 : for (auto& tx : mempool_txs) {
154 [ + - + + ]: 1085860 : if (fuzzed_data_provider.ConsumeBool()) {
155 [ + - + - : 723741 : direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
+ - + - ]
156 : 723741 : }
157 : 1085860 : }
158 : :
159 : : // Calculate all conflicts:
160 : 1638 : CTxMemPool::setEntries all_conflicts;
161 [ + + ]: 721311 : for (auto& txiter : direct_conflicts) {
162 [ + - ]: 719673 : pool.CalculateDescendants(txiter, all_conflicts);
163 : 719673 : }
164 : :
165 : : // Calculate the chunks for a replacement.
166 : 1638 : CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
167 [ + - ]: 1638 : auto calc_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
168 : :
169 [ + + ]: 1638 : if (calc_results.has_value()) {
170 : : // Sanity checks on the chunks.
171 : :
172 : : // Feerates are monotonically decreasing.
173 : 1184 : FeeFrac first_sum;
174 [ + - + + ]: 311230 : for (size_t i = 0; i < calc_results->first.size(); ++i) {
175 [ + - ]: 310046 : first_sum += calc_results->first[i];
176 [ + + + - : 310046 : if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
+ - - + ]
177 : 310046 : }
178 : 1184 : FeeFrac second_sum;
179 [ + - + + ]: 46047 : for (size_t i = 0; i < calc_results->second.size(); ++i) {
180 [ + - ]: 44863 : second_sum += calc_results->second[i];
181 [ + + + - : 44863 : if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
+ - + - ]
182 : 44863 : }
183 : :
184 : 1184 : FeeFrac replaced;
185 [ + + ]: 399734 : for (auto txiter : all_conflicts) {
186 [ + - + - ]: 398550 : replaced.fee += txiter->GetModifiedFee();
187 [ + - + - ]: 398550 : replaced.size += txiter->GetTxSize();
188 : 398550 : }
189 : : // The total fee & size of the new diagram minus replaced fee & size should be the total
190 : : // fee & size of the old diagram minus replacement fee & size.
191 [ + - ]: 1184 : assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
192 : 1184 : }
193 : :
194 : : // If internals report error, wrapper should too
195 [ + - ]: 1638 : auto err_tuple{ImprovesFeerateDiagram(pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)};
196 [ + + ]: 1638 : if (!calc_results.has_value()) {
197 [ + - + - ]: 454 : assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
198 : 454 : } else {
199 : : // Diagram check succeeded
200 [ + - + - : 1184 : auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
+ - ]
201 [ + - + - : 1184 : auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
+ - ]
202 [ + + ]: 1184 : if (!err_tuple.has_value()) {
203 : : // New diagram's final fee should always match or exceed old diagram's
204 [ + - ]: 478 : assert(old_sum.fee <= new_sum.fee);
205 [ + + ]: 1184 : } else if (old_sum.fee > new_sum.fee) {
206 : : // Or it failed, and if old diagram had higher fees, it should be a failure
207 [ + - + - ]: 398 : assert(err_tuple.value().first == DiagramCheckError::FAILURE);
208 : 398 : }
209 : 1184 : }
210 : 1638 : }
|