@@ -241,103 +241,157 @@ std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanRe
241
241
242
242
} // namespace
243
243
244
- FUZZ_TARGET (clusterlin_add_dependencies )
244
+ FUZZ_TARGET (clusterlin_depgraph_sim )
245
245
{
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.
248
247
249
248
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);
271
280
}
272
- parents_mask_shifted >>= 1 ;
281
+ mask_shifted >>= 1 ;
273
282
}
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
+ }
280
296
}
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
+ };
304
299
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
+ };
310
316
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
+ };
312
329
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 ;
323
384
}
385
+ // This should be unreachable (one of the 3 above actions should always be possible).
386
+ assert (false );
324
387
}
325
388
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);
341
395
}
342
396
343
397
FUZZ_TARGET (clusterlin_depgraph_serialization)
0 commit comments