@@ -484,6 +484,123 @@ FUZZ_TARGET(clusterlin_search_finder)
484
484
assert (anc_finder.AllDone ());
485
485
}
486
486
487
+ FUZZ_TARGET (clusterlin_linearization_chunking)
488
+ {
489
+ // Verify the behavior of LinearizationChunking.
490
+
491
+ // Retrieve a depgraph from the fuzz input.
492
+ SpanReader reader (buffer);
493
+ DepGraph<TestBitSet> depgraph;
494
+ try {
495
+ reader >> Using<DepGraphFormatter>(depgraph);
496
+ } catch (const std::ios_base::failure&) {}
497
+
498
+ // Retrieve a topologically-valid subset of depgraph.
499
+ auto todo = TestBitSet::Fill (depgraph.TxCount ());
500
+ auto subset = SetInfo (depgraph, ReadTopologicalSet (depgraph, todo, reader));
501
+
502
+ // Retrieve a valid linearization for depgraph.
503
+ auto linearization = ReadLinearization (depgraph, reader);
504
+
505
+ // Construct a LinearizationChunking object, initially for the whole linearization.
506
+ LinearizationChunking chunking (depgraph, linearization);
507
+
508
+ // Incrementally remove transactions from the chunking object, and check various properties at
509
+ // every step.
510
+ while (todo.Any ()) {
511
+ assert (chunking.NumChunksLeft () > 0 );
512
+
513
+ // Construct linearization with just todo.
514
+ std::vector<ClusterIndex> linearization_left;
515
+ for (auto i : linearization) {
516
+ if (todo[i]) linearization_left.push_back (i);
517
+ }
518
+
519
+ // Compute the chunking for linearization_left.
520
+ auto chunking_left = ChunkLinearization (depgraph, linearization_left);
521
+
522
+ // Verify that it matches the feerates of the chunks of chunking.
523
+ assert (chunking.NumChunksLeft () == chunking_left.size ());
524
+ for (ClusterIndex i = 0 ; i < chunking.NumChunksLeft (); ++i) {
525
+ assert (chunking.GetChunk (i).feerate == chunking_left[i]);
526
+ }
527
+
528
+ // Check consistency of chunking.
529
+ TestBitSet combined;
530
+ for (ClusterIndex i = 0 ; i < chunking.NumChunksLeft (); ++i) {
531
+ const auto & chunk_info = chunking.GetChunk (i);
532
+ // Chunks must be non-empty.
533
+ assert (chunk_info.transactions .Any ());
534
+ // Chunk feerates must be monotonically non-increasing.
535
+ if (i > 0 ) assert (!(chunk_info.feerate >> chunking.GetChunk (i - 1 ).feerate ));
536
+ // Chunks must be a subset of what is left of the linearization.
537
+ assert (chunk_info.transactions .IsSubsetOf (todo));
538
+ // Chunks' claimed feerates must match their transactions' aggregate feerate.
539
+ assert (depgraph.FeeRate (chunk_info.transactions ) == chunk_info.feerate );
540
+ // Chunks must be the highest-feerate remaining prefix.
541
+ SetInfo<TestBitSet> accumulator, best;
542
+ for (auto j : linearization) {
543
+ if (todo[j] && !combined[j]) {
544
+ accumulator |= SetInfo (depgraph, j);
545
+ if (best.feerate .IsEmpty () || accumulator.feerate > best.feerate ) {
546
+ best = accumulator;
547
+ }
548
+ }
549
+ }
550
+ assert (best.transactions == chunk_info.transactions );
551
+ assert (best.feerate == chunk_info.feerate );
552
+ // Chunks cannot overlap.
553
+ assert (!chunk_info.transactions .Overlaps (combined));
554
+ combined |= chunk_info.transactions ;
555
+ // Chunks must be topological.
556
+ for (auto idx : chunk_info.transactions ) {
557
+ assert ((depgraph.Ancestors (idx) & todo).IsSubsetOf (combined));
558
+ }
559
+ }
560
+ assert (combined == todo);
561
+
562
+ // Verify the expected properties of LinearizationChunking::Intersect:
563
+ auto intersect = chunking.Intersect (subset);
564
+ // - Intersecting again doesn't change the result.
565
+ assert (chunking.Intersect (intersect) == intersect);
566
+ // - The intersection is topological.
567
+ TestBitSet intersect_anc;
568
+ for (auto idx : intersect.transactions ) {
569
+ intersect_anc |= (depgraph.Ancestors (idx) & todo);
570
+ }
571
+ assert (intersect.transactions == intersect_anc);
572
+ // - The claimed intersection feerate matches its transactions.
573
+ assert (intersect.feerate == depgraph.FeeRate (intersect.transactions ));
574
+ // - The intersection may only be empty if its input is empty.
575
+ assert (intersect.transactions .Any () == subset.transactions .Any ());
576
+ // - The intersection feerate must be as high as the input.
577
+ assert (intersect.feerate >= subset.feerate );
578
+ // - No non-empty intersection between the intersection and a prefix of the chunks of the
579
+ // remainder of the linearization may be better than the intersection.
580
+ TestBitSet prefix;
581
+ for (ClusterIndex i = 0 ; i < chunking.NumChunksLeft (); ++i) {
582
+ prefix |= chunking.GetChunk (i).transactions ;
583
+ auto reintersect = SetInfo (depgraph, prefix & intersect.transactions );
584
+ if (!reintersect.feerate .IsEmpty ()) {
585
+ assert (reintersect.feerate <= intersect.feerate );
586
+ }
587
+ }
588
+
589
+ // Find a subset to remove from linearization.
590
+ auto done = ReadTopologicalSet (depgraph, todo, reader);
591
+ if (done.None ()) {
592
+ // We need to remove a non-empty subset, so fall back to the unlinearized ancestors of
593
+ // the first transaction in todo if done is empty.
594
+ done = depgraph.Ancestors (todo.First ()) & todo;
595
+ }
596
+ todo -= done;
597
+ chunking.MarkDone (done);
598
+ subset = SetInfo (depgraph, subset.transactions - done);
599
+ }
600
+
601
+ assert (chunking.NumChunksLeft () == 0 );
602
+ }
603
+
487
604
FUZZ_TARGET (clusterlin_linearize)
488
605
{
489
606
// Verify the behavior of Linearize().
0 commit comments