@@ -484,6 +484,123 @@ FUZZ_TARGET(clusterlin_search_finder)
484484 assert (anc_finder.AllDone ());
485485}
486486
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+
487604FUZZ_TARGET (clusterlin_linearize)
488605{
489606 // Verify the behavior of Linearize().
0 commit comments