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