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

     1  package consensus
     2  
     3  import (
     4  	"math/rand"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/dgraph-io/badger/v2"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/stretchr/testify/suite"
    13  
    14  	"github.com/onflow/flow-go/model/flow"
    15  	mempoolAPIs "github.com/onflow/flow-go/module/mempool"
    16  	mempoolImpl "github.com/onflow/flow-go/module/mempool/consensus"
    17  	mempool "github.com/onflow/flow-go/module/mempool/mock"
    18  	"github.com/onflow/flow-go/module/metrics"
    19  	"github.com/onflow/flow-go/module/trace"
    20  	realproto "github.com/onflow/flow-go/state/protocol"
    21  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    22  	storerr "github.com/onflow/flow-go/storage"
    23  	"github.com/onflow/flow-go/storage/badger/operation"
    24  	"github.com/onflow/flow-go/storage/badger/transaction"
    25  	storage "github.com/onflow/flow-go/storage/mock"
    26  	"github.com/onflow/flow-go/utils/unittest"
    27  )
    28  
    29  func TestConsensusBuilder(t *testing.T) {
    30  	suite.Run(t, new(BuilderSuite))
    31  }
    32  
    33  type BuilderSuite struct {
    34  	suite.Suite
    35  
    36  	// test helpers
    37  	firstID           flow.Identifier                               // first block in the range we look at
    38  	finalID           flow.Identifier                               // last finalized block
    39  	parentID          flow.Identifier                               //	Parent block we build on
    40  	finalizedBlockIDs []flow.Identifier                             // blocks between first and final
    41  	pendingBlockIDs   []flow.Identifier                             // blocks between final and parent
    42  	resultForBlock    map[flow.Identifier]*flow.ExecutionResult     // map: BlockID -> Execution Result
    43  	resultByID        map[flow.Identifier]*flow.ExecutionResult     // map: result ID -> Execution Result
    44  	receiptsByID      map[flow.Identifier]*flow.ExecutionReceipt    // map: receipt ID -> ExecutionReceipt
    45  	receiptsByBlockID map[flow.Identifier]flow.ExecutionReceiptList // map: block ID -> flow.ExecutionReceiptList
    46  
    47  	// used to populate and test the seal mempool
    48  	chain   []*flow.Seal                                     // chain of seals starting first
    49  	irsList []*flow.IncorporatedResultSeal                   // chain of IncorporatedResultSeals
    50  	irsMap  map[flow.Identifier]*flow.IncorporatedResultSeal // index for irsList
    51  
    52  	// mempools consumed by builder
    53  	pendingGuarantees []*flow.CollectionGuarantee
    54  	pendingReceipts   []*flow.ExecutionReceipt
    55  	pendingSeals      map[flow.Identifier]*flow.IncorporatedResultSeal // storage for the seal mempool
    56  
    57  	// storage for dbs
    58  	headers       map[flow.Identifier]*flow.Header
    59  	index         map[flow.Identifier]*flow.Index
    60  	blocks        map[flow.Identifier]*flow.Block
    61  	blockChildren map[flow.Identifier][]flow.Identifier // ids of children blocks
    62  
    63  	lastSeal *flow.Seal
    64  
    65  	// real dependencies
    66  	dir      string
    67  	db       *badger.DB
    68  	sentinel uint64
    69  	setter   func(*flow.Header) error
    70  	sign     func(*flow.Header) error
    71  
    72  	// mocked dependencies
    73  	state        *protocol.ParticipantState
    74  	headerDB     *storage.Headers
    75  	sealDB       *storage.Seals
    76  	indexDB      *storage.Index
    77  	blockDB      *storage.Blocks
    78  	resultDB     *storage.ExecutionResults
    79  	receiptsDB   *storage.ExecutionReceipts
    80  	stateMutator *protocol.MutableProtocolState
    81  
    82  	guarPool *mempool.Guarantees
    83  	sealPool *mempool.IncorporatedResultSeals
    84  	recPool  *mempool.ExecutionTree
    85  
    86  	// tracking behaviour
    87  	assembled *flow.Payload // built payload
    88  
    89  	// component under test
    90  	build *Builder
    91  }
    92  
    93  func (bs *BuilderSuite) storeBlock(block *flow.Block) {
    94  	bs.headers[block.ID()] = block.Header
    95  	bs.blocks[block.ID()] = block
    96  	bs.index[block.ID()] = block.Payload.Index()
    97  	bs.blockChildren[block.Header.ParentID] = append(bs.blockChildren[block.Header.ParentID], block.ID())
    98  	for _, result := range block.Payload.Results {
    99  		bs.resultByID[result.ID()] = result
   100  	}
   101  }
   102  
   103  // createAndRecordBlock creates a new block chained to the previous block.
   104  // The new block contains a receipt for a result of the previous
   105  // block, which is also used to create a seal for the previous block. The seal
   106  // and the result are combined in an IncorporatedResultSeal which is a candidate
   107  // for the seals mempool.
   108  func (bs *BuilderSuite) createAndRecordBlock(parentBlock *flow.Block, candidateSealForParent bool) *flow.Block {
   109  	block := unittest.BlockWithParentFixture(parentBlock.Header)
   110  
   111  	// Create a receipt for a result of the parentBlock block,
   112  	// and add it to the payload. The corresponding IncorporatedResult will be used to
   113  	// seal the parentBlock, and to create an IncorporatedResultSeal for the seal mempool.
   114  	var incorporatedResultForPrevBlock *flow.IncorporatedResult
   115  	previousResult, found := bs.resultForBlock[parentBlock.ID()]
   116  	if !found {
   117  		panic("missing execution result for parent")
   118  	}
   119  	receipt := unittest.ExecutionReceiptFixture(unittest.WithResult(previousResult))
   120  	block.Payload.Receipts = append(block.Payload.Receipts, receipt.Meta())
   121  	block.Payload.Results = append(block.Payload.Results, &receipt.ExecutionResult)
   122  
   123  	incorporatedResultForPrevBlock = unittest.IncorporatedResult.Fixture(
   124  		unittest.IncorporatedResult.WithResult(previousResult),
   125  		unittest.IncorporatedResult.WithIncorporatedBlockID(block.ID()),
   126  	)
   127  
   128  	result := unittest.ExecutionResultFixture(
   129  		unittest.WithBlock(block),
   130  		unittest.WithPreviousResult(*previousResult),
   131  	)
   132  
   133  	bs.resultForBlock[result.BlockID] = result
   134  	bs.resultByID[result.ID()] = result
   135  	bs.receiptsByID[receipt.ID()] = receipt
   136  	bs.receiptsByBlockID[receipt.ExecutionResult.BlockID] = append(bs.receiptsByBlockID[receipt.ExecutionResult.BlockID], receipt)
   137  
   138  	// record block in dbs
   139  	bs.storeBlock(block)
   140  
   141  	if candidateSealForParent {
   142  		// seal the parentBlock block with the result included in this block.
   143  		bs.chainSeal(incorporatedResultForPrevBlock)
   144  	}
   145  
   146  	return block
   147  }
   148  
   149  // Create a seal for the result's block. The corresponding
   150  // IncorporatedResultSeal, which ties the seal to the incorporated result it
   151  // seals, is also recorded for future access.
   152  func (bs *BuilderSuite) chainSeal(incorporatedResult *flow.IncorporatedResult) {
   153  	incorporatedResultSeal := unittest.IncorporatedResultSeal.Fixture(
   154  		unittest.IncorporatedResultSeal.WithResult(incorporatedResult.Result),
   155  		unittest.IncorporatedResultSeal.WithIncorporatedBlockID(incorporatedResult.IncorporatedBlockID),
   156  	)
   157  
   158  	bs.chain = append(bs.chain, incorporatedResultSeal.Seal)
   159  	bs.irsMap[incorporatedResultSeal.ID()] = incorporatedResultSeal
   160  	bs.irsList = append(bs.irsList, incorporatedResultSeal)
   161  }
   162  
   163  // SetupTest constructs the following chain of blocks:
   164  //
   165  //	[first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] <- [A3] <- [parent]
   166  //
   167  // Where block
   168  //   - [first] is sealed and finalized
   169  //   - [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool
   170  //   - [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool
   171  //   - [A3] and [parent] are non-finalized, unsealed blocks _without_ candidate seals
   172  //
   173  // Each block incorporates the result for its immediate parent.
   174  //
   175  // Note: In the happy path, the blocks [A3] and [parent] will not have candidate seal for the following reason:
   176  // For the verifiers to start checking a result R, they need a source of randomness for the block _incorporating_
   177  // result R. The result for block [A3] is incorporated in [parent], which does _not_ have a child yet.
   178  func (bs *BuilderSuite) SetupTest() {
   179  
   180  	// set up no-op dependencies
   181  	noopMetrics := metrics.NewNoopCollector()
   182  	noopTracer := trace.NewNoopTracer()
   183  
   184  	// set up test parameters
   185  	numFinalizedBlocks := 4
   186  	numPendingBlocks := 4
   187  
   188  	// reset test helpers
   189  	bs.pendingBlockIDs = nil
   190  	bs.finalizedBlockIDs = nil
   191  	bs.resultForBlock = make(map[flow.Identifier]*flow.ExecutionResult)
   192  	bs.resultByID = make(map[flow.Identifier]*flow.ExecutionResult)
   193  	bs.receiptsByID = make(map[flow.Identifier]*flow.ExecutionReceipt)
   194  	bs.receiptsByBlockID = make(map[flow.Identifier]flow.ExecutionReceiptList)
   195  
   196  	bs.chain = nil
   197  	bs.irsMap = make(map[flow.Identifier]*flow.IncorporatedResultSeal)
   198  	bs.irsList = nil
   199  
   200  	// initialize the pools
   201  	bs.pendingGuarantees = nil
   202  	bs.pendingSeals = nil
   203  	bs.pendingReceipts = nil
   204  
   205  	// initialise the dbs
   206  	bs.lastSeal = nil
   207  	bs.headers = make(map[flow.Identifier]*flow.Header)
   208  	//bs.heights = make(map[uint64]*flow.Header)
   209  	bs.index = make(map[flow.Identifier]*flow.Index)
   210  	bs.blocks = make(map[flow.Identifier]*flow.Block)
   211  	bs.blockChildren = make(map[flow.Identifier][]flow.Identifier)
   212  
   213  	// initialize behaviour tracking
   214  	bs.assembled = nil
   215  
   216  	// Construct the [first] block:
   217  	first := unittest.BlockFixture()
   218  	bs.storeBlock(&first)
   219  	bs.firstID = first.ID()
   220  	firstResult := unittest.ExecutionResultFixture(unittest.WithBlock(&first))
   221  	bs.lastSeal = unittest.Seal.Fixture(unittest.Seal.WithResult(firstResult))
   222  	bs.resultForBlock[firstResult.BlockID] = firstResult
   223  	bs.resultByID[firstResult.ID()] = firstResult
   224  
   225  	// Construct finalized blocks [F0] ... [F4]
   226  	previous := &first
   227  	for n := 0; n < numFinalizedBlocks; n++ {
   228  		finalized := bs.createAndRecordBlock(previous, n > 0) // Do not construct candidate seal for [first], as it is already sealed
   229  		bs.finalizedBlockIDs = append(bs.finalizedBlockIDs, finalized.ID())
   230  		previous = finalized
   231  	}
   232  
   233  	// Construct the last finalized block [final]
   234  	final := bs.createAndRecordBlock(previous, true)
   235  	bs.finalID = final.ID()
   236  
   237  	// Construct the pending (i.e. unfinalized) ancestors [A0], ..., [A3]
   238  	previous = final
   239  	for n := 0; n < numPendingBlocks; n++ {
   240  		pending := bs.createAndRecordBlock(previous, true)
   241  		bs.pendingBlockIDs = append(bs.pendingBlockIDs, pending.ID())
   242  		previous = pending
   243  	}
   244  
   245  	// Construct [parent] block; but do _not_ add candidate seal for its parent
   246  	parent := bs.createAndRecordBlock(previous, false)
   247  	bs.parentID = parent.ID()
   248  
   249  	// set up temporary database for tests
   250  	bs.db, bs.dir = unittest.TempBadgerDB(bs.T())
   251  
   252  	err := bs.db.Update(operation.InsertFinalizedHeight(final.Header.Height))
   253  	bs.Require().NoError(err)
   254  	err = bs.db.Update(operation.IndexBlockHeight(final.Header.Height, bs.finalID))
   255  	bs.Require().NoError(err)
   256  
   257  	err = bs.db.Update(operation.InsertRootHeight(13))
   258  	bs.Require().NoError(err)
   259  
   260  	err = bs.db.Update(operation.InsertSealedHeight(first.Header.Height))
   261  	bs.Require().NoError(err)
   262  	err = bs.db.Update(operation.IndexBlockHeight(first.Header.Height, first.ID()))
   263  	bs.Require().NoError(err)
   264  
   265  	bs.sentinel = 1337
   266  
   267  	bs.setter = func(header *flow.Header) error {
   268  		header.View = 1337
   269  		return nil
   270  	}
   271  	bs.sign = func(_ *flow.Header) error {
   272  		return nil
   273  	}
   274  
   275  	bs.state = &protocol.ParticipantState{}
   276  	bs.state.On("Extend", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
   277  		block := args.Get(1).(*flow.Block)
   278  		bs.Assert().Equal(bs.sentinel, block.Header.View)
   279  		bs.assembled = block.Payload
   280  	}).Return(nil)
   281  	bs.state.On("Final").Return(func() realproto.Snapshot {
   282  		if block, ok := bs.blocks[bs.finalID]; ok {
   283  			snapshot := unittest.StateSnapshotForKnownBlock(block.Header, nil)
   284  			snapshot.On("Descendants").Return(bs.blockChildren[bs.finalID], nil)
   285  			return snapshot
   286  		}
   287  		return unittest.StateSnapshotForUnknownBlock()
   288  	})
   289  
   290  	// set up storage mocks for tests
   291  	bs.sealDB = &storage.Seals{}
   292  	bs.sealDB.On("HighestInFork", mock.Anything).Return(bs.lastSeal, nil)
   293  
   294  	bs.headerDB = &storage.Headers{}
   295  	bs.headerDB.On("ByBlockID", mock.Anything).Return(
   296  		func(blockID flow.Identifier) *flow.Header {
   297  			return bs.headers[blockID]
   298  		},
   299  		func(blockID flow.Identifier) error {
   300  			_, exists := bs.headers[blockID]
   301  			if !exists {
   302  				return storerr.ErrNotFound
   303  			}
   304  			return nil
   305  		},
   306  	)
   307  
   308  	bs.indexDB = &storage.Index{}
   309  	bs.indexDB.On("ByBlockID", mock.Anything).Return(
   310  		func(blockID flow.Identifier) *flow.Index {
   311  			return bs.index[blockID]
   312  		},
   313  		func(blockID flow.Identifier) error {
   314  			_, exists := bs.index[blockID]
   315  			if !exists {
   316  				return storerr.ErrNotFound
   317  			}
   318  			return nil
   319  		},
   320  	)
   321  
   322  	bs.blockDB = &storage.Blocks{}
   323  	bs.blockDB.On("ByID", mock.Anything).Return(
   324  		func(blockID flow.Identifier) *flow.Block {
   325  			return bs.blocks[blockID]
   326  		},
   327  		func(blockID flow.Identifier) error {
   328  			_, exists := bs.blocks[blockID]
   329  			if !exists {
   330  				return storerr.ErrNotFound
   331  			}
   332  			return nil
   333  		},
   334  	)
   335  
   336  	bs.resultDB = &storage.ExecutionResults{}
   337  	bs.resultDB.On("ByID", mock.Anything).Return(
   338  		func(resultID flow.Identifier) *flow.ExecutionResult {
   339  			return bs.resultByID[resultID]
   340  		},
   341  		func(resultID flow.Identifier) error {
   342  			_, exists := bs.resultByID[resultID]
   343  			if !exists {
   344  				return storerr.ErrNotFound
   345  			}
   346  			return nil
   347  		},
   348  	)
   349  
   350  	bs.receiptsDB = &storage.ExecutionReceipts{}
   351  	bs.receiptsDB.On("ByID", mock.Anything).Return(
   352  		func(receiptID flow.Identifier) *flow.ExecutionReceipt {
   353  			return bs.receiptsByID[receiptID]
   354  		},
   355  		func(receiptID flow.Identifier) error {
   356  			_, exists := bs.receiptsByID[receiptID]
   357  			if !exists {
   358  				return storerr.ErrNotFound
   359  			}
   360  			return nil
   361  		},
   362  	)
   363  	bs.receiptsDB.On("ByBlockID", mock.Anything).Return(
   364  		func(blockID flow.Identifier) flow.ExecutionReceiptList {
   365  			return bs.receiptsByBlockID[blockID]
   366  		},
   367  		func(blockID flow.Identifier) error {
   368  			_, exists := bs.receiptsByBlockID[blockID]
   369  			if !exists {
   370  				return storerr.ErrNotFound
   371  			}
   372  			return nil
   373  		},
   374  	)
   375  
   376  	// set up memory pool mocks for tests
   377  	bs.guarPool = &mempool.Guarantees{}
   378  	bs.guarPool.On("Size").Return(uint(0)) // only used by metrics
   379  	bs.guarPool.On("All").Return(
   380  		func() []*flow.CollectionGuarantee {
   381  			return bs.pendingGuarantees
   382  		},
   383  	)
   384  
   385  	bs.sealPool = &mempool.IncorporatedResultSeals{}
   386  	bs.sealPool.On("Size").Return(uint(0)) // only used by metrics
   387  	bs.sealPool.On("All").Return(
   388  		func() []*flow.IncorporatedResultSeal {
   389  			res := make([]*flow.IncorporatedResultSeal, 0, len(bs.pendingSeals))
   390  			for _, ps := range bs.pendingSeals {
   391  				res = append(res, ps)
   392  			}
   393  			return res
   394  		},
   395  	)
   396  	bs.sealPool.On("ByID", mock.Anything).Return(
   397  		func(id flow.Identifier) *flow.IncorporatedResultSeal {
   398  			return bs.pendingSeals[id]
   399  		},
   400  		func(id flow.Identifier) bool {
   401  			_, exists := bs.pendingSeals[id]
   402  			return exists
   403  		},
   404  	)
   405  
   406  	bs.recPool = &mempool.ExecutionTree{}
   407  	bs.recPool.On("PruneUpToHeight", mock.Anything).Return(nil).Maybe()
   408  	bs.recPool.On("Size").Return(uint(0)).Maybe() // used for metrics only
   409  	bs.recPool.On("AddResult", mock.Anything, mock.Anything).Return(nil).Maybe()
   410  	bs.recPool.On("AddReceipt", mock.Anything, mock.Anything).Return(false, nil).Maybe()
   411  	bs.recPool.On("ReachableReceipts", mock.Anything, mock.Anything, mock.Anything).Return(
   412  		func(resultID flow.Identifier, blockFilter mempoolAPIs.BlockFilter, receiptFilter mempoolAPIs.ReceiptFilter) []*flow.ExecutionReceipt {
   413  			return bs.pendingReceipts
   414  		},
   415  		nil,
   416  	)
   417  
   418  	// setup mock state mutator, we don't need a real once since we are using mocked participant state.
   419  	bs.stateMutator = protocol.NewMutableProtocolState(bs.T())
   420  	bs.stateMutator.On("EvolveState", mock.Anything, mock.Anything, mock.Anything).Return(unittest.IdentifierFixture(), transaction.NewDeferredBlockPersist(), nil)
   421  
   422  	// initialize the builder
   423  	bs.build, err = NewBuilder(
   424  		noopMetrics,
   425  		bs.db,
   426  		bs.state,
   427  		bs.headerDB,
   428  		bs.sealDB,
   429  		bs.indexDB,
   430  		bs.blockDB,
   431  		bs.resultDB,
   432  		bs.receiptsDB,
   433  		bs.stateMutator,
   434  		bs.guarPool,
   435  		bs.sealPool,
   436  		bs.recPool,
   437  		noopTracer,
   438  	)
   439  	require.NoError(bs.T(), err)
   440  
   441  	bs.build.cfg.expiry = 11
   442  }
   443  
   444  func (bs *BuilderSuite) TearDownTest() {
   445  	err := bs.db.Close()
   446  	bs.Assert().NoError(err)
   447  	err = os.RemoveAll(bs.dir)
   448  	bs.Assert().NoError(err)
   449  }
   450  
   451  func (bs *BuilderSuite) TestPayloadEmptyValid() {
   452  
   453  	// we should build an empty block with default setup
   454  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   455  	bs.Require().NoError(err)
   456  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   457  	bs.Assert().Empty(bs.assembled.Seals, "should have no seals in payload with empty mempool")
   458  }
   459  
   460  func (bs *BuilderSuite) TestPayloadGuaranteeValid() {
   461  
   462  	// add sixteen guarantees to the pool
   463  	bs.pendingGuarantees = unittest.CollectionGuaranteesFixture(16, unittest.WithCollRef(bs.finalID))
   464  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   465  	bs.Require().NoError(err)
   466  	bs.Assert().ElementsMatch(bs.pendingGuarantees, bs.assembled.Guarantees, "should have guarantees from mempool in payload")
   467  }
   468  
   469  func (bs *BuilderSuite) TestPayloadGuaranteeDuplicate() {
   470  
   471  	// create some valid guarantees
   472  	valid := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(bs.finalID))
   473  
   474  	forkBlocks := append(bs.finalizedBlockIDs, bs.pendingBlockIDs...)
   475  
   476  	// create some duplicate guarantees and add to random blocks on the fork
   477  	duplicated := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID))
   478  	for _, guarantee := range duplicated {
   479  		blockID := forkBlocks[rand.Intn(len(forkBlocks))]
   480  		index := bs.index[blockID]
   481  		index.CollectionIDs = append(index.CollectionIDs, guarantee.ID())
   482  		bs.index[blockID] = index
   483  	}
   484  
   485  	// add sixteen guarantees to the pool
   486  	bs.pendingGuarantees = append(valid, duplicated...)
   487  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   488  	bs.Require().NoError(err)
   489  	bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid guarantees from mempool in payload")
   490  }
   491  
   492  func (bs *BuilderSuite) TestPayloadGuaranteeReferenceUnknown() {
   493  
   494  	// create 12 valid guarantees
   495  	valid := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID))
   496  
   497  	// create 4 guarantees with unknown reference
   498  	unknown := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(unittest.IdentifierFixture()))
   499  
   500  	// add all guarantees to the pool
   501  	bs.pendingGuarantees = append(valid, unknown...)
   502  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   503  	bs.Require().NoError(err)
   504  	bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid from mempool in payload")
   505  }
   506  
   507  func (bs *BuilderSuite) TestPayloadGuaranteeReferenceExpired() {
   508  
   509  	// create 12 valid guarantees
   510  	valid := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID))
   511  
   512  	// create 4 expired guarantees
   513  	header := unittest.BlockHeaderFixture()
   514  	header.Height = bs.headers[bs.finalID].Height - 12
   515  	bs.headers[header.ID()] = header
   516  	expired := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(header.ID()))
   517  
   518  	// add all guarantees to the pool
   519  	bs.pendingGuarantees = append(valid, expired...)
   520  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   521  	bs.Require().NoError(err)
   522  	bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid from mempool in payload")
   523  }
   524  
   525  // TestPayloadSeals_AllValid checks that builder seals as many blocks as possible (happy path):
   526  //
   527  //	[first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] <- [A3] <- [parent]
   528  //
   529  // Where block
   530  //   - [first] is sealed and finalized
   531  //   - [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool
   532  //   - [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool
   533  //   - [A3] and [parent] are non-finalized, unsealed blocks _without_ candidate seals
   534  //
   535  // Expected behaviour:
   536  //   - builder should include seals [F0], ..., [A4]
   537  //   - note: Block [A3] will not have a seal in the happy path for the following reason:
   538  //     In our example, the result for block A3 is incorporated in block A4. But, for the verifiers to start
   539  //     their work, they need a child block of A4, because the child contains the source of randomness for
   540  //     A4. But we are just constructing this child right now. Hence, the verifiers couldn't have checked
   541  //     the result for A3.
   542  func (bs *BuilderSuite) TestPayloadSeals_AllValid() {
   543  	//	Populate seals mempool with valid chain of seals for blocks [F0], ..., [A2]
   544  	bs.pendingSeals = bs.irsMap
   545  
   546  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   547  	bs.Require().NoError(err)
   548  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   549  	bs.Assert().ElementsMatch(bs.chain, bs.assembled.Seals, "should have included valid chain of seals")
   550  }
   551  
   552  // TestPayloadSeals_Limit verifies that builder does not exceed  maxSealLimit
   553  func (bs *BuilderSuite) TestPayloadSeals_Limit() {
   554  	// use valid chain of seals in mempool
   555  	bs.pendingSeals = bs.irsMap
   556  
   557  	// change maxSealCount to one less than the number of items in the mempool
   558  	limit := uint(2)
   559  	bs.build.cfg.maxSealCount = limit
   560  
   561  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   562  	bs.Require().NoError(err)
   563  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   564  	bs.Assert().Equal(bs.chain[:limit], bs.assembled.Seals, "should have excluded seals above maxSealCount")
   565  }
   566  
   567  // TestPayloadSeals_OnlyFork checks that the builder only includes seals corresponding
   568  // to blocks on the current fork (and _not_ seals for sealable blocks on other forks)
   569  func (bs *BuilderSuite) TestPayloadSeals_OnlyFork() {
   570  	// in the test setup, we already created a single fork
   571  	//    [first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] ..
   572  	// For this test, we add fork:                        ^
   573  	//                                                    └--- [B0] <- [B1] <- ....<- [B6] <- [B7]
   574  	// Where block
   575  	//   * [first] is sealed and finalized
   576  	//   * [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool
   577  	//   * [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool
   578  	forkHead := bs.blocks[bs.finalID]
   579  	for i := 0; i < 8; i++ {
   580  		// Usually, the blocks [B6] and [B7] will not have candidate seal for the following reason:
   581  		// For the verifiers to start checking a result R, they need a source of randomness for the block _incorporating_
   582  		// result R. The result for block [B6] is incorporated in [B7], which does _not_ have a child yet.
   583  		forkHead = bs.createAndRecordBlock(forkHead, i < 6)
   584  	}
   585  
   586  	bs.pendingSeals = bs.irsMap
   587  	_, err := bs.build.BuildOn(forkHead.ID(), bs.setter, bs.sign)
   588  	bs.Require().NoError(err)
   589  
   590  	// expected seals: [F0] <- ... <- [final] <- [B0] <- ... <- [B5]
   591  	// Note: bs.chain contains seals for blocks [F0]...[A2] followed by seals for [final], [B0]...[B5]
   592  	bs.Assert().Equal(10, len(bs.assembled.Seals), "unexpected number of seals")
   593  	bs.Assert().ElementsMatch(bs.chain[:4], bs.assembled.Seals[:4], "should have included only valid chain of seals")
   594  	bs.Assert().ElementsMatch(bs.chain[8:], bs.assembled.Seals[4:], "should have included only valid chain of seals")
   595  
   596  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   597  }
   598  
   599  // TestPayloadSeals_EnforceGap checks that builder leaves a 1-block gap between block incorporating the result
   600  // and the block sealing the result. Without this gap, some nodes might not be able to compute the Verifier
   601  // assignment for the seal and therefore reject the block. This edge case only occurs in a very specific situation:
   602  //
   603  //	                                                                       ┌---- [A5] (orphaned fork)
   604  //	                                                                       v
   605  //	...<- [B0] <- [B1] <- [B2] <- [B3] <- [B4{incorporates result R for B1}] <- ░newBlock░
   606  //
   607  // SCENARIO:
   608  //   - block B0 is sealed
   609  //     Proposer for ░newBlock░ knows block A5. Hence, it knows a QC for block B4, which contains the Source Of Randomness (SOR) for B4.
   610  //     Therefore, the proposer can construct the verifier assignment for [B4{incorporates result R for B1}]
   611  //   - Assume that verification was fast enough, so the proposer has sufficient approvals for result R.
   612  //     Therefore, the proposer has a candidate seal, sealing result R for block B4, in its mempool.
   613  //
   614  // Replica trying to verify ░newBlock░:
   615  //
   616  //   - Assume that the replica does _not_ know A5. Therefore, it _cannot_ compute the verifier assignment for B4.
   617  //
   618  // Problem:  If the proposer included the seal for B1, the replica could not check it.
   619  // Solution: There must be a gap between the block incorporating the result (here B4) and
   620  // the block sealing the result. A gap of one block is sufficient.
   621  //
   622  //	                                                                     ┌---- [A5] (orphaned fork)
   623  //	                                                                     v
   624  //	...<- [B0] <- [B1] <- [B2] <- [B3] <- [B4{incorporates result R for B1}] <- [B5] <- [B6{seals B1}]
   625  //	                                                                           ~~~~~~
   626  //	                                                                             gap
   627  //
   628  // We test the two distinct cases:
   629  //
   630  //	 (i) Builder does _not_ include seal for B1 when constructing block B5
   631  //	(ii) Builder _includes_ seal for B1 when constructing block B6
   632  func (bs *BuilderSuite) TestPayloadSeals_EnforceGap() {
   633  	// we use bs.parentID as block B0
   634  	b0result := bs.resultForBlock[bs.parentID]
   635  	b0seal := unittest.Seal.Fixture(unittest.Seal.WithResult(b0result))
   636  
   637  	// create blocks B1 to B4:
   638  	b1 := bs.createAndRecordBlock(bs.blocks[bs.parentID], true)
   639  	bchain := unittest.ChainFixtureFrom(3, b1.Header) // creates blocks b2, b3, b4
   640  	b4 := bchain[2]
   641  
   642  	// Incorporate result for block B1 into payload of block B4
   643  	resultB1 := bs.resultForBlock[b1.ID()]
   644  	receiptB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultB1))
   645  	b4.SetPayload(
   646  		flow.Payload{
   647  			Results:  []*flow.ExecutionResult{&receiptB1.ExecutionResult},
   648  			Receipts: []*flow.ExecutionReceiptMeta{receiptB1.Meta()},
   649  		})
   650  
   651  	// add blocks B2, B3, B4, A5 to the mocked storage layer (block b0 and b1 are already added):
   652  	a5 := unittest.BlockWithParentFixture(b4.Header)
   653  	for _, b := range append(bchain, a5) {
   654  		bs.storeBlock(b)
   655  	}
   656  
   657  	// mock for of candidate seal mempool:
   658  	bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal)
   659  	b1seal := storeSealForIncorporatedResult(resultB1, b4.ID(), bs.pendingSeals)
   660  
   661  	// mock for seals storage layer:
   662  	bs.sealDB = &storage.Seals{}
   663  	bs.build.seals = bs.sealDB
   664  
   665  	bs.T().Run("Build on top of B4 and check that no seals are included", func(t *testing.T) {
   666  		bs.sealDB.On("HighestInFork", b4.ID()).Return(b0seal, nil)
   667  
   668  		_, err := bs.build.BuildOn(b4.ID(), bs.setter, bs.sign)
   669  		require.NoError(t, err)
   670  		bs.recPool.AssertExpectations(t)
   671  		require.Empty(t, bs.assembled.Seals, "should not include any seals")
   672  	})
   673  
   674  	bs.T().Run("Build on top of B5 and check that seals for B1 is included", func(t *testing.T) {
   675  		b5 := unittest.BlockWithParentFixture(b4.Header) // creating block b5
   676  		bs.storeBlock(b5)
   677  		bs.sealDB.On("HighestInFork", b5.ID()).Return(b0seal, nil)
   678  
   679  		_, err := bs.build.BuildOn(b5.ID(), bs.setter, bs.sign)
   680  		require.NoError(t, err)
   681  		bs.recPool.AssertExpectations(t)
   682  		require.Equal(t, 1, len(bs.assembled.Seals), "only seal for B1 expected")
   683  		require.Equal(t, b1seal.Seal, bs.assembled.Seals[0])
   684  	})
   685  }
   686  
   687  // TestPayloadSeals_Duplicates verifies that the builder does not duplicate seals for already sealed blocks:
   688  //
   689  //	... <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3]
   690  //
   691  // Where block
   692  //   - [F0] ... [F3] sealed blocks but their candidate seals are still included in mempool
   693  //   - [A0] ... [A3] unsealed blocks with candidate seals are included in mempool
   694  //
   695  // Expected behaviour:
   696  //   - builder should only include seals [A0], ..., [A3]
   697  func (bs *BuilderSuite) TestPayloadSeals_Duplicate() {
   698  	//	Pretend that the first n blocks are already sealed
   699  	n := 4
   700  	lastSeal := bs.chain[n-1]
   701  	mockSealDB := &storage.Seals{}
   702  	mockSealDB.On("HighestInFork", mock.Anything).Return(lastSeal, nil)
   703  	bs.build.seals = mockSealDB
   704  
   705  	// seals for all blocks [F0], ..., [A3] are still in the mempool:
   706  	bs.pendingSeals = bs.irsMap
   707  
   708  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   709  	bs.Require().NoError(err)
   710  	bs.Assert().Equal(bs.chain[n:], bs.assembled.Seals, "should have rejected duplicate seals")
   711  }
   712  
   713  // TestPayloadSeals_MissingNextSeal checks how the builder handles the fork
   714  //
   715  //	[S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3]
   716  //
   717  // Where block
   718  //   - [S] is sealed and finalized
   719  //   - [F0] finalized, unsealed block but _without_ candidate seal in mempool
   720  //   - [F1] ... [F3] are finalized, unsealed blocks with candidate seals are included in mempool
   721  //   - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool
   722  //
   723  // Expected behaviour:
   724  //   - builder should not include any seals as the immediately next seal is not in mempool
   725  func (bs *BuilderSuite) TestPayloadSeals_MissingNextSeal() {
   726  	// remove the seal for block [F0]
   727  	firstSeal := bs.irsList[0]
   728  	delete(bs.irsMap, firstSeal.ID())
   729  	bs.pendingSeals = bs.irsMap
   730  
   731  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   732  	bs.Require().NoError(err)
   733  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   734  	bs.Assert().Empty(bs.assembled.Seals, "should not have included any seals from cutoff chain")
   735  }
   736  
   737  // TestPayloadSeals_MissingInterimSeal checks how the builder handles the fork
   738  //
   739  //	[S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3]
   740  //
   741  // Where block
   742  //   - [S] is sealed and finalized
   743  //   - [F0] ... [F2] are finalized, unsealed blocks with candidate seals are included in mempool
   744  //   - [F4] finalized, unsealed block but _without_ candidate seal in mempool
   745  //   - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool
   746  //
   747  // Expected behaviour:
   748  //   - builder should only include candidate seals for [F0], [F1], [F2]
   749  func (bs *BuilderSuite) TestPayloadSeals_MissingInterimSeal() {
   750  	// remove a seal for block [F4]
   751  	seal := bs.irsList[3]
   752  	delete(bs.irsMap, seal.ID())
   753  	bs.pendingSeals = bs.irsMap
   754  
   755  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   756  	bs.Require().NoError(err)
   757  	bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool")
   758  	bs.Assert().ElementsMatch(bs.chain[:3], bs.assembled.Seals, "should have included only beginning of broken chain")
   759  }
   760  
   761  // TestValidatePayloadSeals_ExecutionForks checks how the builder's seal-inclusion logic
   762  // handles execution forks.
   763  //
   764  // we have the chain in storage:
   765  //
   766  //	F <- A{Result[F]_1, Result[F]_2, ReceiptMeta[F]_1, ReceiptMeta[F]_2}
   767  //	      <- B{Result[A]_1, Result[A]_2, ReceiptMeta[A]_1, ReceiptMeta[A]_2}
   768  //	        <- C{Result[B]_1, Result[B]_2, ReceiptMeta[B]_1, ReceiptMeta[B]_2}
   769  //	          <- D{Seal for Result[F]_1}
   770  //
   771  // here F is the latest finalized block (with ID bs.finalID)
   772  //
   773  // Note that we are explicitly testing the handling of an execution fork that
   774  // was incorporated _before_ the seal
   775  //
   776  //	Blocks:      F  <-----------   A    <-----------   B
   777  //	Results:   Result[F]_1  <-  Result[A]_1  <-  Result[B]_1 :: the root of this execution tree is sealed
   778  //	           Result[F]_2  <-  Result[A]_2  <-  Result[B]_2 :: the root of this execution tree conflicts with sealed result
   779  //
   780  // The builder is tasked with creating the payload for block X:
   781  //
   782  //	F <- A{..} <- B{..} <- C{..} <- D{..} <- X
   783  //
   784  // We test the two distinct cases:
   785  //
   786  // (i) verify that execution fork conflicting with sealed result is not sealed
   787  // (ii) verify that multiple execution forks are properly handled
   788  func (bs *BuilderSuite) TestValidatePayloadSeals_ExecutionForks() {
   789  	bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past  [lastSeal]
   790  
   791  	blockF := bs.blocks[bs.finalID]
   792  	blocks := []*flow.Block{blockF}
   793  	blocks = append(blocks, unittest.ChainFixtureFrom(4, blockF.Header)...)              // elements  [F, A, B, C, D]
   794  	receiptChain1 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements  [Result[F]_1, Result[A]_1, Result[B]_1, ...]
   795  	receiptChain2 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements  [Result[F]_2, Result[A]_2, Result[B]_2, ...]
   796  
   797  	for i := 1; i <= 3; i++ { // set payload for blocks A, B, C
   798  		blocks[i].SetPayload(flow.Payload{
   799  			Results:  []*flow.ExecutionResult{&receiptChain1[i-1].ExecutionResult, &receiptChain2[i-1].ExecutionResult},
   800  			Receipts: []*flow.ExecutionReceiptMeta{receiptChain1[i-1].Meta(), receiptChain2[i-1].Meta()},
   801  		})
   802  	}
   803  	sealedResult := receiptChain1[0].ExecutionResult
   804  	sealF := unittest.Seal.Fixture(unittest.Seal.WithResult(&sealedResult))
   805  	blocks[4].SetPayload(flow.Payload{ // set payload for block D
   806  		Seals: []*flow.Seal{sealF},
   807  	})
   808  	for i := 0; i <= 4; i++ {
   809  		// we need to run this several times, as in each iteration as we have _multiple_ execution chains.
   810  		// In each iteration, we only mange to reconnect one additional height
   811  		unittest.ReconnectBlocksAndReceipts(blocks, receiptChain1)
   812  		unittest.ReconnectBlocksAndReceipts(blocks, receiptChain2)
   813  	}
   814  
   815  	for _, b := range blocks {
   816  		bs.storeBlock(b)
   817  	}
   818  	bs.sealDB = &storage.Seals{}
   819  	bs.build.seals = bs.sealDB
   820  	bs.sealDB.On("HighestInFork", mock.Anything).Return(sealF, nil)
   821  	bs.resultByID[sealedResult.ID()] = &sealedResult
   822  
   823  	bs.T().Run("verify that execution fork conflicting with sealed result is not sealed", func(t *testing.T) {
   824  		bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal)
   825  		storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals)
   826  
   827  		_, err := bs.build.BuildOn(blocks[4].ID(), bs.setter, bs.sign)
   828  		require.NoError(t, err)
   829  		require.Empty(t, bs.assembled.Seals, "should not have included seal for conflicting execution fork")
   830  	})
   831  
   832  	bs.T().Run("verify that multiple execution forks are properly handled", func(t *testing.T) {
   833  		bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal)
   834  		sealResultA_1 := storeSealForIncorporatedResult(&receiptChain1[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals)
   835  		sealResultB_1 := storeSealForIncorporatedResult(&receiptChain1[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals)
   836  		storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals)
   837  		storeSealForIncorporatedResult(&receiptChain2[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals)
   838  
   839  		_, err := bs.build.BuildOn(blocks[4].ID(), bs.setter, bs.sign)
   840  		require.NoError(t, err)
   841  		require.ElementsMatch(t, []*flow.Seal{sealResultA_1.Seal, sealResultB_1.Seal}, bs.assembled.Seals, "valid fork should have been sealed")
   842  	})
   843  }
   844  
   845  // TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult tests the receipt selection:
   846  // Expectation: Builder should trigger ExecutionTree to search Execution Tree from
   847  // last sealed result on respective fork.
   848  //
   849  // We test with the following main chain tree
   850  //
   851  //	                                                ┌-[X0] <- [X1{seals ..F4}]
   852  //		                                            v
   853  //	 [lastSeal] <- [F0] <- [F1] <- [F2] <- [F3] <- [F4] <- [A0] <- [A1{seals ..F2}] <- [A2] <- [A3]
   854  //
   855  // Where
   856  // * blocks [lastSeal], [F1], ... [F4], [A0], ... [A4], are created by BuilderSuite
   857  // * latest sealed block for a specific fork is provided by test-local seals storage mock
   858  func (bs *BuilderSuite) TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult() {
   859  	bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past  [lastSeal]
   860  	x0 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true)
   861  	x1 := bs.createAndRecordBlock(x0, true)
   862  
   863  	// set last sealed blocks:
   864  	f2 := bs.blocks[bs.finalizedBlockIDs[2]]
   865  	f2eal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[f2.ID()]))
   866  	f4Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[bs.finalID]))
   867  	bs.sealDB = &storage.Seals{}
   868  	bs.build.seals = bs.sealDB
   869  
   870  	// reset receipts mempool to verify calls made by Builder
   871  	bs.recPool = &mempool.ExecutionTree{}
   872  	bs.recPool.On("Size").Return(uint(0)).Maybe()
   873  	bs.build.recPool = bs.recPool
   874  
   875  	// building on top of X0: latest finalized block in fork is [lastSeal]; expect search to start with sealed result
   876  	bs.sealDB.On("HighestInFork", x0.ID()).Return(bs.lastSeal, nil)
   877  	bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once()
   878  	_, err := bs.build.BuildOn(x0.ID(), bs.setter, bs.sign)
   879  	bs.Require().NoError(err)
   880  	bs.recPool.AssertExpectations(bs.T())
   881  
   882  	// building on top of X1: latest finalized block in fork is [F4]; expect search to start with sealed result
   883  	bs.sealDB.On("HighestInFork", x1.ID()).Return(f4Seal, nil)
   884  	bs.recPool.On("ReachableReceipts", f4Seal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once()
   885  	_, err = bs.build.BuildOn(x1.ID(), bs.setter, bs.sign)
   886  	bs.Require().NoError(err)
   887  	bs.recPool.AssertExpectations(bs.T())
   888  
   889  	// building on top of A3 (with ID bs.parentID): latest finalized block in fork is [F4]; expect search to start with sealed result
   890  	bs.sealDB.On("HighestInFork", bs.parentID).Return(f2eal, nil)
   891  	bs.recPool.On("ReachableReceipts", f2eal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once()
   892  	_, err = bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   893  	bs.Require().NoError(err)
   894  	bs.recPool.AssertExpectations(bs.T())
   895  }
   896  
   897  // TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork tests the receipt selection:
   898  // In this test, we check that the Builder provides a BlockFilter which only allows
   899  // blocks on the fork, which we are extending. We construct the following chain tree:
   900  //
   901  //	    ┌--[X1]   ┌-[Y2]                                             ┌-- [A6]
   902  //	    v         v                                                  v
   903  //	<- [Final] <- [*B1*] <- [*B2*] <- [*B3*] <- [*B4{seals B1}*] <- [*B5*] <- ░newBlock░
   904  //	                          ^
   905  //	                          └-- [C3] <- [C4]
   906  //	                                 ^--- [D4]
   907  //
   908  // Expectation: BlockFilter should pass blocks marked with star: B1, ... ,B5
   909  // All other blocks should be filtered out.
   910  //
   911  // Context:
   912  // While the receipt selection itself is performed by the ExecutionTree, the Builder
   913  // controls the selection by providing suitable BlockFilter and ReceiptFilter.
   914  func (bs *BuilderSuite) TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork() {
   915  	b1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true)
   916  	b2 := bs.createAndRecordBlock(b1, true)
   917  	b3 := bs.createAndRecordBlock(b2, true)
   918  	b4 := bs.createAndRecordBlock(b3, true)
   919  	b5 := bs.createAndRecordBlock(b4, true)
   920  
   921  	x1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true)
   922  	y2 := bs.createAndRecordBlock(b1, true)
   923  	a6 := bs.createAndRecordBlock(b5, true)
   924  
   925  	c3 := bs.createAndRecordBlock(b2, true)
   926  	c4 := bs.createAndRecordBlock(c3, true)
   927  	d4 := bs.createAndRecordBlock(c3, true)
   928  
   929  	// set last sealed blocks:
   930  	b1Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[b1.ID()]))
   931  	bs.sealDB = &storage.Seals{}
   932  	bs.sealDB.On("HighestInFork", b5.ID()).Return(b1Seal, nil)
   933  	bs.build.seals = bs.sealDB
   934  
   935  	// setup mock to test the BlockFilter provided by Builder
   936  	bs.recPool = &mempool.ExecutionTree{}
   937  	bs.recPool.On("Size").Return(uint(0)).Maybe()
   938  	bs.recPool.On("ReachableReceipts", b1Seal.ResultID, mock.Anything, mock.Anything).Run(
   939  		func(args mock.Arguments) {
   940  			blockFilter := args[1].(mempoolAPIs.BlockFilter)
   941  			for _, h := range []*flow.Header{b1.Header, b2.Header, b3.Header, b4.Header, b5.Header} {
   942  				assert.True(bs.T(), blockFilter(h))
   943  			}
   944  			for _, h := range []*flow.Header{bs.blocks[bs.finalID].Header, x1.Header, y2.Header, a6.Header, c3.Header, c4.Header, d4.Header} {
   945  				assert.False(bs.T(), blockFilter(h))
   946  			}
   947  		}).Return([]*flow.ExecutionReceipt{}, nil).Once()
   948  	bs.build.recPool = bs.recPool
   949  
   950  	_, err := bs.build.BuildOn(b5.ID(), bs.setter, bs.sign)
   951  	bs.Require().NoError(err)
   952  	bs.recPool.AssertExpectations(bs.T())
   953  }
   954  
   955  // TestPayloadReceipts_SkipDuplicatedReceipts tests the receipt selection:
   956  // Expectation: we check that the Builder provides a ReceiptFilter which
   957  // filters out duplicated receipts.
   958  // Comment:
   959  // While the receipt selection itself is performed by the ExecutionTree, the Builder
   960  // controls the selection by providing suitable BlockFilter and ReceiptFilter.
   961  func (bs *BuilderSuite) TestPayloadReceipts_SkipDuplicatedReceipts() {
   962  	// setup mock to test the ReceiptFilter provided by Builder
   963  	bs.recPool = &mempool.ExecutionTree{}
   964  	bs.recPool.On("Size").Return(uint(0)).Maybe()
   965  	bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run(
   966  		func(args mock.Arguments) {
   967  			receiptFilter := args[2].(mempoolAPIs.ReceiptFilter)
   968  			// verify that all receipts already included in blocks are filtered out:
   969  			for _, block := range bs.blocks {
   970  				resultByID := block.Payload.Results.Lookup()
   971  				for _, meta := range block.Payload.Receipts {
   972  					result := resultByID[meta.ResultID]
   973  					rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
   974  					assert.False(bs.T(), receiptFilter(rcpt))
   975  				}
   976  			}
   977  			// Verify that receipts for unsealed blocks, which are _not_ already incorporated are accepted:
   978  			for _, block := range bs.blocks {
   979  				if block.ID() != bs.firstID { // block with ID bs.firstID is already sealed
   980  					rcpt := unittest.ReceiptForBlockFixture(block)
   981  					assert.True(bs.T(), receiptFilter(rcpt))
   982  				}
   983  			}
   984  		}).Return([]*flow.ExecutionReceipt{}, nil).Once()
   985  	bs.build.recPool = bs.recPool
   986  
   987  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
   988  	bs.Require().NoError(err)
   989  	bs.recPool.AssertExpectations(bs.T())
   990  }
   991  
   992  // TestPayloadReceipts_SkipReceiptsForSealedBlock tests the receipt selection:
   993  // Expectation: we check that the Builder provides a ReceiptFilter which
   994  // filters out _any_ receipt for the sealed block.
   995  //
   996  // Comment:
   997  // While the receipt selection itself is performed by the ExecutionTree, the Builder
   998  // controls the selection by providing suitable BlockFilter and ReceiptFilter.
   999  func (bs *BuilderSuite) TestPayloadReceipts_SkipReceiptsForSealedBlock() {
  1000  	// setup mock to test the ReceiptFilter provided by Builder
  1001  	bs.recPool = &mempool.ExecutionTree{}
  1002  	bs.recPool.On("Size").Return(uint(0)).Maybe()
  1003  	bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run(
  1004  		func(args mock.Arguments) {
  1005  			receiptFilter := args[2].(mempoolAPIs.ReceiptFilter)
  1006  
  1007  			// receipt for sealed block committing to same result as the sealed result
  1008  			rcpt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.firstID]))
  1009  			assert.False(bs.T(), receiptFilter(rcpt))
  1010  
  1011  			// receipt for sealed block committing to different result as the sealed result
  1012  			rcpt = unittest.ReceiptForBlockFixture(bs.blocks[bs.firstID])
  1013  			assert.False(bs.T(), receiptFilter(rcpt))
  1014  		}).Return([]*flow.ExecutionReceipt{}, nil).Once()
  1015  	bs.build.recPool = bs.recPool
  1016  
  1017  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
  1018  	bs.Require().NoError(err)
  1019  	bs.recPool.AssertExpectations(bs.T())
  1020  }
  1021  
  1022  // TestPayloadReceipts_BlockLimit tests that the builder does not include more
  1023  // receipts than the configured maxReceiptCount.
  1024  func (bs *BuilderSuite) TestPayloadReceipts_BlockLimit() {
  1025  
  1026  	// Populate the mempool with 5 valid receipts
  1027  	receipts := []*flow.ExecutionReceipt{}
  1028  	metas := []*flow.ExecutionReceiptMeta{}
  1029  	expectedResults := []*flow.ExecutionResult{}
  1030  	var i uint64
  1031  	for i = 0; i < 5; i++ {
  1032  		blockOnFork := bs.blocks[bs.irsList[i].Seal.BlockID]
  1033  		pendingReceipt := unittest.ReceiptForBlockFixture(blockOnFork)
  1034  		receipts = append(receipts, pendingReceipt)
  1035  		metas = append(metas, pendingReceipt.Meta())
  1036  		expectedResults = append(expectedResults, &pendingReceipt.ExecutionResult)
  1037  	}
  1038  	bs.pendingReceipts = receipts
  1039  
  1040  	// set maxReceiptCount to 3
  1041  	var limit uint = 3
  1042  	bs.build.cfg.maxReceiptCount = limit
  1043  
  1044  	// ensure that only 3 of the 5 receipts were included
  1045  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
  1046  	bs.Require().NoError(err)
  1047  	bs.Assert().ElementsMatch(metas[:limit], bs.assembled.Receipts, "should have excluded receipts above maxReceiptCount")
  1048  	bs.Assert().ElementsMatch(expectedResults[:limit], bs.assembled.Results, "should have excluded results above maxReceiptCount")
  1049  }
  1050  
  1051  // TestPayloadReceipts_AsProvidedByReceiptForest tests the receipt selection.
  1052  // Expectation: Builder should embed the Receipts as provided by the ExecutionTree
  1053  func (bs *BuilderSuite) TestPayloadReceipts_AsProvidedByReceiptForest() {
  1054  	var expectedReceipts []*flow.ExecutionReceipt
  1055  	var expectedMetas []*flow.ExecutionReceiptMeta
  1056  	var expectedResults []*flow.ExecutionResult
  1057  	for i := 0; i < 10; i++ {
  1058  		expectedReceipts = append(expectedReceipts, unittest.ExecutionReceiptFixture())
  1059  		expectedMetas = append(expectedMetas, expectedReceipts[i].Meta())
  1060  		expectedResults = append(expectedResults, &expectedReceipts[i].ExecutionResult)
  1061  	}
  1062  	bs.recPool = &mempool.ExecutionTree{}
  1063  	bs.recPool.On("Size").Return(uint(0)).Maybe()
  1064  	bs.recPool.On("AddResult", mock.Anything, mock.Anything).Return(nil).Maybe()
  1065  	bs.recPool.On("ReachableReceipts", mock.Anything, mock.Anything, mock.Anything).Return(expectedReceipts, nil).Once()
  1066  	bs.build.recPool = bs.recPool
  1067  
  1068  	_, err := bs.build.BuildOn(bs.parentID, bs.setter, bs.sign)
  1069  	bs.Require().NoError(err)
  1070  	bs.Assert().ElementsMatch(expectedMetas, bs.assembled.Receipts, "should include receipts as returned by ExecutionTree")
  1071  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "should include results as returned by ExecutionTree")
  1072  	bs.recPool.AssertExpectations(bs.T())
  1073  }
  1074  
  1075  // TestIntegration_PayloadReceiptNoParentResult is a mini-integration test combining the
  1076  // Builder with a full ExecutionTree mempool. We check that the builder does not include
  1077  // receipts whose PreviousResult is not already incorporated in the chain.
  1078  //
  1079  // Here we create 4 consecutive blocks S, A, B, and C, where A contains a valid
  1080  // receipt for block S, but blocks B and C have empty payloads.
  1081  //
  1082  // We populate the mempool with valid receipts for blocks A, and C, but NOT for
  1083  // block B.
  1084  //
  1085  // The expected behaviour is that the builder should not include the receipt for
  1086  // block C, because the chain and the mempool do not contain a valid receipt for
  1087  // the parent result (block B's result).
  1088  //
  1089  // ... <- S[ER{parent}] <- A[ER{S}] <- B <- C <- X (candidate)
  1090  func (bs *BuilderSuite) TestIntegration_PayloadReceiptNoParentResult() {
  1091  	// make blocks S, A, B, C
  1092  	parentReceipt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1093  	blockSABC := unittest.ChainFixtureFrom(4, bs.blocks[bs.parentID].Header)
  1094  	resultS := unittest.ExecutionResultFixture(unittest.WithBlock(blockSABC[0]), unittest.WithPreviousResult(*bs.resultForBlock[bs.parentID]))
  1095  	receiptSABC := unittest.ReceiptChainFor(blockSABC, resultS)
  1096  	blockSABC[0].Payload.Receipts = []*flow.ExecutionReceiptMeta{parentReceipt.Meta()}
  1097  	blockSABC[0].Payload.Results = []*flow.ExecutionResult{&parentReceipt.ExecutionResult}
  1098  	blockSABC[1].Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptSABC[0].Meta()}
  1099  	blockSABC[1].Payload.Results = []*flow.ExecutionResult{&receiptSABC[0].ExecutionResult}
  1100  	blockSABC[2].Payload.Receipts = []*flow.ExecutionReceiptMeta{}
  1101  	blockSABC[3].Payload.Receipts = []*flow.ExecutionReceiptMeta{}
  1102  	unittest.ReconnectBlocksAndReceipts(blockSABC, receiptSABC) // update block header so that blocks are chained together
  1103  
  1104  	bs.storeBlock(blockSABC[0])
  1105  	bs.storeBlock(blockSABC[1])
  1106  	bs.storeBlock(blockSABC[2])
  1107  	bs.storeBlock(blockSABC[3])
  1108  
  1109  	// Instantiate real Execution Tree mempool;
  1110  	bs.build.recPool = mempoolImpl.NewExecutionTree()
  1111  	for _, block := range bs.blocks {
  1112  		resultByID := block.Payload.Results.Lookup()
  1113  		for _, meta := range block.Payload.Receipts {
  1114  			result := resultByID[meta.ResultID]
  1115  			rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
  1116  			_, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header)
  1117  			bs.NoError(err)
  1118  		}
  1119  	}
  1120  	// for receipts _not_ included in blocks, add only receipt for A and C but NOT B
  1121  	_, _ = bs.build.recPool.AddReceipt(receiptSABC[1], blockSABC[1].Header)
  1122  	_, _ = bs.build.recPool.AddReceipt(receiptSABC[3], blockSABC[3].Header)
  1123  
  1124  	_, err := bs.build.BuildOn(blockSABC[3].ID(), bs.setter, bs.sign)
  1125  	bs.Require().NoError(err)
  1126  	expectedReceipts := flow.ExecutionReceiptMetaList{receiptSABC[1].Meta()}
  1127  	expectedResults := flow.ExecutionResultList{&receiptSABC[1].ExecutionResult}
  1128  	bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain only receipt for block a")
  1129  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain only result for block a")
  1130  }
  1131  
  1132  // TestIntegration_ExtendDifferentExecutionPathsOnSameFork tests that the
  1133  // builder includes receipts that form different valid execution paths contained
  1134  // on the current fork.
  1135  //
  1136  //	                                      candidate
  1137  //	P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- X[ER{B}, ER{B}']
  1138  func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnSameFork() {
  1139  
  1140  	// A is a block containing a valid receipt for block P
  1141  	recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1142  	A := unittest.BlockWithParentFixture(bs.headers[bs.parentID])
  1143  	A.SetPayload(flow.Payload{
  1144  		Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()},
  1145  		Results:  []*flow.ExecutionResult{&recP.ExecutionResult},
  1146  	})
  1147  
  1148  	// B is a block containing two valid receipts, with different results, for
  1149  	// block A
  1150  	resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1151  	recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1))
  1152  	resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1153  	recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2))
  1154  	B := unittest.BlockWithParentFixture(A.Header)
  1155  	B.SetPayload(flow.Payload{
  1156  		Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()},
  1157  		Results:  []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult},
  1158  	})
  1159  
  1160  	bs.storeBlock(A)
  1161  	bs.storeBlock(B)
  1162  
  1163  	// Instantiate real Execution Tree mempool;
  1164  	bs.build.recPool = mempoolImpl.NewExecutionTree()
  1165  	for _, block := range bs.blocks {
  1166  		resultByID := block.Payload.Results.Lookup()
  1167  		for _, meta := range block.Payload.Receipts {
  1168  			result := resultByID[meta.ResultID]
  1169  			rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
  1170  			_, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header)
  1171  			bs.NoError(err)
  1172  		}
  1173  	}
  1174  
  1175  	// Create two valid receipts for block B which build on different receipts
  1176  	// for the parent block (A); recB1 builds on top of RecA1, whilst recB2
  1177  	// builds on top of RecA2.
  1178  	resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult))
  1179  	recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1))
  1180  	resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult))
  1181  	recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2))
  1182  
  1183  	// Add recB1 and recB2 to the mempool for inclusion in the next candidate
  1184  	_, _ = bs.build.recPool.AddReceipt(recB1, B.Header)
  1185  	_, _ = bs.build.recPool.AddReceipt(recB2, B.Header)
  1186  
  1187  	_, err := bs.build.BuildOn(B.ID(), bs.setter, bs.sign)
  1188  	bs.Require().NoError(err)
  1189  	expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta()}
  1190  	expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult}
  1191  	bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks")
  1192  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks")
  1193  }
  1194  
  1195  // TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks tests that the
  1196  // builder picks up receipts that were already included in a different fork.
  1197  //
  1198  //	                                   candidate
  1199  //	P <- A[ER{P}] <- B[ER{A}] <- X[ER{A}',ER{B}, ER{B}']
  1200  //	              |
  1201  //	              < ------ C[ER{A}']
  1202  //
  1203  // Where:
  1204  //   - ER{A} and ER{A}' are receipts for block A that don't have the same
  1205  //     result.
  1206  //   - ER{B} is a receipt for B with parent result ER{A}
  1207  //   - ER{B}' is a receipt for B with parent result ER{A}'
  1208  //
  1209  // When buiding on top of B, we expect the candidate payload to contain ER{A}',
  1210  // ER{B}, and ER{B}'
  1211  //
  1212  //	ER{P} <- ER{A}  <- ER{B}
  1213  //	       |
  1214  //	       < ER{A}' <- ER{B}'
  1215  func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks() {
  1216  	// A is a block containing a valid receipt for block P
  1217  	recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1218  	A := unittest.BlockWithParentFixture(bs.headers[bs.parentID])
  1219  	A.SetPayload(flow.Payload{
  1220  		Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()},
  1221  		Results:  []*flow.ExecutionResult{&recP.ExecutionResult},
  1222  	})
  1223  
  1224  	// B is a block that builds on A containing a valid receipt for A
  1225  	resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1226  	recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1))
  1227  	B := unittest.BlockWithParentFixture(A.Header)
  1228  	B.SetPayload(flow.Payload{
  1229  		Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()},
  1230  		Results:  []*flow.ExecutionResult{&recA1.ExecutionResult},
  1231  	})
  1232  
  1233  	// C is another block that builds on A containing a valid receipt for A but
  1234  	// different from the receipt contained in B
  1235  	resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1236  	recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2))
  1237  	C := unittest.BlockWithParentFixture(A.Header)
  1238  	C.SetPayload(flow.Payload{
  1239  		Receipts: []*flow.ExecutionReceiptMeta{recA2.Meta()},
  1240  		Results:  []*flow.ExecutionResult{&recA2.ExecutionResult},
  1241  	})
  1242  
  1243  	bs.storeBlock(A)
  1244  	bs.storeBlock(B)
  1245  	bs.storeBlock(C)
  1246  
  1247  	// Instantiate real Execution Tree mempool;
  1248  	bs.build.recPool = mempoolImpl.NewExecutionTree()
  1249  	for _, block := range bs.blocks {
  1250  		resultByID := block.Payload.Results.Lookup()
  1251  		for _, meta := range block.Payload.Receipts {
  1252  			result := resultByID[meta.ResultID]
  1253  			rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
  1254  			_, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header)
  1255  			bs.NoError(err)
  1256  		}
  1257  	}
  1258  
  1259  	// create and add a receipt for block B which builds on top of recA2, which
  1260  	// is not on the same execution fork
  1261  	resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult))
  1262  	recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1))
  1263  	resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult))
  1264  	recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2))
  1265  
  1266  	_, err := bs.build.recPool.AddReceipt(recB1, B.Header)
  1267  	bs.Require().NoError(err)
  1268  	_, err = bs.build.recPool.AddReceipt(recB2, B.Header)
  1269  	bs.Require().NoError(err)
  1270  
  1271  	_, err = bs.build.BuildOn(B.ID(), bs.setter, bs.sign)
  1272  	bs.Require().NoError(err)
  1273  	expectedReceipts := []*flow.ExecutionReceiptMeta{recA2.Meta(), recB1.Meta(), recB2.Meta()}
  1274  	expectedResults := []*flow.ExecutionResult{&recA2.ExecutionResult, &recB1.ExecutionResult, &recB2.ExecutionResult}
  1275  	bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should extend different execution paths")
  1276  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should extend different execution paths")
  1277  }
  1278  
  1279  // TestIntegration_DuplicateReceipts checks that the builder does not re-include
  1280  // receipts that are already incorporated in blocks on the fork.
  1281  //
  1282  //	P <- A(r_P) <- B(r_A) <- X (candidate)
  1283  func (bs *BuilderSuite) TestIntegration_DuplicateReceipts() {
  1284  	// A is a block containing a valid receipt for block P
  1285  	recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1286  	A := unittest.BlockWithParentFixture(bs.headers[bs.parentID])
  1287  	A.SetPayload(flow.Payload{
  1288  		Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()},
  1289  		Results:  []*flow.ExecutionResult{&recP.ExecutionResult},
  1290  	})
  1291  
  1292  	// B is a block that builds on A containing a valid receipt for A
  1293  	resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1294  	recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1))
  1295  	B := unittest.BlockWithParentFixture(A.Header)
  1296  	B.SetPayload(flow.Payload{
  1297  		Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()},
  1298  		Results:  []*flow.ExecutionResult{&recA1.ExecutionResult},
  1299  	})
  1300  
  1301  	bs.storeBlock(A)
  1302  	bs.storeBlock(B)
  1303  
  1304  	// Instantiate real Execution Tree mempool;
  1305  	bs.build.recPool = mempoolImpl.NewExecutionTree()
  1306  	for _, block := range bs.blocks {
  1307  		resultByID := block.Payload.Results.Lookup()
  1308  		for _, meta := range block.Payload.Receipts {
  1309  			result := resultByID[meta.ResultID]
  1310  			rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
  1311  			_, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header)
  1312  			bs.NoError(err)
  1313  		}
  1314  	}
  1315  
  1316  	_, err := bs.build.BuildOn(B.ID(), bs.setter, bs.sign)
  1317  	bs.Require().NoError(err)
  1318  	expectedReceipts := []*flow.ExecutionReceiptMeta{}
  1319  	expectedResults := []*flow.ExecutionResult{}
  1320  	bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should not include receipts that are already incorporated in the current fork")
  1321  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated")
  1322  }
  1323  
  1324  // TestIntegration_ResultAlreadyIncorporated checks that the builder includes
  1325  // receipts for results that were already incorporated in blocks on the fork.
  1326  //
  1327  //	P <- A(ER[P]) <- X (candidate)
  1328  func (bs *BuilderSuite) TestIntegration_ResultAlreadyIncorporated() {
  1329  	// A is a block containing a valid receipt for block P
  1330  	recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1331  	A := unittest.BlockWithParentFixture(bs.headers[bs.parentID])
  1332  	A.SetPayload(flow.Payload{
  1333  		Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()},
  1334  		Results:  []*flow.ExecutionResult{&recP.ExecutionResult},
  1335  	})
  1336  
  1337  	recP_B := unittest.ExecutionReceiptFixture(unittest.WithResult(&recP.ExecutionResult))
  1338  
  1339  	bs.storeBlock(A)
  1340  
  1341  	// Instantiate real Execution Tree mempool;
  1342  	bs.build.recPool = mempoolImpl.NewExecutionTree()
  1343  	for _, block := range bs.blocks {
  1344  		resultByID := block.Payload.Results.Lookup()
  1345  		for _, meta := range block.Payload.Receipts {
  1346  			result := resultByID[meta.ResultID]
  1347  			rcpt := flow.ExecutionReceiptFromMeta(*meta, *result)
  1348  			_, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header)
  1349  			bs.NoError(err)
  1350  		}
  1351  	}
  1352  
  1353  	_, err := bs.build.recPool.AddReceipt(recP_B, bs.blocks[recP_B.ExecutionResult.BlockID].Header)
  1354  	bs.NoError(err)
  1355  
  1356  	_, err = bs.build.BuildOn(A.ID(), bs.setter, bs.sign)
  1357  	bs.Require().NoError(err)
  1358  	expectedReceipts := []*flow.ExecutionReceiptMeta{recP_B.Meta()}
  1359  	expectedResults := []*flow.ExecutionResult{}
  1360  	bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should include receipt metas for results that were already incorporated")
  1361  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated")
  1362  }
  1363  
  1364  func storeSealForIncorporatedResult(result *flow.ExecutionResult, incorporatingBlockID flow.Identifier, pendingSeals map[flow.Identifier]*flow.IncorporatedResultSeal) *flow.IncorporatedResultSeal {
  1365  	incorporatedResultSeal := unittest.IncorporatedResultSeal.Fixture(
  1366  		unittest.IncorporatedResultSeal.WithResult(result),
  1367  		unittest.IncorporatedResultSeal.WithIncorporatedBlockID(incorporatingBlockID),
  1368  	)
  1369  	pendingSeals[incorporatedResultSeal.ID()] = incorporatedResultSeal
  1370  	return incorporatedResultSeal
  1371  }
  1372  
  1373  // TestIntegration_RepopulateExecutionTreeAtStartup tests that the
  1374  // builder includes receipts for candidate block after fresh start, meaning
  1375  // it will repopulate execution tree in constructor
  1376  //
  1377  //	P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- C <- X[ER{B}, ER{B}', ER{C} ]
  1378  //	       |
  1379  //	   finalized
  1380  func (bs *BuilderSuite) TestIntegration_RepopulateExecutionTreeAtStartup() {
  1381  	// setup initial state
  1382  	// A is a block containing a valid receipt for block P
  1383  	recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID]))
  1384  	A := unittest.BlockWithParentFixture(bs.headers[bs.parentID])
  1385  	A.SetPayload(flow.Payload{
  1386  		Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()},
  1387  		Results:  []*flow.ExecutionResult{&recP.ExecutionResult},
  1388  	})
  1389  
  1390  	// B is a block containing two valid receipts, with different results, for
  1391  	// block A
  1392  	resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1393  	recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1))
  1394  	resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult))
  1395  	recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2))
  1396  	B := unittest.BlockWithParentFixture(A.Header)
  1397  	B.SetPayload(flow.Payload{
  1398  		Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()},
  1399  		Results:  []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult},
  1400  	})
  1401  
  1402  	C := unittest.BlockWithParentFixture(B.Header)
  1403  
  1404  	bs.storeBlock(A)
  1405  	bs.storeBlock(B)
  1406  	bs.storeBlock(C)
  1407  
  1408  	// store execution results
  1409  	for _, block := range []*flow.Block{A, B, C} {
  1410  		// for current block create empty receipts list
  1411  		bs.receiptsByBlockID[block.ID()] = flow.ExecutionReceiptList{}
  1412  
  1413  		for _, result := range block.Payload.Results {
  1414  			bs.resultByID[result.ID()] = result
  1415  		}
  1416  		for _, meta := range block.Payload.Receipts {
  1417  			receipt := flow.ExecutionReceiptFromMeta(*meta, *bs.resultByID[meta.ResultID])
  1418  			bs.receiptsByID[meta.ID()] = receipt
  1419  			bs.receiptsByBlockID[receipt.ExecutionResult.BlockID] = append(bs.receiptsByBlockID[receipt.ExecutionResult.BlockID], receipt)
  1420  		}
  1421  	}
  1422  
  1423  	// mark A as finalized
  1424  	bs.finalID = A.ID()
  1425  
  1426  	// set up no-op dependencies
  1427  	noopMetrics := metrics.NewNoopCollector()
  1428  	noopTracer := trace.NewNoopTracer()
  1429  
  1430  	// Instantiate real Execution Tree mempool;
  1431  	recPool := mempoolImpl.NewExecutionTree()
  1432  
  1433  	// create builder which has to repopulate execution tree
  1434  	var err error
  1435  	bs.build, err = NewBuilder(
  1436  		noopMetrics,
  1437  		bs.db,
  1438  		bs.state,
  1439  		bs.headerDB,
  1440  		bs.sealDB,
  1441  		bs.indexDB,
  1442  		bs.blockDB,
  1443  		bs.resultDB,
  1444  		bs.receiptsDB,
  1445  		bs.stateMutator,
  1446  		bs.guarPool,
  1447  		bs.sealPool,
  1448  		recPool,
  1449  		noopTracer,
  1450  	)
  1451  	require.NoError(bs.T(), err)
  1452  	bs.build.cfg.expiry = 11
  1453  
  1454  	// Create two valid receipts for block B which build on different receipts
  1455  	// for the parent block (A); recB1 builds on top of RecA1, whilst recB2
  1456  	// builds on top of RecA2.
  1457  	resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult))
  1458  	recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1))
  1459  	resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult))
  1460  	recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2))
  1461  	resC := unittest.ExecutionResultFixture(unittest.WithBlock(C), unittest.WithPreviousResult(recB1.ExecutionResult))
  1462  	recC := unittest.ExecutionReceiptFixture(unittest.WithResult(resC))
  1463  
  1464  	// Add recB1 and recB2 to the mempool for inclusion in the next candidate
  1465  	_, _ = bs.build.recPool.AddReceipt(recB1, B.Header)
  1466  	_, _ = bs.build.recPool.AddReceipt(recB2, B.Header)
  1467  	_, _ = bs.build.recPool.AddReceipt(recC, C.Header)
  1468  
  1469  	_, err = bs.build.BuildOn(C.ID(), bs.setter, bs.sign)
  1470  	bs.Require().NoError(err)
  1471  	expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta(), recC.Meta()}
  1472  	expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult, &recC.ExecutionResult}
  1473  	bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks")
  1474  	bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks")
  1475  }