@@ -241,103 +241,157 @@ std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanRe
241241
242242} // namespace
243243
244- FUZZ_TARGET (clusterlin_add_dependencies )
244+ FUZZ_TARGET (clusterlin_depgraph_sim )
245245{
246- // Verify that computing a DepGraph from a cluster, or building it step by step using
247- // AddDependencies has the same effect.
246+ // Simulation test to verify the full behavior of DepGraph.
248247
249248 FuzzedDataProvider provider (buffer.data (), buffer.size ());
250- auto rng_seed = provider.ConsumeIntegral <uint64_t >();
251- InsecureRandomContext rng (rng_seed);
252-
253- // Construct a cluster of a certain length, with no dependencies.
254- auto num_tx = provider.ConsumeIntegralInRange <ClusterIndex>(2 , TestBitSet::Size ());
255- Cluster<TestBitSet> cluster (num_tx, std::pair{FeeFrac{0 , 1 }, TestBitSet{}});
256- // Construct the corresponding DepGraph object (also no dependencies).
257- DepGraph depgraph_batch (cluster);
258- SanityCheck (depgraph_batch);
259- // Read (parents, child) pairs, and add the dependencies to the cluster and depgraph.
260- std::vector<std::pair<ClusterIndex, ClusterIndex>> deps_list;
261- LIMITED_WHILE (provider.remaining_bytes () > 0 , TestBitSet::Size ()) {
262- const auto parents_mask = provider.ConsumeIntegralInRange <uint64_t >(0 , (uint64_t {1 } << num_tx) - 1 );
263- auto child = provider.ConsumeIntegralInRange <ClusterIndex>(0 , num_tx - 1 );
264-
265- auto parents_mask_shifted = parents_mask;
266- TestBitSet deps;
267- for (ClusterIndex i = 0 ; i < num_tx; ++i) {
268- if (parents_mask_shifted & 1 ) {
269- deps.Set (i);
270- cluster[child].second .Set (i);
249+
250+ /* * Real DepGraph being tested. */
251+ DepGraph<TestBitSet> real;
252+ /* * Simulated DepGraph (sim[i] is std::nullopt if position i does not exist; otherwise,
253+ * sim[i]->first is its individual feerate, and sim[i]->second is its set of ancestors. */
254+ std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size ()> sim;
255+ /* * The number of non-nullopt position in sim. */
256+ ClusterIndex num_tx_sim{0 };
257+
258+ /* * Read a valid index of a transaction from the provider. */
259+ auto idx_fn = [&]() {
260+ auto offset = provider.ConsumeIntegralInRange <ClusterIndex>(0 , num_tx_sim - 1 );
261+ for (ClusterIndex i = 0 ; i < sim.size (); ++i) {
262+ if (!sim[i].has_value ()) continue ;
263+ if (offset == 0 ) return i;
264+ --offset;
265+ }
266+ assert (false );
267+ return ClusterIndex (-1 );
268+ };
269+
270+ /* * Read a valid subset of the transactions from the provider. */
271+ auto subset_fn = [&]() {
272+ auto range = (uint64_t {1 } << num_tx_sim) - 1 ;
273+ const auto mask = provider.ConsumeIntegralInRange <uint64_t >(0 , range);
274+ auto mask_shifted = mask;
275+ TestBitSet subset;
276+ for (ClusterIndex i = 0 ; i < sim.size (); ++i) {
277+ if (!sim[i].has_value ()) continue ;
278+ if (mask_shifted & 1 ) {
279+ subset.Set (i);
271280 }
272- parents_mask_shifted >>= 1 ;
281+ mask_shifted >>= 1 ;
273282 }
274- assert (parents_mask_shifted == 0 );
275- depgraph_batch.AddDependencies (deps, child);
276- for (auto i : deps) {
277- deps_list.emplace_back (i, child);
278- assert (depgraph_batch.Ancestors (child)[i]);
279- assert (depgraph_batch.Descendants (i)[child]);
283+ assert (mask_shifted == 0 );
284+ return subset;
285+ };
286+
287+ /* * Read any set of transactions from the provider (including unused positions). */
288+ auto set_fn = [&]() {
289+ auto range = (uint64_t {1 } << sim.size ()) - 1 ;
290+ const auto mask = provider.ConsumeIntegralInRange <uint64_t >(0 , range);
291+ TestBitSet set;
292+ for (ClusterIndex i = 0 ; i < sim.size (); ++i) {
293+ if ((mask >> i) & 1 ) {
294+ set.Set (i);
295+ }
280296 }
281- }
282- // Sanity check the result.
283- SanityCheck (depgraph_batch);
284- // Verify that the resulting DepGraph matches one recomputed from the cluster.
285- assert (DepGraph (cluster) == depgraph_batch);
286-
287- DepGraph<TestBitSet> depgraph_individual;
288- // Add all transactions to depgraph_individual.
289- for (const auto & [feerate, parents] : cluster) {
290- depgraph_individual.AddTransaction (feerate);
291- }
292- SanityCheck (depgraph_individual);
293- // Add all individual dependencies to depgraph_individual in randomized order.
294- std::shuffle (deps_list.begin (), deps_list.end (), rng);
295- for (auto [parent, child] : deps_list) {
296- depgraph_individual.AddDependencies (TestBitSet::Singleton (parent), child);
297- assert (depgraph_individual.Ancestors (child)[parent]);
298- assert (depgraph_individual.Descendants (parent)[child]);
299- }
300- // Sanity check and compare again the batched version.
301- SanityCheck (depgraph_individual);
302- assert (depgraph_individual == depgraph_batch);
303- }
297+ return set;
298+ };
304299
305- FUZZ_TARGET (clusterlin_cluster_serialization)
306- {
307- // Verify that any graph of transactions has its ancestry correctly computed by DepGraph, and
308- // if it is a DAG, that it can be serialized as a DepGraph in a way that roundtrips. This
309- // guarantees that any acyclic cluster has a corresponding DepGraph serialization.
300+ /* * Propagate ancestor information in sim. */
301+ auto anc_update_fn = [&]() {
302+ while (true ) {
303+ bool updates{false };
304+ for (ClusterIndex chl = 0 ; chl < sim.size (); ++chl) {
305+ if (!sim[chl].has_value ()) continue ;
306+ for (auto par : sim[chl]->second ) {
307+ if (!sim[chl]->second .IsSupersetOf (sim[par]->second )) {
308+ sim[chl]->second |= sim[par]->second ;
309+ updates = true ;
310+ }
311+ }
312+ }
313+ if (!updates) break ;
314+ }
315+ };
310316
311- FuzzedDataProvider provider (buffer.data (), buffer.size ());
317+ /* * Compare the state of transaction i in the simulation with the real one. */
318+ auto check_fn = [&](ClusterIndex i) {
319+ // Compare used positions.
320+ assert (real.Positions ()[i] == sim[i].has_value ());
321+ if (sim[i].has_value ()) {
322+ // Compare feerate.
323+ assert (real.FeeRate (i) == sim[i]->first );
324+ // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
325+ // and descendants, so we can restrict ourselves to ancestors here).
326+ assert (real.Ancestors (i) == sim[i]->second );
327+ }
328+ };
312329
313- // Construct a cluster in a naive way (using a FuzzedDataProvider-based serialization).
314- Cluster<TestBitSet> cluster;
315- auto num_tx = provider.ConsumeIntegralInRange <ClusterIndex>(1 , 32 );
316- cluster.resize (num_tx);
317- for (ClusterIndex i = 0 ; i < num_tx; ++i) {
318- cluster[i].first .size = provider.ConsumeIntegralInRange <int32_t >(1 , 0x3fffff );
319- cluster[i].first .fee = provider.ConsumeIntegralInRange <int64_t >(-0x8000000000000 , 0x7ffffffffffff );
320- for (ClusterIndex j = 0 ; j < num_tx; ++j) {
321- if (i == j) continue ;
322- if (provider.ConsumeBool ()) cluster[i].second .Set (j);
330+ LIMITED_WHILE (provider.remaining_bytes () > 0 , 1000 ) {
331+ uint8_t command = provider.ConsumeIntegral <uint8_t >();
332+ if (num_tx_sim == 0 || ((command % 3 ) <= 0 && num_tx_sim < TestBitSet::Size ())) {
333+ // AddTransaction.
334+ auto fee = provider.ConsumeIntegralInRange <int64_t >(-0x8000000000000 , 0x7ffffffffffff );
335+ auto size = provider.ConsumeIntegralInRange <int32_t >(1 , 0x3fffff );
336+ FeeFrac feerate{fee, size};
337+ // Apply to DepGraph.
338+ auto idx = real.AddTransaction (feerate);
339+ // Verify that the returned index is correct.
340+ assert (!sim[idx].has_value ());
341+ for (ClusterIndex i = 0 ; i < TestBitSet::Size (); ++i) {
342+ if (!sim[i].has_value ()) {
343+ assert (idx == i);
344+ break ;
345+ }
346+ }
347+ // Update sim.
348+ sim[idx] = {feerate, TestBitSet::Singleton (idx)};
349+ ++num_tx_sim;
350+ continue ;
351+ }
352+ if ((command % 3 ) <= 1 && num_tx_sim > 0 ) {
353+ // AddDependencies.
354+ ClusterIndex child = idx_fn ();
355+ auto parents = subset_fn ();
356+ // Apply to DepGraph.
357+ real.AddDependencies (parents, child);
358+ // Apply to sim.
359+ sim[child]->second |= parents;
360+ continue ;
361+ }
362+ if (num_tx_sim > 0 ) {
363+ // Remove transactions.
364+ auto del = set_fn ();
365+ // Propagate all ancestry information before deleting anything in the simulation (as
366+ // intermediary transactions may be deleted which impact connectivity).
367+ anc_update_fn ();
368+ // Compare the state of the transactions being deleted.
369+ for (auto i : del) check_fn (i);
370+ // Apply to DepGraph.
371+ real.RemoveTransactions (del);
372+ // Apply to sim.
373+ for (ClusterIndex i = 0 ; i < sim.size (); ++i) {
374+ if (sim[i].has_value ()) {
375+ if (del[i]) {
376+ --num_tx_sim;
377+ sim[i] = std::nullopt ;
378+ } else {
379+ sim[i]->second -= del;
380+ }
381+ }
382+ }
383+ continue ;
323384 }
385+ // This should be unreachable (one of the 3 above actions should always be possible).
386+ assert (false );
324387 }
325388
326- // Construct dependency graph, and verify it matches the cluster (which includes a round-trip
327- // check for the serialization).
328- DepGraph depgraph (cluster);
329- VerifyDepGraphFromCluster (cluster, depgraph);
330-
331- // Remove an arbitrary subset (in order to construct a graph with holes) and verify that it
332- // still sanity checks (incl. round-tripping serialization).
333- uint64_t del = provider.ConsumeIntegralInRange <uint64_t >(1 , (uint64_t {1 } << TestBitSet::Size ()) - 1 );
334- TestBitSet setdel;
335- for (ClusterIndex i = 0 ; i < TestBitSet::Size (); ++i) {
336- if (del & 1 ) setdel.Set (i);
337- del >>= 1 ;
338- }
339- depgraph.RemoveTransactions (setdel);
340- SanityCheck (depgraph);
389+ // Compare the real obtained depgraph against the simulation.
390+ anc_update_fn ();
391+ for (ClusterIndex i = 0 ; i < sim.size (); ++i) check_fn (i);
392+ assert (real.TxCount () == num_tx_sim);
393+ // Sanity check the result (which includes round-tripping serialization, if applicable).
394+ SanityCheck (real);
341395}
342396
343397FUZZ_TARGET (clusterlin_depgraph_serialization)
0 commit comments