@@ -269,6 +269,24 @@ FUZZ_TARGET(txgraph)
269269 sims.reserve (2 );
270270 sims.emplace_back (max_count);
271271
272+ /* * Struct encapsulating information about a BlockBuilder that's currently live. */
273+ struct BlockBuilderData
274+ {
275+ /* * BlockBuilder object from real. */
276+ std::unique_ptr<TxGraph::BlockBuilder> builder;
277+ /* * The set of transactions marked as included in *builder. */
278+ SimTxGraph::SetType included;
279+ /* * The set of transactions marked as included or skipped in *builder. */
280+ SimTxGraph::SetType done;
281+ /* * The last chunk feerate returned by *builder. IsEmpty() if none yet. */
282+ FeePerWeight last_feerate;
283+
284+ BlockBuilderData (std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
285+ };
286+
287+ /* * Currently active block builders. */
288+ std::vector<BlockBuilderData> block_builders;
289+
272290 /* * Function to pick any Ref (for either sim in sims: from sim.simmap or sim.removed, or the
273291 * empty Ref). */
274292 auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
@@ -342,13 +360,20 @@ FUZZ_TARGET(txgraph)
342360 LIMITED_WHILE (provider.remaining_bytes () > 0 , 200 ) {
343361 // Read a one-byte command.
344362 int command = provider.ConsumeIntegral <uint8_t >();
363+ int orig_command = command;
364+
345365 // Treat the lowest bit of a command as a flag (which selects a variant of some of the
346366 // operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
347367 // the rest of the bits in command.
348368 bool alt = command & 1 ;
349369 bool use_main = command & 2 ;
350370 command >>= 2 ;
351371
372+ /* * Use the bottom 2 bits of command to select an entry in the block_builders vector (if
373+ * any). These use the same bits as alt/use_main, so don't use those in actions below
374+ * where builder_idx is used as well. */
375+ int builder_idx = block_builders.empty () ? -1 : int ((orig_command & 3 ) % block_builders.size ());
376+
352377 // Provide convenient aliases for the top simulated graph (main, or staging if it exists),
353378 // one for the simulated graph selected based on use_main (for operations that can operate
354379 // on both graphs), and one that always refers to the main graph.
@@ -359,7 +384,7 @@ FUZZ_TARGET(txgraph)
359384 // Keep decrementing command for each applicable operation, until one is hit. Multiple
360385 // iterations may be necessary.
361386 while (true ) {
362- if (top_sim.GetTransactionCount () < SimTxGraph::MAX_TRANSACTIONS && command-- == 0 ) {
387+ if ((block_builders. empty () || sims. size () > 1 ) && top_sim.GetTransactionCount () < SimTxGraph::MAX_TRANSACTIONS && command-- == 0 ) {
363388 // AddTransaction.
364389 int64_t fee;
365390 int32_t size;
@@ -381,7 +406,7 @@ FUZZ_TARGET(txgraph)
381406 // Move it in place.
382407 *ref_loc = std::move (ref);
383408 break ;
384- } else if (top_sim.GetTransactionCount () + top_sim.removed .size () > 1 && command-- == 0 ) {
409+ } else if ((block_builders. empty () || sims. size () > 1 ) && top_sim.GetTransactionCount () + top_sim.removed .size () > 1 && command-- == 0 ) {
385410 // AddDependency.
386411 auto par = pick_fn ();
387412 auto chl = pick_fn ();
@@ -395,7 +420,7 @@ FUZZ_TARGET(txgraph)
395420 top_sim.AddDependency (par, chl);
396421 real->AddDependency (*par, *chl);
397422 break ;
398- } else if (top_sim.removed .size () < 100 && command-- == 0 ) {
423+ } else if ((block_builders. empty () || sims. size () > 1 ) && top_sim.removed .size () < 100 && command-- == 0 ) {
399424 // RemoveTransaction. Either all its ancestors or all its descendants are also
400425 // removed (if any), to make sure TxGraph's reordering of removals and dependencies
401426 // has no effect.
@@ -425,7 +450,7 @@ FUZZ_TARGET(txgraph)
425450 }
426451 sel_sim.removed .pop_back ();
427452 break ;
428- } else if (command-- == 0 ) {
453+ } else if (block_builders. empty () && command-- == 0 ) {
429454 // ~Ref (of any transaction).
430455 std::vector<TxGraph::Ref*> to_destroy;
431456 to_destroy.push_back (pick_fn ());
@@ -447,7 +472,7 @@ FUZZ_TARGET(txgraph)
447472 }
448473 }
449474 break ;
450- } else if (command-- == 0 ) {
475+ } else if (block_builders. empty () && command-- == 0 ) {
451476 // SetTransactionFee.
452477 int64_t fee;
453478 if (alt) {
@@ -578,7 +603,7 @@ FUZZ_TARGET(txgraph)
578603 sims.back ().modified = SimTxGraph::SetType{};
579604 real->StartStaging ();
580605 break ;
581- } else if (sims.size () > 1 && command-- == 0 ) {
606+ } else if (block_builders. empty () && sims.size () > 1 && command-- == 0 ) {
582607 // CommitStaging.
583608 real->CommitStaging ();
584609 sims.erase (sims.begin ());
@@ -664,6 +689,65 @@ FUZZ_TARGET(txgraph)
664689 assert (FeeRateCompare (real_staged_diagram[i], real_staged_diagram[i - 1 ]) <= 0 );
665690 }
666691 break ;
692+ } else if (block_builders.size () < 4 && !main_sim.IsOversized () && command-- == 0 ) {
693+ // GetBlockBuilder.
694+ block_builders.emplace_back (real->GetBlockBuilder ());
695+ break ;
696+ } else if (!block_builders.empty () && command-- == 0 ) {
697+ // ~BlockBuilder.
698+ block_builders.erase (block_builders.begin () + builder_idx);
699+ break ;
700+ } else if (!block_builders.empty () && command-- == 0 ) {
701+ // BlockBuilder::GetCurrentChunk, followed by Include/Skip.
702+ auto & builder_data = block_builders[builder_idx];
703+ auto new_included = builder_data.included ;
704+ auto new_done = builder_data.done ;
705+ auto chunk = builder_data.builder ->GetCurrentChunk ();
706+ if (chunk) {
707+ // Chunk feerates must be monotonously decreasing.
708+ if (!builder_data.last_feerate .IsEmpty ()) {
709+ assert (!(chunk->second >> builder_data.last_feerate ));
710+ }
711+ builder_data.last_feerate = chunk->second ;
712+ // Verify the contents of GetCurrentChunk.
713+ FeePerWeight sum_feerate;
714+ for (TxGraph::Ref* ref : chunk->first ) {
715+ // Each transaction in the chunk must exist in the main graph.
716+ auto simpos = main_sim.Find (ref);
717+ assert (simpos != SimTxGraph::MISSING);
718+ // Verify the claimed chunk feerate.
719+ sum_feerate += main_sim.graph .FeeRate (simpos);
720+ // Make sure no transaction is reported twice.
721+ assert (!new_done[simpos]);
722+ new_done.Set (simpos);
723+ // The concatenation of all included transactions must be topologically valid.
724+ new_included.Set (simpos);
725+ assert (main_sim.graph .Ancestors (simpos).IsSubsetOf (new_included));
726+ }
727+ assert (sum_feerate == chunk->second );
728+ } else {
729+ // When we reach the end, if nothing was skipped, the entire graph should have
730+ // been reported.
731+ if (builder_data.done == builder_data.included ) {
732+ assert (builder_data.done .Count () == main_sim.GetTransactionCount ());
733+ }
734+ }
735+ // Possibly invoke GetCurrentChunk() again, which should give the same result.
736+ if ((orig_command % 7 ) >= 5 ) {
737+ auto chunk2 = builder_data.builder ->GetCurrentChunk ();
738+ assert (chunk == chunk2);
739+ }
740+ // Skip or include.
741+ if ((orig_command % 5 ) >= 3 ) {
742+ // Skip.
743+ builder_data.builder ->Skip ();
744+ } else {
745+ // Include.
746+ builder_data.builder ->Include ();
747+ builder_data.included = new_included;
748+ }
749+ builder_data.done = new_done;
750+ break ;
667751 }
668752 }
669753 }
@@ -718,6 +802,28 @@ FUZZ_TARGET(txgraph)
718802 }
719803 }
720804
805+ // The same order should be obtained through a BlockBuilder as implied by CompareMainOrder,
806+ // if nothing is skipped.
807+ auto builder = real->GetBlockBuilder ();
808+ std::vector<SimTxGraph::Pos> vec_builder;
809+ while (auto chunk = builder->GetCurrentChunk ()) {
810+ FeePerWeight sum;
811+ for (TxGraph::Ref* ref : chunk->first ) {
812+ // The reported chunk feerate must match the chunk feerate obtained by asking
813+ // it for each of the chunk's transactions individually.
814+ assert (real->GetMainChunkFeerate (*ref) == chunk->second );
815+ // Verify the chunk feerate matches the sum of the reported individual feerates.
816+ sum += real->GetIndividualFeerate (*ref);
817+ // Chunks must contain transactions that exist in the graph.
818+ auto simpos = sims[0 ].Find (ref);
819+ assert (simpos != SimTxGraph::MISSING);
820+ vec_builder.push_back (simpos);
821+ }
822+ assert (sum == chunk->second );
823+ builder->Include ();
824+ }
825+ assert (vec_builder == vec1);
826+
721827 // Check that the implied ordering gives rise to a combined diagram that matches the
722828 // diagram constructed from the individual cluster linearization chunkings.
723829 auto main_real_diagram = get_diagram_fn (/* main_only=*/ true );
@@ -848,6 +954,8 @@ FUZZ_TARGET(txgraph)
848954 // Sanity check again (because invoking inspectors may modify internal unobservable state).
849955 real->SanityCheck ();
850956
957+ // Kill the block builders.
958+ block_builders.clear ();
851959 // Kill the TxGraph object.
852960 real.reset ();
853961 // Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
0 commit comments