@@ -23,12 +23,30 @@ namespace {
23
23
const BasicTestingSetup* g_setup;
24
24
} // namespace
25
25
26
+ const int NUM_ITERS = 10000 ;
27
+
28
+ std::vector<COutPoint> g_outpoints;
29
+
26
30
void initialize_rbf ()
27
31
{
28
32
static const auto testing_setup = MakeNoLogFileContext<>();
29
33
g_setup = testing_setup.get ();
30
34
}
31
35
36
+ void initialize_package_rbf ()
37
+ {
38
+ static const auto testing_setup = MakeNoLogFileContext<>();
39
+ g_setup = testing_setup.get ();
40
+
41
+ // Create a fixed set of unique "UTXOs" to source parents from
42
+ // to avoid fuzzer giving circular references
43
+ for (int i = 0 ; i < NUM_ITERS; ++i) {
44
+ g_outpoints.emplace_back ();
45
+ g_outpoints.back ().n = i;
46
+ }
47
+
48
+ }
49
+
32
50
FUZZ_TARGET (rbf, .init = initialize_rbf)
33
51
{
34
52
FuzzedDataProvider fuzzed_data_provider (buffer.data (), buffer.size ());
@@ -40,7 +58,7 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
40
58
41
59
CTxMemPool pool{MemPoolOptionsForTest (g_setup->m_node )};
42
60
43
- LIMITED_WHILE (fuzzed_data_provider.ConsumeBool (), 10000 )
61
+ LIMITED_WHILE (fuzzed_data_provider.ConsumeBool (), NUM_ITERS )
44
62
{
45
63
const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
46
64
if (!another_mtx) {
@@ -63,3 +81,98 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
63
81
(void )IsRBFOptIn (tx, pool);
64
82
}
65
83
}
84
+
85
+ void CheckDiagramConcave (std::vector<FeeFrac>& diagram)
86
+ {
87
+ // Diagrams are in monotonically-decreasing feerate order.
88
+ FeeFrac last_chunk = diagram.front ();
89
+ for (size_t i = 1 ; i<diagram.size (); ++i) {
90
+ FeeFrac next_chunk = diagram[i] - diagram[i-1 ];
91
+ assert (next_chunk <= last_chunk);
92
+ last_chunk = next_chunk;
93
+ }
94
+ }
95
+
96
+ FUZZ_TARGET (package_rbf, .init = initialize_package_rbf)
97
+ {
98
+ FuzzedDataProvider fuzzed_data_provider (buffer.data (), buffer.size ());
99
+ SetMockTime (ConsumeTime (fuzzed_data_provider));
100
+
101
+ std::optional<CMutableTransaction> child = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
102
+ if (!child) return ;
103
+
104
+ CTxMemPool pool{MemPoolOptionsForTest (g_setup->m_node )};
105
+
106
+ // Add a bunch of parent-child pairs to the mempool, and remember them.
107
+ std::vector<CTransaction> mempool_txs;
108
+ size_t iter{0 };
109
+
110
+ LOCK2 (cs_main, pool.cs );
111
+
112
+ LIMITED_WHILE (fuzzed_data_provider.ConsumeBool (), NUM_ITERS)
113
+ {
114
+ // Make sure txns only have one input, and that a unique input is given to avoid circular references
115
+ std::optional<CMutableTransaction> parent = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
116
+ if (!parent) {
117
+ continue ;
118
+ }
119
+ assert (iter <= g_outpoints.size ());
120
+ parent->vin .resize (1 );
121
+ parent->vin [0 ].prevout = g_outpoints[iter++];
122
+
123
+ mempool_txs.emplace_back (*parent);
124
+ pool.addUnchecked (ConsumeTxMemPoolEntry (fuzzed_data_provider, mempool_txs.back ()));
125
+ if (fuzzed_data_provider.ConsumeBool () && !child->vin .empty ()) {
126
+ child->vin [0 ].prevout = COutPoint{mempool_txs.back ().GetHash (), 0 };
127
+ }
128
+ mempool_txs.emplace_back (*child);
129
+ pool.addUnchecked (ConsumeTxMemPoolEntry (fuzzed_data_provider, mempool_txs.back ()));
130
+ }
131
+
132
+ // Pick some transactions at random to be the direct conflicts
133
+ CTxMemPool::setEntries direct_conflicts;
134
+ for (auto & tx : mempool_txs) {
135
+ if (fuzzed_data_provider.ConsumeBool ()) {
136
+ direct_conflicts.insert (*pool.GetIter (tx.GetHash ()));
137
+ }
138
+ }
139
+
140
+ // Calculate all conflicts:
141
+ CTxMemPool::setEntries all_conflicts;
142
+ for (auto & txiter : direct_conflicts) {
143
+ pool.CalculateDescendants (txiter, all_conflicts);
144
+ }
145
+
146
+ // Calculate the feerate diagrams for a replacement.
147
+ CAmount replacement_fees = ConsumeMoney (fuzzed_data_provider);
148
+ int64_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange <int64_t >(1 , 1000000 );
149
+ auto calc_results{pool.CalculateFeerateDiagramsForRBF (replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
150
+
151
+ if (calc_results.has_value ()) {
152
+ // Sanity checks on the diagrams.
153
+
154
+ // Diagrams start at 0.
155
+ assert (calc_results->first .front ().size == 0 );
156
+ assert (calc_results->first .front ().fee == 0 );
157
+ assert (calc_results->second .front ().size == 0 );
158
+ assert (calc_results->second .front ().fee == 0 );
159
+
160
+ CheckDiagramConcave (calc_results->first );
161
+ CheckDiagramConcave (calc_results->second );
162
+
163
+ CAmount replaced_fee{0 };
164
+ int64_t replaced_size{0 };
165
+ for (auto txiter : all_conflicts) {
166
+ replaced_fee += txiter->GetModifiedFee ();
167
+ replaced_size += txiter->GetTxSize ();
168
+ }
169
+ // The total fee of the new diagram should be the total fee of the old
170
+ // diagram - replaced_fee + replacement_fees
171
+ assert (calc_results->first .back ().fee - replaced_fee + replacement_fees == calc_results->second .back ().fee );
172
+ assert (calc_results->first .back ().size - replaced_size + replacement_vsize == calc_results->second .back ().size );
173
+ }
174
+
175
+ // If internals report error, wrapper should too
176
+ auto err_tuple{ImprovesFeerateDiagram (pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)};
177
+ if (!calc_results.has_value ()) assert (err_tuple.has_value ());
178
+ }
0 commit comments