@@ -23,12 +23,30 @@ namespace {
2323const BasicTestingSetup* g_setup;
2424} // namespace
2525
26+ const int NUM_ITERS = 10000 ;
27+
28+ std::vector<COutPoint> g_outpoints;
29+
2630void initialize_rbf ()
2731{
2832 static const auto testing_setup = MakeNoLogFileContext<>();
2933 g_setup = testing_setup.get ();
3034}
3135
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+
3250FUZZ_TARGET (rbf, .init = initialize_rbf)
3351{
3452 FuzzedDataProvider fuzzed_data_provider (buffer.data (), buffer.size ());
@@ -40,7 +58,7 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
4058
4159 CTxMemPool pool{MemPoolOptionsForTest (g_setup->m_node )};
4260
43- LIMITED_WHILE (fuzzed_data_provider.ConsumeBool (), 10000 )
61+ LIMITED_WHILE (fuzzed_data_provider.ConsumeBool (), NUM_ITERS )
4462 {
4563 const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
4664 if (!another_mtx) {
@@ -63,3 +81,98 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
6381 (void )IsRBFOptIn (tx, pool);
6482 }
6583}
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