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