github.com/koko1123/flow-go-1@v0.29.6/module/builder/collection/builder_test.go (about)

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