@@ -269,6 +269,24 @@ FUZZ_TARGET(txgraph)
269
269
sims.reserve (2 );
270
270
sims.emplace_back (max_count);
271
271
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
+
272
290
/* * Function to pick any Ref (for either sim in sims: from sim.simmap or sim.removed, or the
273
291
* empty Ref). */
274
292
auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
@@ -342,13 +360,20 @@ FUZZ_TARGET(txgraph)
342
360
LIMITED_WHILE (provider.remaining_bytes () > 0 , 200 ) {
343
361
// Read a one-byte command.
344
362
int command = provider.ConsumeIntegral <uint8_t >();
363
+ int orig_command = command;
364
+
345
365
// Treat the lowest bit of a command as a flag (which selects a variant of some of the
346
366
// operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
347
367
// the rest of the bits in command.
348
368
bool alt = command & 1 ;
349
369
bool use_main = command & 2 ;
350
370
command >>= 2 ;
351
371
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
+
352
377
// Provide convenient aliases for the top simulated graph (main, or staging if it exists),
353
378
// one for the simulated graph selected based on use_main (for operations that can operate
354
379
// on both graphs), and one that always refers to the main graph.
@@ -359,7 +384,7 @@ FUZZ_TARGET(txgraph)
359
384
// Keep decrementing command for each applicable operation, until one is hit. Multiple
360
385
// iterations may be necessary.
361
386
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 ) {
363
388
// AddTransaction.
364
389
int64_t fee;
365
390
int32_t size;
@@ -381,7 +406,7 @@ FUZZ_TARGET(txgraph)
381
406
// Move it in place.
382
407
*ref_loc = std::move (ref);
383
408
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 ) {
385
410
// AddDependency.
386
411
auto par = pick_fn ();
387
412
auto chl = pick_fn ();
@@ -395,7 +420,7 @@ FUZZ_TARGET(txgraph)
395
420
top_sim.AddDependency (par, chl);
396
421
real->AddDependency (*par, *chl);
397
422
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 ) {
399
424
// RemoveTransaction. Either all its ancestors or all its descendants are also
400
425
// removed (if any), to make sure TxGraph's reordering of removals and dependencies
401
426
// has no effect.
@@ -425,7 +450,7 @@ FUZZ_TARGET(txgraph)
425
450
}
426
451
sel_sim.removed .pop_back ();
427
452
break ;
428
- } else if (command-- == 0 ) {
453
+ } else if (block_builders. empty () && command-- == 0 ) {
429
454
// ~Ref (of any transaction).
430
455
std::vector<TxGraph::Ref*> to_destroy;
431
456
to_destroy.push_back (pick_fn ());
@@ -447,7 +472,7 @@ FUZZ_TARGET(txgraph)
447
472
}
448
473
}
449
474
break ;
450
- } else if (command-- == 0 ) {
475
+ } else if (block_builders. empty () && command-- == 0 ) {
451
476
// SetTransactionFee.
452
477
int64_t fee;
453
478
if (alt) {
@@ -578,7 +603,7 @@ FUZZ_TARGET(txgraph)
578
603
sims.back ().modified = SimTxGraph::SetType{};
579
604
real->StartStaging ();
580
605
break ;
581
- } else if (sims.size () > 1 && command-- == 0 ) {
606
+ } else if (block_builders. empty () && sims.size () > 1 && command-- == 0 ) {
582
607
// CommitStaging.
583
608
real->CommitStaging ();
584
609
sims.erase (sims.begin ());
@@ -664,6 +689,65 @@ FUZZ_TARGET(txgraph)
664
689
assert (FeeRateCompare (real_staged_diagram[i], real_staged_diagram[i - 1 ]) <= 0 );
665
690
}
666
691
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 ;
667
751
}
668
752
}
669
753
}
@@ -718,6 +802,28 @@ FUZZ_TARGET(txgraph)
718
802
}
719
803
}
720
804
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
+
721
827
// Check that the implied ordering gives rise to a combined diagram that matches the
722
828
// diagram constructed from the individual cluster linearization chunkings.
723
829
auto main_real_diagram = get_diagram_fn (/* main_only=*/ true );
@@ -848,6 +954,8 @@ FUZZ_TARGET(txgraph)
848
954
// Sanity check again (because invoking inspectors may modify internal unobservable state).
849
955
real->SanityCheck ();
850
956
957
+ // Kill the block builders.
958
+ block_builders.clear ();
851
959
// Kill the TxGraph object.
852
960
real.reset ();
853
961
// Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
0 commit comments