github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/builder/collection/builder_test.go (about)

     1  package collection_test
     2  
     3  import (
     4  	"context"
     5  	"math/rand"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/dgraph-io/badger/v2"
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	model "github.com/onflow/flow-go/model/cluster"
    16  	"github.com/onflow/flow-go/model/flow"
    17  	builder "github.com/onflow/flow-go/module/builder/collection"
    18  	"github.com/onflow/flow-go/module/mempool"
    19  	"github.com/onflow/flow-go/module/mempool/herocache"
    20  	"github.com/onflow/flow-go/module/metrics"
    21  	"github.com/onflow/flow-go/module/trace"
    22  	"github.com/onflow/flow-go/state/cluster"
    23  	clusterkv "github.com/onflow/flow-go/state/cluster/badger"
    24  	"github.com/onflow/flow-go/state/protocol"
    25  	pbadger "github.com/onflow/flow-go/state/protocol/badger"
    26  	"github.com/onflow/flow-go/state/protocol/events"
    27  	"github.com/onflow/flow-go/state/protocol/inmem"
    28  	"github.com/onflow/flow-go/state/protocol/protocol_state/kvstore"
    29  	"github.com/onflow/flow-go/state/protocol/util"
    30  	"github.com/onflow/flow-go/storage"
    31  	bstorage "github.com/onflow/flow-go/storage/badger"
    32  	"github.com/onflow/flow-go/storage/badger/operation"
    33  	"github.com/onflow/flow-go/storage/badger/procedure"
    34  	sutil "github.com/onflow/flow-go/storage/util"
    35  	"github.com/onflow/flow-go/utils/unittest"
    36  )
    37  
    38  var noopSetter = func(*flow.Header) error { return nil }
    39  var noopSigner = func(*flow.Header) error { return nil }
    40  
    41  type BuilderSuite struct {
    42  	suite.Suite
    43  	db    *badger.DB
    44  	dbdir string
    45  
    46  	genesis      *model.Block
    47  	chainID      flow.ChainID
    48  	epochCounter uint64
    49  
    50  	headers  storage.Headers
    51  	payloads storage.ClusterPayloads
    52  	blocks   storage.Blocks
    53  
    54  	state cluster.MutableState
    55  
    56  	// protocol state for reference blocks for transactions
    57  	protoState protocol.FollowerState
    58  
    59  	pool    mempool.Transactions
    60  	builder *builder.Builder
    61  }
    62  
    63  // runs before each test runs
    64  func (suite *BuilderSuite) SetupTest() {
    65  	var err error
    66  
    67  	suite.genesis = model.Genesis()
    68  	suite.chainID = suite.genesis.Header.ChainID
    69  
    70  	suite.pool = herocache.NewTransactions(1000, unittest.Logger(), metrics.NewNoopCollector())
    71  
    72  	suite.dbdir = unittest.TempDir(suite.T())
    73  	suite.db = unittest.BadgerDB(suite.T(), suite.dbdir)
    74  
    75  	metrics := metrics.NewNoopCollector()
    76  	tracer := trace.NewNoopTracer()
    77  	log := zerolog.Nop()
    78  	all := sutil.StorageLayer(suite.T(), suite.db)
    79  	consumer := events.NewNoop()
    80  
    81  	suite.headers = all.Headers
    82  	suite.blocks = all.Blocks
    83  	suite.payloads = bstorage.NewClusterPayloads(metrics, suite.db)
    84  
    85  	// just bootstrap with a genesis block, we'll use this as reference
    86  	root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles()))
    87  	// ensure we don't enter a new epoch for tests that build many blocks
    88  	result.ServiceEvents[0].Event.(*flow.EpochSetup).FinalView = root.Header.View + 100000
    89  	seal.ResultID = result.ID()
    90  	root.Payload.ProtocolStateID = kvstore.NewDefaultKVStore(inmem.ProtocolStateFromEpochServiceEvents(
    91  		result.ServiceEvents[0].Event.(*flow.EpochSetup),
    92  		result.ServiceEvents[1].Event.(*flow.EpochCommit),
    93  	).ID()).ID()
    94  	rootSnapshot, err := inmem.SnapshotFromBootstrapState(root, result, seal, unittest.QuorumCertificateFixture(unittest.QCWithRootBlockID(root.ID())))
    95  	require.NoError(suite.T(), err)
    96  	suite.epochCounter = rootSnapshot.Encodable().SealingSegment.LatestProtocolStateEntry().EpochEntry.EpochCounter()
    97  
    98  	clusterQC := unittest.QuorumCertificateFixture(unittest.QCWithRootBlockID(suite.genesis.ID()))
    99  	root.Payload.ProtocolStateID = kvstore.NewDefaultKVStore(inmem.ProtocolStateFromEpochServiceEvents(
   100  		result.ServiceEvents[0].Event.(*flow.EpochSetup),
   101  		result.ServiceEvents[1].Event.(*flow.EpochCommit),
   102  	).ID()).ID()
   103  	clusterStateRoot, err := clusterkv.NewStateRoot(suite.genesis, clusterQC, suite.epochCounter)
   104  	suite.Require().NoError(err)
   105  	clusterState, err := clusterkv.Bootstrap(suite.db, clusterStateRoot)
   106  	suite.Require().NoError(err)
   107  
   108  	suite.state, err = clusterkv.NewMutableState(clusterState, tracer, suite.headers, suite.payloads)
   109  	suite.Require().NoError(err)
   110  
   111  	state, err := pbadger.Bootstrap(
   112  		metrics,
   113  		suite.db,
   114  		all.Headers,
   115  		all.Seals,
   116  		all.Results,
   117  		all.Blocks,
   118  		all.QuorumCertificates,
   119  		all.Setups,
   120  		all.EpochCommits,
   121  		all.EpochProtocolState,
   122  		all.ProtocolKVStore,
   123  		all.VersionBeacons,
   124  		rootSnapshot,
   125  	)
   126  	require.NoError(suite.T(), err)
   127  
   128  	suite.protoState, err = pbadger.NewFollowerState(
   129  		log,
   130  		tracer,
   131  		consumer,
   132  		state,
   133  		all.Index,
   134  		all.Payloads,
   135  		util.MockBlockTimer(),
   136  	)
   137  	require.NoError(suite.T(), err)
   138  
   139  	// add some transactions to transaction pool
   140  	for i := 0; i < 3; i++ {
   141  		transaction := unittest.TransactionBodyFixture(func(tx *flow.TransactionBody) {
   142  			tx.ReferenceBlockID = root.ID()
   143  			tx.ProposalKey.SequenceNumber = uint64(i)
   144  			tx.GasLimit = uint64(9999)
   145  		})
   146  		added := suite.pool.Add(&transaction)
   147  		suite.Assert().True(added)
   148  	}
   149  
   150  	suite.builder, _ = builder.NewBuilder(suite.db, tracer, suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter)
   151  }
   152  
   153  // runs after each test finishes
   154  func (suite *BuilderSuite) TearDownTest() {
   155  	err := suite.db.Close()
   156  	suite.Assert().NoError(err)
   157  	err = os.RemoveAll(suite.dbdir)
   158  	suite.Assert().NoError(err)
   159  }
   160  
   161  func (suite *BuilderSuite) InsertBlock(block model.Block) {
   162  	err := suite.db.Update(procedure.InsertClusterBlock(&block))
   163  	suite.Assert().NoError(err)
   164  }
   165  
   166  func (suite *BuilderSuite) FinalizeBlock(block model.Block) {
   167  	err := suite.db.Update(func(tx *badger.Txn) error {
   168  		var refBlock flow.Header
   169  		err := operation.RetrieveHeader(block.Payload.ReferenceBlockID, &refBlock)(tx)
   170  		if err != nil {
   171  			return err
   172  		}
   173  		err = procedure.FinalizeClusterBlock(block.ID())(tx)
   174  		if err != nil {
   175  			return err
   176  		}
   177  		err = operation.IndexClusterBlockByReferenceHeight(refBlock.Height, block.ID())(tx)
   178  		return err
   179  	})
   180  	suite.Assert().NoError(err)
   181  }
   182  
   183  // Payload returns a payload containing the given transactions, with a valid
   184  // reference block ID.
   185  func (suite *BuilderSuite) Payload(transactions ...*flow.TransactionBody) model.Payload {
   186  	final, err := suite.protoState.Final().Head()
   187  	suite.Require().NoError(err)
   188  	return model.PayloadFromTransactions(final.ID(), transactions...)
   189  }
   190  
   191  // ProtoStateRoot returns the root block of the protocol state.
   192  func (suite *BuilderSuite) ProtoStateRoot() *flow.Header {
   193  	return suite.protoState.Params().FinalizedRoot()
   194  }
   195  
   196  // ClearPool removes all items from the pool
   197  func (suite *BuilderSuite) ClearPool() {
   198  	// TODO use Clear()
   199  	for _, tx := range suite.pool.All() {
   200  		suite.pool.Remove(tx.ID())
   201  	}
   202  }
   203  
   204  // FillPool adds n transactions to the pool, using the given generator function.
   205  func (suite *BuilderSuite) FillPool(n int, create func() *flow.TransactionBody) {
   206  	for i := 0; i < n; i++ {
   207  		tx := create()
   208  		suite.pool.Add(tx)
   209  	}
   210  }
   211  
   212  func TestBuilder(t *testing.T) {
   213  	suite.Run(t, new(BuilderSuite))
   214  }
   215  
   216  func (suite *BuilderSuite) TestBuildOn_NonExistentParent() {
   217  	// use a non-existent parent ID
   218  	parentID := unittest.IdentifierFixture()
   219  
   220  	_, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   221  	suite.Assert().Error(err)
   222  }
   223  
   224  func (suite *BuilderSuite) TestBuildOn_Success() {
   225  
   226  	var expectedHeight uint64 = 42
   227  	setter := func(h *flow.Header) error {
   228  		h.Height = expectedHeight
   229  		return nil
   230  	}
   231  
   232  	header, err := suite.builder.BuildOn(suite.genesis.ID(), setter, noopSigner)
   233  	suite.Require().NoError(err)
   234  
   235  	// setter should have been run
   236  	suite.Assert().Equal(expectedHeight, header.Height)
   237  
   238  	// should be able to retrieve built block from storage
   239  	var built model.Block
   240  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   241  	suite.Assert().NoError(err)
   242  	builtCollection := built.Payload.Collection
   243  
   244  	// should reference a valid reference block
   245  	// (since genesis is the only block, it's the only valid reference)
   246  	mainGenesis, err := suite.protoState.AtHeight(0).Head()
   247  	suite.Assert().NoError(err)
   248  	suite.Assert().Equal(mainGenesis.ID(), built.Payload.ReferenceBlockID)
   249  
   250  	// payload should include only items from mempool
   251  	mempoolTransactions := suite.pool.All()
   252  	suite.Assert().Len(builtCollection.Transactions, 3)
   253  	suite.Assert().True(collectionContains(builtCollection, flow.GetIDs(mempoolTransactions)...))
   254  }
   255  
   256  // when there are transactions with an unknown reference block in the pool, we should not include them in collections
   257  func (suite *BuilderSuite) TestBuildOn_WithUnknownReferenceBlock() {
   258  
   259  	// before modifying the mempool, note the valid transactions already in the pool
   260  	validMempoolTransactions := suite.pool.All()
   261  
   262  	// add a transaction unknown reference block to the pool
   263  	unknownReferenceTx := unittest.TransactionBodyFixture()
   264  	unknownReferenceTx.ReferenceBlockID = unittest.IdentifierFixture()
   265  	suite.pool.Add(&unknownReferenceTx)
   266  
   267  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   268  	suite.Require().NoError(err)
   269  
   270  	// should be able to retrieve built block from storage
   271  	var built model.Block
   272  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   273  	suite.Assert().NoError(err)
   274  	builtCollection := built.Payload.Collection
   275  
   276  	suite.Assert().Len(builtCollection.Transactions, 3)
   277  	// payload should include only the transactions with a valid reference block
   278  	suite.Assert().True(collectionContains(builtCollection, flow.GetIDs(validMempoolTransactions)...))
   279  	// should not contain the unknown-reference transaction
   280  	suite.Assert().False(collectionContains(builtCollection, unknownReferenceTx.ID()))
   281  }
   282  
   283  // when there are transactions with a known but unfinalized reference block in the pool, we should not include them in collections
   284  func (suite *BuilderSuite) TestBuildOn_WithUnfinalizedReferenceBlock() {
   285  
   286  	// before modifying the mempool, note the valid transactions already in the pool
   287  	validMempoolTransactions := suite.pool.All()
   288  
   289  	// add an unfinalized block to the protocol state
   290  	genesis, err := suite.protoState.Final().Head()
   291  	suite.Require().NoError(err)
   292  	protocolState, err := suite.protoState.Final().ProtocolState()
   293  	suite.Require().NoError(err)
   294  	protocolStateID := protocolState.ID()
   295  
   296  	unfinalizedReferenceBlock := unittest.BlockWithParentFixture(genesis)
   297  	unfinalizedReferenceBlock.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(protocolStateID)))
   298  	err = suite.protoState.ExtendCertified(context.Background(), unfinalizedReferenceBlock,
   299  		unittest.CertifyBlock(unfinalizedReferenceBlock.Header))
   300  	suite.Require().NoError(err)
   301  
   302  	// add a transaction with unfinalized reference block to the pool
   303  	unfinalizedReferenceTx := unittest.TransactionBodyFixture()
   304  	unfinalizedReferenceTx.ReferenceBlockID = unfinalizedReferenceBlock.ID()
   305  	suite.pool.Add(&unfinalizedReferenceTx)
   306  
   307  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   308  	suite.Require().NoError(err)
   309  
   310  	// should be able to retrieve built block from storage
   311  	var built model.Block
   312  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   313  	suite.Assert().NoError(err)
   314  	builtCollection := built.Payload.Collection
   315  
   316  	suite.Assert().Len(builtCollection.Transactions, 3)
   317  	// payload should include only the transactions with a valid reference block
   318  	suite.Assert().True(collectionContains(builtCollection, flow.GetIDs(validMempoolTransactions)...))
   319  	// should not contain the unfinalized-reference transaction
   320  	suite.Assert().False(collectionContains(builtCollection, unfinalizedReferenceTx.ID()))
   321  }
   322  
   323  // when there are transactions with an orphaned reference block in the pool, we should not include them in collections
   324  func (suite *BuilderSuite) TestBuildOn_WithOrphanedReferenceBlock() {
   325  
   326  	// before modifying the mempool, note the valid transactions already in the pool
   327  	validMempoolTransactions := suite.pool.All()
   328  
   329  	// add an orphaned block to the protocol state
   330  	genesis, err := suite.protoState.Final().Head()
   331  	suite.Require().NoError(err)
   332  	protocolState, err := suite.protoState.Final().ProtocolState()
   333  	suite.Require().NoError(err)
   334  	protocolStateID := protocolState.ID()
   335  
   336  	// create a block extending genesis which will be orphaned
   337  	orphan := unittest.BlockWithParentFixture(genesis)
   338  	orphan.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(protocolStateID)))
   339  	err = suite.protoState.ExtendCertified(context.Background(), orphan, unittest.CertifyBlock(orphan.Header))
   340  	suite.Require().NoError(err)
   341  	// create and finalize a block on top of genesis, orphaning `orphan`
   342  	block1 := unittest.BlockWithParentFixture(genesis)
   343  	block1.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(protocolStateID)))
   344  	err = suite.protoState.ExtendCertified(context.Background(), block1, unittest.CertifyBlock(block1.Header))
   345  	suite.Require().NoError(err)
   346  	err = suite.protoState.Finalize(context.Background(), block1.ID())
   347  	suite.Require().NoError(err)
   348  
   349  	// add a transaction with orphaned reference block to the pool
   350  	orphanedReferenceTx := unittest.TransactionBodyFixture()
   351  	orphanedReferenceTx.ReferenceBlockID = orphan.ID()
   352  	suite.pool.Add(&orphanedReferenceTx)
   353  
   354  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   355  	suite.Require().NoError(err)
   356  
   357  	// should be able to retrieve built block from storage
   358  	var built model.Block
   359  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   360  	suite.Assert().NoError(err)
   361  	builtCollection := built.Payload.Collection
   362  
   363  	suite.Assert().Len(builtCollection.Transactions, 3)
   364  	// payload should include only the transactions with a valid reference block
   365  	suite.Assert().True(collectionContains(builtCollection, flow.GetIDs(validMempoolTransactions)...))
   366  	// should not contain the unknown-reference transaction
   367  	suite.Assert().False(collectionContains(builtCollection, orphanedReferenceTx.ID()))
   368  	// the transaction with orphaned reference should be removed from the mempool
   369  	suite.Assert().False(suite.pool.Has(orphanedReferenceTx.ID()))
   370  }
   371  
   372  func (suite *BuilderSuite) TestBuildOn_WithForks() {
   373  	t := suite.T()
   374  
   375  	mempoolTransactions := suite.pool.All()
   376  	tx1 := mempoolTransactions[0] // in fork 1
   377  	tx2 := mempoolTransactions[1] // in fork 2
   378  	tx3 := mempoolTransactions[2] // in no block
   379  
   380  	// build first fork on top of genesis
   381  	payload1 := suite.Payload(tx1)
   382  	block1 := unittest.ClusterBlockWithParent(suite.genesis)
   383  	block1.SetPayload(payload1)
   384  
   385  	// insert block on fork 1
   386  	suite.InsertBlock(block1)
   387  
   388  	// build second fork on top of genesis
   389  	payload2 := suite.Payload(tx2)
   390  	block2 := unittest.ClusterBlockWithParent(suite.genesis)
   391  	block2.SetPayload(payload2)
   392  
   393  	// insert block on fork 2
   394  	suite.InsertBlock(block2)
   395  
   396  	// build on top of fork 1
   397  	header, err := suite.builder.BuildOn(block1.ID(), noopSetter, noopSigner)
   398  	require.NoError(t, err)
   399  
   400  	// should be able to retrieve built block from storage
   401  	var built model.Block
   402  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   403  	assert.NoError(t, err)
   404  	builtCollection := built.Payload.Collection
   405  
   406  	// payload should include ONLY tx2 and tx3
   407  	assert.Len(t, builtCollection.Transactions, 2)
   408  	assert.True(t, collectionContains(builtCollection, tx2.ID(), tx3.ID()))
   409  	assert.False(t, collectionContains(builtCollection, tx1.ID()))
   410  }
   411  
   412  func (suite *BuilderSuite) TestBuildOn_ConflictingFinalizedBlock() {
   413  	t := suite.T()
   414  
   415  	mempoolTransactions := suite.pool.All()
   416  	tx1 := mempoolTransactions[0] // in a finalized block
   417  	tx2 := mempoolTransactions[1] // in an un-finalized block
   418  	tx3 := mempoolTransactions[2] // in no blocks
   419  
   420  	t.Logf("tx1: %s\ntx2: %s\ntx3: %s", tx1.ID(), tx2.ID(), tx3.ID())
   421  
   422  	// build a block containing tx1 on genesis
   423  	finalizedPayload := suite.Payload(tx1)
   424  	finalizedBlock := unittest.ClusterBlockWithParent(suite.genesis)
   425  	finalizedBlock.SetPayload(finalizedPayload)
   426  	suite.InsertBlock(finalizedBlock)
   427  	t.Logf("finalized: height=%d id=%s txs=%s parent_id=%s\t\n", finalizedBlock.Header.Height, finalizedBlock.ID(), finalizedPayload.Collection.Light(), finalizedBlock.Header.ParentID)
   428  
   429  	// build a block containing tx2 on the first block
   430  	unFinalizedPayload := suite.Payload(tx2)
   431  	unFinalizedBlock := unittest.ClusterBlockWithParent(&finalizedBlock)
   432  	unFinalizedBlock.SetPayload(unFinalizedPayload)
   433  	suite.InsertBlock(unFinalizedBlock)
   434  	t.Logf("finalized: height=%d id=%s txs=%s parent_id=%s\t\n", unFinalizedBlock.Header.Height, unFinalizedBlock.ID(), unFinalizedPayload.Collection.Light(), unFinalizedBlock.Header.ParentID)
   435  
   436  	// finalize first block
   437  	suite.FinalizeBlock(finalizedBlock)
   438  
   439  	// build on the un-finalized block
   440  	header, err := suite.builder.BuildOn(unFinalizedBlock.ID(), noopSetter, noopSigner)
   441  	require.NoError(t, err)
   442  
   443  	// retrieve the built block from storage
   444  	var built model.Block
   445  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   446  	assert.NoError(t, err)
   447  	builtCollection := built.Payload.Collection
   448  
   449  	// payload should only contain tx3
   450  	assert.Len(t, builtCollection.Light().Transactions, 1)
   451  	assert.True(t, collectionContains(builtCollection, tx3.ID()))
   452  	assert.False(t, collectionContains(builtCollection, tx1.ID(), tx2.ID()))
   453  
   454  	// tx1 should be removed from mempool, as it is in a finalized block
   455  	assert.False(t, suite.pool.Has(tx1.ID()))
   456  	// tx2 should NOT be removed from mempool, as it is in an un-finalized block
   457  	assert.True(t, suite.pool.Has(tx2.ID()))
   458  }
   459  
   460  func (suite *BuilderSuite) TestBuildOn_ConflictingInvalidatedForks() {
   461  	t := suite.T()
   462  
   463  	mempoolTransactions := suite.pool.All()
   464  	tx1 := mempoolTransactions[0] // in a finalized block
   465  	tx2 := mempoolTransactions[1] // in an invalidated block
   466  	tx3 := mempoolTransactions[2] // in no blocks
   467  
   468  	t.Logf("tx1: %s\ntx2: %s\ntx3: %s", tx1.ID(), tx2.ID(), tx3.ID())
   469  
   470  	// build a block containing tx1 on genesis - will be finalized
   471  	finalizedPayload := suite.Payload(tx1)
   472  	finalizedBlock := unittest.ClusterBlockWithParent(suite.genesis)
   473  	finalizedBlock.SetPayload(finalizedPayload)
   474  
   475  	suite.InsertBlock(finalizedBlock)
   476  	t.Logf("finalized: id=%s\tparent_id=%s\theight=%d\n", finalizedBlock.ID(), finalizedBlock.Header.ParentID, finalizedBlock.Header.Height)
   477  
   478  	// build a block containing tx2 ALSO on genesis - will be invalidated
   479  	invalidatedPayload := suite.Payload(tx2)
   480  	invalidatedBlock := unittest.ClusterBlockWithParent(suite.genesis)
   481  	invalidatedBlock.SetPayload(invalidatedPayload)
   482  	suite.InsertBlock(invalidatedBlock)
   483  	t.Logf("invalidated: id=%s\tparent_id=%s\theight=%d\n", invalidatedBlock.ID(), invalidatedBlock.Header.ParentID, invalidatedBlock.Header.Height)
   484  
   485  	// finalize first block - this indirectly invalidates the second block
   486  	suite.FinalizeBlock(finalizedBlock)
   487  
   488  	// build on the finalized block
   489  	header, err := suite.builder.BuildOn(finalizedBlock.ID(), noopSetter, noopSigner)
   490  	require.NoError(t, err)
   491  
   492  	// retrieve the built block from storage
   493  	var built model.Block
   494  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   495  	assert.NoError(t, err)
   496  	builtCollection := built.Payload.Collection
   497  
   498  	// tx2 and tx3 should be in the built collection
   499  	assert.Len(t, builtCollection.Light().Transactions, 2)
   500  	assert.True(t, collectionContains(builtCollection, tx2.ID(), tx3.ID()))
   501  	assert.False(t, collectionContains(builtCollection, tx1.ID()))
   502  }
   503  
   504  func (suite *BuilderSuite) TestBuildOn_LargeHistory() {
   505  	t := suite.T()
   506  
   507  	// use a mempool with 2000 transactions, one per block
   508  	suite.pool = herocache.NewTransactions(2000, unittest.Logger(), metrics.NewNoopCollector())
   509  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter, builder.WithMaxCollectionSize(10000))
   510  
   511  	// get a valid reference block ID
   512  	final, err := suite.protoState.Final().Head()
   513  	require.NoError(t, err)
   514  	refID := final.ID()
   515  
   516  	// keep track of the head of the chain
   517  	head := *suite.genesis
   518  
   519  	// keep track of invalidated transaction IDs
   520  	var invalidatedTxIds []flow.Identifier
   521  
   522  	// create a large history of blocks with invalidated forks every 3 blocks on
   523  	// average - build until the height exceeds transaction expiry
   524  	for i := 0; ; i++ {
   525  
   526  		// create a transaction
   527  		tx := unittest.TransactionBodyFixture(func(tx *flow.TransactionBody) {
   528  			tx.ReferenceBlockID = refID
   529  			tx.ProposalKey.SequenceNumber = uint64(i)
   530  		})
   531  		added := suite.pool.Add(&tx)
   532  		assert.True(t, added)
   533  
   534  		// 1/3 of the time create a conflicting fork that will be invalidated
   535  		// don't do this the first and last few times to ensure we don't
   536  		// try to fork genesis and the last block is the valid fork.
   537  		conflicting := rand.Intn(3) == 0 && i > 5 && i < 995
   538  
   539  		// by default, build on the head - if we are building a
   540  		// conflicting fork, build on the parent of the head
   541  		parent := head
   542  		if conflicting {
   543  			err = suite.db.View(procedure.RetrieveClusterBlock(parent.Header.ParentID, &parent))
   544  			assert.NoError(t, err)
   545  			// add the transaction to the invalidated list
   546  			invalidatedTxIds = append(invalidatedTxIds, tx.ID())
   547  		}
   548  
   549  		// create a block containing the transaction
   550  		block := unittest.ClusterBlockWithParent(&head)
   551  		payload := suite.Payload(&tx)
   552  		block.SetPayload(payload)
   553  		suite.InsertBlock(block)
   554  
   555  		// reset the valid head if we aren't building a conflicting fork
   556  		if !conflicting {
   557  			head = block
   558  			suite.FinalizeBlock(block)
   559  			assert.NoError(t, err)
   560  		}
   561  
   562  		// stop building blocks once we've built a history which exceeds the transaction
   563  		// expiry length - this tests that deduplication works properly against old blocks
   564  		// which nevertheless have a potentially conflicting reference block
   565  		if head.Header.Height > flow.DefaultTransactionExpiry+100 {
   566  			break
   567  		}
   568  	}
   569  
   570  	t.Log("conflicting: ", len(invalidatedTxIds))
   571  
   572  	// build on the head block
   573  	header, err := suite.builder.BuildOn(head.ID(), noopSetter, noopSigner)
   574  	require.NoError(t, err)
   575  
   576  	// retrieve the built block from storage
   577  	var built model.Block
   578  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   579  	require.NoError(t, err)
   580  	builtCollection := built.Payload.Collection
   581  
   582  	// payload should only contain transactions from invalidated blocks
   583  	assert.Len(t, builtCollection.Transactions, len(invalidatedTxIds), "expected len=%d, got len=%d", len(invalidatedTxIds), len(builtCollection.Transactions))
   584  	assert.True(t, collectionContains(builtCollection, invalidatedTxIds...))
   585  }
   586  
   587  func (suite *BuilderSuite) TestBuildOn_MaxCollectionSize() {
   588  	// set the max collection size to 1
   589  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter, builder.WithMaxCollectionSize(1))
   590  
   591  	// build a block
   592  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   593  	suite.Require().NoError(err)
   594  
   595  	// retrieve the built block from storage
   596  	var built model.Block
   597  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   598  	suite.Require().NoError(err)
   599  	builtCollection := built.Payload.Collection
   600  
   601  	// should be only 1 transaction in the collection
   602  	suite.Assert().Equal(builtCollection.Len(), 1)
   603  }
   604  
   605  func (suite *BuilderSuite) TestBuildOn_MaxCollectionByteSize() {
   606  	// set the max collection byte size to 400 (each tx is about 150 bytes)
   607  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter, builder.WithMaxCollectionByteSize(400))
   608  
   609  	// build a block
   610  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   611  	suite.Require().NoError(err)
   612  
   613  	// retrieve the built block from storage
   614  	var built model.Block
   615  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   616  	suite.Require().NoError(err)
   617  	builtCollection := built.Payload.Collection
   618  
   619  	// should be only 2 transactions in the collection, since each tx is ~273 bytes and the limit is 600 bytes
   620  	suite.Assert().Equal(builtCollection.Len(), 2)
   621  }
   622  
   623  func (suite *BuilderSuite) TestBuildOn_MaxCollectionTotalGas() {
   624  	// set the max gas to 20,000
   625  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter, builder.WithMaxCollectionTotalGas(20000))
   626  
   627  	// build a block
   628  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   629  	suite.Require().NoError(err)
   630  
   631  	// retrieve the built block from storage
   632  	var built model.Block
   633  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   634  	suite.Require().NoError(err)
   635  	builtCollection := built.Payload.Collection
   636  
   637  	// should be only 2 transactions in collection, since each transaction has gas limit of 9,999 and collection limit is set to 20,000
   638  	suite.Assert().Equal(builtCollection.Len(), 2)
   639  }
   640  
   641  func (suite *BuilderSuite) TestBuildOn_ExpiredTransaction() {
   642  
   643  	// create enough main-chain blocks that an expired transaction is possible
   644  	genesis, err := suite.protoState.Final().Head()
   645  	suite.Require().NoError(err)
   646  	protocolState, err := suite.protoState.Final().ProtocolState()
   647  	suite.Require().NoError(err)
   648  	protocolStateID := protocolState.ID()
   649  
   650  	head := genesis
   651  	for i := 0; i < flow.DefaultTransactionExpiry+1; i++ {
   652  		block := unittest.BlockWithParentFixture(head)
   653  		block.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(protocolStateID)))
   654  		err = suite.protoState.ExtendCertified(context.Background(), block, unittest.CertifyBlock(block.Header))
   655  		suite.Require().NoError(err)
   656  		err = suite.protoState.Finalize(context.Background(), block.ID())
   657  		suite.Require().NoError(err)
   658  		head = block.Header
   659  	}
   660  
   661  	// reset the pool and builder
   662  	suite.pool = herocache.NewTransactions(10, unittest.Logger(), metrics.NewNoopCollector())
   663  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter)
   664  
   665  	// insert a transaction referring genesis (now expired)
   666  	tx1 := unittest.TransactionBodyFixture(func(tx *flow.TransactionBody) {
   667  		tx.ReferenceBlockID = genesis.ID()
   668  		tx.ProposalKey.SequenceNumber = 0
   669  	})
   670  	added := suite.pool.Add(&tx1)
   671  	suite.Assert().True(added)
   672  
   673  	// insert a transaction referencing the head (valid)
   674  	tx2 := unittest.TransactionBodyFixture(func(tx *flow.TransactionBody) {
   675  		tx.ReferenceBlockID = head.ID()
   676  		tx.ProposalKey.SequenceNumber = 1
   677  	})
   678  	added = suite.pool.Add(&tx2)
   679  	suite.Assert().True(added)
   680  
   681  	suite.T().Log("tx1: ", tx1.ID())
   682  	suite.T().Log("tx2: ", tx2.ID())
   683  
   684  	// build a block
   685  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   686  	suite.Require().NoError(err)
   687  
   688  	// retrieve the built block from storage
   689  	var built model.Block
   690  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   691  	suite.Require().NoError(err)
   692  	builtCollection := built.Payload.Collection
   693  
   694  	// the block should only contain the un-expired transaction
   695  	suite.Assert().False(collectionContains(builtCollection, tx1.ID()))
   696  	suite.Assert().True(collectionContains(builtCollection, tx2.ID()))
   697  	// the expired transaction should have been removed from the mempool
   698  	suite.Assert().False(suite.pool.Has(tx1.ID()))
   699  }
   700  
   701  func (suite *BuilderSuite) TestBuildOn_EmptyMempool() {
   702  
   703  	// start with an empty mempool
   704  	suite.pool = herocache.NewTransactions(1000, unittest.Logger(), metrics.NewNoopCollector())
   705  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter)
   706  
   707  	header, err := suite.builder.BuildOn(suite.genesis.ID(), noopSetter, noopSigner)
   708  	suite.Require().NoError(err)
   709  
   710  	var built model.Block
   711  	err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   712  	suite.Require().NoError(err)
   713  
   714  	// should reference a valid reference block
   715  	// (since genesis is the only block, it's the only valid reference)
   716  	mainGenesis, err := suite.protoState.AtHeight(0).Head()
   717  	suite.Assert().NoError(err)
   718  	suite.Assert().Equal(mainGenesis.ID(), built.Payload.ReferenceBlockID)
   719  
   720  	// the payload should be empty
   721  	suite.Assert().Equal(0, built.Payload.Collection.Len())
   722  }
   723  
   724  // With rate limiting turned off, we should fill collections as fast as we can
   725  // regardless of how many transactions with the same payer we include.
   726  func (suite *BuilderSuite) TestBuildOn_NoRateLimiting() {
   727  
   728  	// start with an empty mempool
   729  	suite.ClearPool()
   730  
   731  	// create builder with no rate limit and max 10 tx/collection
   732  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   733  		builder.WithMaxCollectionSize(10),
   734  		builder.WithMaxPayerTransactionRate(0),
   735  	)
   736  
   737  	// fill the pool with 100 transactions from the same payer
   738  	payer := unittest.RandomAddressFixture()
   739  	create := func() *flow.TransactionBody {
   740  		tx := unittest.TransactionBodyFixture()
   741  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   742  		tx.Payer = payer
   743  		return &tx
   744  	}
   745  	suite.FillPool(100, create)
   746  
   747  	// since we have no rate limiting we should fill all collections and in 10 blocks
   748  	parentID := suite.genesis.ID()
   749  	for i := 0; i < 10; i++ {
   750  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   751  		suite.Require().NoError(err)
   752  		parentID = header.ID()
   753  
   754  		// each collection should be full with 10 transactions
   755  		var built model.Block
   756  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   757  		suite.Assert().NoError(err)
   758  		suite.Assert().Len(built.Payload.Collection.Transactions, 10)
   759  	}
   760  }
   761  
   762  // With rate limiting turned on, we should be able to fill transactions as fast
   763  // as possible so long as per-payer limits are not reached. This test generates
   764  // transactions such that the number of transactions with a given proposer exceeds
   765  // the rate limit -- since it's the proposer not the payer, it shouldn't limit
   766  // our collections.
   767  func (suite *BuilderSuite) TestBuildOn_RateLimitNonPayer() {
   768  
   769  	// start with an empty mempool
   770  	suite.ClearPool()
   771  
   772  	// create builder with 5 tx/payer and max 10 tx/collection
   773  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   774  		builder.WithMaxCollectionSize(10),
   775  		builder.WithMaxPayerTransactionRate(5),
   776  	)
   777  
   778  	// fill the pool with 100 transactions with the same proposer
   779  	// since it's not the same payer, rate limit does not apply
   780  	proposer := unittest.RandomAddressFixture()
   781  	create := func() *flow.TransactionBody {
   782  		tx := unittest.TransactionBodyFixture()
   783  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   784  		tx.Payer = unittest.RandomAddressFixture()
   785  		tx.ProposalKey = flow.ProposalKey{
   786  			Address:        proposer,
   787  			KeyIndex:       rand.Uint64(),
   788  			SequenceNumber: rand.Uint64(),
   789  		}
   790  		return &tx
   791  	}
   792  	suite.FillPool(100, create)
   793  
   794  	// since rate limiting does not apply to non-payer keys, we should fill all collections in 10 blocks
   795  	parentID := suite.genesis.ID()
   796  	for i := 0; i < 10; i++ {
   797  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   798  		suite.Require().NoError(err)
   799  		parentID = header.ID()
   800  
   801  		// each collection should be full with 10 transactions
   802  		var built model.Block
   803  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   804  		suite.Assert().NoError(err)
   805  		suite.Assert().Len(built.Payload.Collection.Transactions, 10)
   806  	}
   807  }
   808  
   809  // When configured with a rate limit of k>1, we should be able to include up to
   810  // k transactions with a given payer per collection
   811  func (suite *BuilderSuite) TestBuildOn_HighRateLimit() {
   812  
   813  	// start with an empty mempool
   814  	suite.ClearPool()
   815  
   816  	// create builder with 5 tx/payer and max 10 tx/collection
   817  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   818  		builder.WithMaxCollectionSize(10),
   819  		builder.WithMaxPayerTransactionRate(5),
   820  	)
   821  
   822  	// fill the pool with 50 transactions from the same payer
   823  	payer := unittest.RandomAddressFixture()
   824  	create := func() *flow.TransactionBody {
   825  		tx := unittest.TransactionBodyFixture()
   826  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   827  		tx.Payer = payer
   828  		return &tx
   829  	}
   830  	suite.FillPool(50, create)
   831  
   832  	// rate-limiting should be applied, resulting in half-full collections (5/10)
   833  	parentID := suite.genesis.ID()
   834  	for i := 0; i < 10; i++ {
   835  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   836  		suite.Require().NoError(err)
   837  		parentID = header.ID()
   838  
   839  		// each collection should be half-full with 5 transactions
   840  		var built model.Block
   841  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   842  		suite.Assert().NoError(err)
   843  		suite.Assert().Len(built.Payload.Collection.Transactions, 5)
   844  	}
   845  }
   846  
   847  // When configured with a rate limit of k<1, we should be able to include 1
   848  // transactions with a given payer every ceil(1/k) collections
   849  func (suite *BuilderSuite) TestBuildOn_LowRateLimit() {
   850  
   851  	// start with an empty mempool
   852  	suite.ClearPool()
   853  
   854  	// create builder with .5 tx/payer and max 10 tx/collection
   855  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   856  		builder.WithMaxCollectionSize(10),
   857  		builder.WithMaxPayerTransactionRate(.5),
   858  	)
   859  
   860  	// fill the pool with 5 transactions from the same payer
   861  	payer := unittest.RandomAddressFixture()
   862  	create := func() *flow.TransactionBody {
   863  		tx := unittest.TransactionBodyFixture()
   864  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   865  		tx.Payer = payer
   866  		return &tx
   867  	}
   868  	suite.FillPool(5, create)
   869  
   870  	// rate-limiting should be applied, resulting in every ceil(1/k) collections
   871  	// having one transaction and empty collections otherwise
   872  	parentID := suite.genesis.ID()
   873  	for i := 0; i < 10; i++ {
   874  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   875  		suite.Require().NoError(err)
   876  		parentID = header.ID()
   877  
   878  		// collections should either be empty or have 1 transaction
   879  		var built model.Block
   880  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   881  		suite.Assert().NoError(err)
   882  		if i%2 == 0 {
   883  			suite.Assert().Len(built.Payload.Collection.Transactions, 1)
   884  		} else {
   885  			suite.Assert().Len(built.Payload.Collection.Transactions, 0)
   886  		}
   887  	}
   888  }
   889  func (suite *BuilderSuite) TestBuildOn_UnlimitedPayer() {
   890  
   891  	// start with an empty mempool
   892  	suite.ClearPool()
   893  
   894  	// create builder with 5 tx/payer and max 10 tx/collection
   895  	// configure an unlimited payer
   896  	payer := unittest.RandomAddressFixture()
   897  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   898  		builder.WithMaxCollectionSize(10),
   899  		builder.WithMaxPayerTransactionRate(5),
   900  		builder.WithUnlimitedPayers(payer),
   901  	)
   902  
   903  	// fill the pool with 100 transactions from the same payer
   904  	create := func() *flow.TransactionBody {
   905  		tx := unittest.TransactionBodyFixture()
   906  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   907  		tx.Payer = payer
   908  		return &tx
   909  	}
   910  	suite.FillPool(100, create)
   911  
   912  	// rate-limiting should not be applied, since the payer is marked as unlimited
   913  	parentID := suite.genesis.ID()
   914  	for i := 0; i < 10; i++ {
   915  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   916  		suite.Require().NoError(err)
   917  		parentID = header.ID()
   918  
   919  		// each collection should be full with 10 transactions
   920  		var built model.Block
   921  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   922  		suite.Assert().NoError(err)
   923  		suite.Assert().Len(built.Payload.Collection.Transactions, 10)
   924  
   925  	}
   926  }
   927  
   928  // TestBuildOn_RateLimitDryRun tests that rate limiting rules aren't enforced
   929  // if dry-run is enabled.
   930  func (suite *BuilderSuite) TestBuildOn_RateLimitDryRun() {
   931  
   932  	// start with an empty mempool
   933  	suite.ClearPool()
   934  
   935  	// create builder with 5 tx/payer and max 10 tx/collection
   936  	// configure an unlimited payer
   937  	payer := unittest.RandomAddressFixture()
   938  	suite.builder, _ = builder.NewBuilder(suite.db, trace.NewNoopTracer(), suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter,
   939  		builder.WithMaxCollectionSize(10),
   940  		builder.WithMaxPayerTransactionRate(5),
   941  		builder.WithRateLimitDryRun(true),
   942  	)
   943  
   944  	// fill the pool with 100 transactions from the same payer
   945  	create := func() *flow.TransactionBody {
   946  		tx := unittest.TransactionBodyFixture()
   947  		tx.ReferenceBlockID = suite.ProtoStateRoot().ID()
   948  		tx.Payer = payer
   949  		return &tx
   950  	}
   951  	suite.FillPool(100, create)
   952  
   953  	// rate-limiting should not be applied, since dry-run setting is enabled
   954  	parentID := suite.genesis.ID()
   955  	for i := 0; i < 10; i++ {
   956  		header, err := suite.builder.BuildOn(parentID, noopSetter, noopSigner)
   957  		suite.Require().NoError(err)
   958  		parentID = header.ID()
   959  
   960  		// each collection should be full with 10 transactions
   961  		var built model.Block
   962  		err = suite.db.View(procedure.RetrieveClusterBlock(header.ID(), &built))
   963  		suite.Assert().NoError(err)
   964  		suite.Assert().Len(built.Payload.Collection.Transactions, 10)
   965  	}
   966  }
   967  
   968  // helper to check whether a collection contains each of the given transactions.
   969  func collectionContains(collection flow.Collection, txIDs ...flow.Identifier) bool {
   970  
   971  	lookup := make(map[flow.Identifier]struct{}, len(txIDs))
   972  	for _, tx := range collection.Transactions {
   973  		lookup[tx.ID()] = struct{}{}
   974  	}
   975  
   976  	for _, txID := range txIDs {
   977  		_, exists := lookup[txID]
   978  		if !exists {
   979  			return false
   980  		}
   981  	}
   982  
   983  	return true
   984  }
   985  
   986  func BenchmarkBuildOn10(b *testing.B)     { benchmarkBuildOn(b, 10) }
   987  func BenchmarkBuildOn100(b *testing.B)    { benchmarkBuildOn(b, 100) }
   988  func BenchmarkBuildOn1000(b *testing.B)   { benchmarkBuildOn(b, 1000) }
   989  func BenchmarkBuildOn10000(b *testing.B)  { benchmarkBuildOn(b, 10000) }
   990  func BenchmarkBuildOn100000(b *testing.B) { benchmarkBuildOn(b, 100000) }
   991  
   992  func benchmarkBuildOn(b *testing.B, size int) {
   993  	b.StopTimer()
   994  	b.ResetTimer()
   995  
   996  	// re-use the builder suite
   997  	suite := new(BuilderSuite)
   998  
   999  	// Copied from SetupTest. We can't use that function because suite.Assert
  1000  	// is incompatible with benchmarks.
  1001  	// ref: https://github.com/stretchr/testify/issues/811
  1002  	{
  1003  		var err error
  1004  
  1005  		suite.genesis = model.Genesis()
  1006  		suite.chainID = suite.genesis.Header.ChainID
  1007  
  1008  		suite.pool = herocache.NewTransactions(1000, unittest.Logger(), metrics.NewNoopCollector())
  1009  
  1010  		suite.dbdir = unittest.TempDir(b)
  1011  		suite.db = unittest.BadgerDB(b, suite.dbdir)
  1012  		defer func() {
  1013  			err = suite.db.Close()
  1014  			assert.NoError(b, err)
  1015  			err = os.RemoveAll(suite.dbdir)
  1016  			assert.NoError(b, err)
  1017  		}()
  1018  
  1019  		metrics := metrics.NewNoopCollector()
  1020  		tracer := trace.NewNoopTracer()
  1021  		all := sutil.StorageLayer(suite.T(), suite.db)
  1022  		suite.headers = all.Headers
  1023  		suite.blocks = all.Blocks
  1024  		suite.payloads = bstorage.NewClusterPayloads(metrics, suite.db)
  1025  
  1026  		qc := unittest.QuorumCertificateFixture(unittest.QCWithRootBlockID(suite.genesis.ID()))
  1027  		stateRoot, err := clusterkv.NewStateRoot(suite.genesis, qc, suite.epochCounter)
  1028  
  1029  		state, err := clusterkv.Bootstrap(suite.db, stateRoot)
  1030  		assert.NoError(b, err)
  1031  
  1032  		suite.state, err = clusterkv.NewMutableState(state, tracer, suite.headers, suite.payloads)
  1033  		assert.NoError(b, err)
  1034  
  1035  		// add some transactions to transaction pool
  1036  		for i := 0; i < 3; i++ {
  1037  			tx := unittest.TransactionBodyFixture()
  1038  			added := suite.pool.Add(&tx)
  1039  			assert.True(b, added)
  1040  		}
  1041  
  1042  		// create the builder
  1043  		suite.builder, _ = builder.NewBuilder(suite.db, tracer, suite.protoState, suite.state, suite.headers, suite.headers, suite.payloads, suite.pool, unittest.Logger(), suite.epochCounter)
  1044  	}
  1045  
  1046  	// create a block history to test performance against
  1047  	final := suite.genesis
  1048  	for i := 0; i < size; i++ {
  1049  		block := unittest.ClusterBlockWithParent(final)
  1050  		err := suite.db.Update(procedure.InsertClusterBlock(&block))
  1051  		require.NoError(b, err)
  1052  
  1053  		// finalize the block 80% of the time, resulting in a fork-rate of 20%
  1054  		if rand.Intn(100) < 80 {
  1055  			err = suite.db.Update(procedure.FinalizeClusterBlock(block.ID()))
  1056  			require.NoError(b, err)
  1057  			final = &block
  1058  		}
  1059  	}
  1060  
  1061  	b.StartTimer()
  1062  	for n := 0; n < b.N; n++ {
  1063  		_, err := suite.builder.BuildOn(final.ID(), noopSetter, noopSigner)
  1064  		assert.NoError(b, err)
  1065  	}
  1066  }