github.com/onflow/flow-go@v0.33.17/engine/execution/ingestion/engine_test.go (about)

     1  package ingestion
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/rs/zerolog"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/onflow/flow-go/crypto"
    16  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    17  
    18  	enginePkg "github.com/onflow/flow-go/engine"
    19  	"github.com/onflow/flow-go/engine/execution"
    20  	computation "github.com/onflow/flow-go/engine/execution/computation/mock"
    21  	"github.com/onflow/flow-go/engine/execution/ingestion/loader"
    22  	"github.com/onflow/flow-go/engine/execution/ingestion/mocks"
    23  	"github.com/onflow/flow-go/engine/execution/ingestion/stop"
    24  	"github.com/onflow/flow-go/engine/execution/ingestion/uploader"
    25  	uploadermock "github.com/onflow/flow-go/engine/execution/ingestion/uploader/mock"
    26  	provider "github.com/onflow/flow-go/engine/execution/provider/mock"
    27  	stateMock "github.com/onflow/flow-go/engine/execution/state/mock"
    28  	"github.com/onflow/flow-go/engine/testutil/mocklocal"
    29  	"github.com/onflow/flow-go/model/flow"
    30  	"github.com/onflow/flow-go/module/mempool/entity"
    31  	"github.com/onflow/flow-go/module/metrics"
    32  	"github.com/onflow/flow-go/module/trace"
    33  	"github.com/onflow/flow-go/network/mocknetwork"
    34  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    35  	storageerr "github.com/onflow/flow-go/storage"
    36  	storage "github.com/onflow/flow-go/storage/mock"
    37  	"github.com/onflow/flow-go/utils/unittest"
    38  )
    39  
    40  type testingContext struct {
    41  	t                  *testing.T
    42  	engine             *Engine
    43  	headers            *storage.Headers
    44  	blocks             *storage.Blocks
    45  	collections        *mocks.MockCollectionStore
    46  	state              *protocol.State
    47  	computationManager *computation.ComputationManager
    48  	providerEngine     *provider.ProviderEngine
    49  	executionState     *stateMock.ExecutionState
    50  	stopControl        *stop.StopControl
    51  	uploadMgr          *uploader.Manager
    52  	fetcher            *mocks.MockFetcher
    53  
    54  	mu *sync.Mutex
    55  }
    56  
    57  func runWithEngine(t *testing.T, f func(testingContext)) {
    58  
    59  	net := new(mocknetwork.EngineRegistry)
    60  
    61  	// generates signing identity including staking key for signing
    62  	seed := make([]byte, crypto.KeyGenSeedMinLen)
    63  	n, err := rand.Read(seed)
    64  	require.Equal(t, n, crypto.KeyGenSeedMinLen)
    65  	require.NoError(t, err)
    66  	sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
    67  	require.NoError(t, err)
    68  	myIdentity := unittest.IdentityFixture()
    69  	myIdentity.Role = flow.RoleExecution
    70  	myIdentity.StakingPubKey = sk.PublicKey()
    71  
    72  	me := mocklocal.NewMockLocal(sk, myIdentity.ID(), t)
    73  
    74  	headers := storage.NewHeaders(t)
    75  	blocks := storage.NewBlocks(t)
    76  	collections := mocks.NewMockCollectionStore()
    77  
    78  	computationManager := computation.NewComputationManager(t)
    79  	providerEngine := provider.NewProviderEngine(t)
    80  	protocolState := protocol.NewState(t)
    81  	executionState := stateMock.NewExecutionState(t)
    82  
    83  	var engine *Engine
    84  
    85  	defer func() {
    86  		<-engine.Done()
    87  		computationManager.AssertExpectations(t)
    88  		protocolState.AssertExpectations(t)
    89  		executionState.AssertExpectations(t)
    90  		providerEngine.AssertExpectations(t)
    91  	}()
    92  
    93  	log := unittest.Logger()
    94  	metrics := metrics.NewNoopCollector()
    95  
    96  	tracer, err := trace.NewTracer(log, "test", "test", trace.SensitivityCaptureAll)
    97  	require.NoError(t, err)
    98  
    99  	unit := enginePkg.NewUnit()
   100  	stopControl := stop.NewStopControl(
   101  		unit,
   102  		time.Second,
   103  		zerolog.Nop(),
   104  		executionState,
   105  		headers,
   106  		nil,
   107  		nil,
   108  		&flow.Header{Height: 1},
   109  		false,
   110  		false,
   111  	)
   112  
   113  	uploadMgr := uploader.NewManager(trace.NewNoopTracer())
   114  
   115  	fetcher := mocks.NewMockFetcher()
   116  	loader := loader.NewUnexecutedLoader(log, protocolState, headers, executionState)
   117  
   118  	engine, err = New(
   119  		unit,
   120  		log,
   121  		net,
   122  		me,
   123  		fetcher,
   124  		headers,
   125  		blocks,
   126  		collections,
   127  		computationManager,
   128  		providerEngine,
   129  		executionState,
   130  		metrics,
   131  		tracer,
   132  		false,
   133  		nil,
   134  		uploadMgr,
   135  		stopControl,
   136  		loader,
   137  	)
   138  	require.NoError(t, err)
   139  
   140  	f(testingContext{
   141  		t:                  t,
   142  		engine:             engine,
   143  		headers:            headers,
   144  		blocks:             blocks,
   145  		collections:        collections,
   146  		state:              protocolState,
   147  		computationManager: computationManager,
   148  		providerEngine:     providerEngine,
   149  		executionState:     executionState,
   150  		uploadMgr:          uploadMgr,
   151  		stopControl:        stopControl,
   152  		fetcher:            fetcher,
   153  
   154  		mu: &sync.Mutex{},
   155  	})
   156  
   157  	<-engine.Done()
   158  }
   159  
   160  // TestExecuteOneBlock verifies after collection is received,
   161  // block is executed, uploaded, and broadcasted
   162  func TestExecuteOneBlock(t *testing.T) {
   163  	runWithEngine(t, func(ctx testingContext) {
   164  		// create a mocked storage that has similar behavior as the real execution state.
   165  		// the mocked storage allows us to prepare results for the prepared blocks, so that
   166  		// the mocked methods know what to return, and it also allows us to verify that the
   167  		// mocked API is called with correct data.
   168  		store := mocks.NewMockBlockStore(t)
   169  
   170  		col := unittest.CollectionFixture(1)
   171  		// Root <- A
   172  		blockA := makeBlockWithCollection(store.RootBlock, &col)
   173  		result := store.CreateBlockAndMockResult(t, blockA)
   174  
   175  		ctx.mockIsBlockExecuted(store)
   176  		ctx.mockStateCommitmentByBlockID(store)
   177  		ctx.mockGetExecutionResultID(store)
   178  		ctx.mockNewStorageSnapshot(result)
   179  
   180  		// receive block
   181  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   182  		require.NoError(t, err)
   183  
   184  		wg := sync.WaitGroup{}
   185  		wg.Add(1) // wait for block A to be executed
   186  
   187  		ctx.mockComputeBlock(store)
   188  		ctx.mockSaveExecutionResults(store, &wg)
   189  
   190  		// verify upload will be called
   191  		uploader := uploadermock.NewUploader(ctx.t)
   192  		uploader.On("Upload", result).Return(nil).Once()
   193  		ctx.uploadMgr.AddUploader(uploader)
   194  
   195  		// verify broadcast will be called
   196  		ctx.providerEngine.On("BroadcastExecutionReceipt",
   197  			mock.Anything,
   198  			blockA.Block.Header.Height,
   199  			result.ExecutionReceipt).Return(true, nil).Once()
   200  
   201  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col)
   202  		require.NoError(t, err)
   203  
   204  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   205  
   206  		// verify collection is fetched
   207  		require.True(t, ctx.fetcher.IsFetched(col.ID()))
   208  
   209  		// verify block is executed
   210  		store.AssertExecuted(t, "A", blockA.ID())
   211  	})
   212  }
   213  
   214  // verify block will be executed if collection is received first
   215  func TestExecuteBlocks(t *testing.T) {
   216  
   217  	runWithEngine(t, func(ctx testingContext) {
   218  		store := mocks.NewMockBlockStore(t)
   219  
   220  		col1 := unittest.CollectionFixture(1)
   221  		col2 := unittest.CollectionFixture(1)
   222  		// Root <- A[C1] <- B[C2]
   223  		// prepare two blocks, so that receiving C2 before C1 won't trigger any block to be executed,
   224  		// which creates the case where C2 collection is received first, and block B will become
   225  		// executable as soon as its parent block A is executed.
   226  		blockA := makeBlockWithCollection(store.RootBlock, &col1)
   227  		blockB := makeBlockWithCollection(blockA.Block.Header, &col2)
   228  		resultA := store.CreateBlockAndMockResult(t, blockA)
   229  		resultB := store.CreateBlockAndMockResult(t, blockB)
   230  
   231  		ctx.mockIsBlockExecuted(store)
   232  		ctx.mockStateCommitmentByBlockID(store)
   233  		ctx.mockGetExecutionResultID(store)
   234  		ctx.mockNewStorageSnapshot(resultA)
   235  		ctx.mockNewStorageSnapshot(resultB)
   236  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   237  
   238  		// receive block
   239  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   240  		require.NoError(t, err)
   241  
   242  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   243  		require.NoError(t, err)
   244  
   245  		ctx.mockComputeBlock(store)
   246  		wg := sync.WaitGroup{}
   247  		wg.Add(2) // wait for 2 blocks to be executed
   248  		ctx.mockSaveExecutionResults(store, &wg)
   249  
   250  		require.NoError(t, ctx.engine.handleCollection(unittest.IdentifierFixture(), &col2))
   251  		require.NoError(t, ctx.engine.handleCollection(unittest.IdentifierFixture(), &col1))
   252  
   253  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   254  
   255  		// verify collection is fetched
   256  		require.True(t, ctx.fetcher.IsFetched(col1.ID()))
   257  		require.True(t, ctx.fetcher.IsFetched(col2.ID()))
   258  
   259  		// verify block is executed
   260  		store.AssertExecuted(t, "A", blockA.ID())
   261  		store.AssertExecuted(t, "B", blockB.ID())
   262  	})
   263  }
   264  
   265  // verify block will be executed if collection is already in storage
   266  func TestExecuteNextBlockIfCollectionIsReady(t *testing.T) {
   267  	runWithEngine(t, func(ctx testingContext) {
   268  		store := mocks.NewMockBlockStore(t)
   269  
   270  		col1 := unittest.CollectionFixture(1)
   271  		col2 := unittest.CollectionFixture(1)
   272  
   273  		// Root <- A[C1] <- B[C2]
   274  		blockA := makeBlockWithCollection(store.RootBlock, &col1)
   275  		blockB := makeBlockWithCollection(blockA.Block.Header, &col2)
   276  		resultA := store.CreateBlockAndMockResult(t, blockA)
   277  		resultB := store.CreateBlockAndMockResult(t, blockB)
   278  
   279  		// C2 is available in storage
   280  		require.NoError(t, ctx.collections.Store(&col2))
   281  
   282  		ctx.mockIsBlockExecuted(store)
   283  		ctx.mockStateCommitmentByBlockID(store)
   284  		ctx.mockGetExecutionResultID(store)
   285  		ctx.mockNewStorageSnapshot(resultA)
   286  		ctx.mockNewStorageSnapshot(resultB)
   287  
   288  		// receiving block A and B will not trigger any execution
   289  		// because A is missing collection C1, B is waiting for A to be executed
   290  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   291  		require.NoError(t, err)
   292  
   293  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   294  		require.NoError(t, err)
   295  
   296  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   297  		ctx.mockComputeBlock(store)
   298  		wg := sync.WaitGroup{}
   299  		wg.Add(2) // waiting for A and B to be executed
   300  		ctx.mockSaveExecutionResults(store, &wg)
   301  
   302  		// receiving collection C1 will execute both A and B
   303  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col1)
   304  		require.NoError(t, err)
   305  
   306  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   307  
   308  		// verify collection is fetched
   309  		require.True(t, ctx.fetcher.IsFetched(col1.ID()))
   310  		require.False(t, ctx.fetcher.IsFetched(col2.ID()))
   311  
   312  		// verify block is executed
   313  		store.AssertExecuted(t, "A", blockA.ID())
   314  		store.AssertExecuted(t, "B", blockB.ID())
   315  	})
   316  }
   317  
   318  // verify block will only be executed once even if block or collection are received multiple times
   319  func TestExecuteBlockOnlyOnce(t *testing.T) {
   320  	runWithEngine(t, func(ctx testingContext) {
   321  		store := mocks.NewMockBlockStore(t)
   322  
   323  		col := unittest.CollectionFixture(1)
   324  		// Root <- A[C]
   325  		blockA := makeBlockWithCollection(store.RootBlock, &col)
   326  		resultA := store.CreateBlockAndMockResult(t, blockA)
   327  
   328  		ctx.mockIsBlockExecuted(store)
   329  		ctx.mockStateCommitmentByBlockID(store)
   330  		ctx.mockGetExecutionResultID(store)
   331  		ctx.mockNewStorageSnapshot(resultA)
   332  
   333  		// receive block
   334  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   335  		require.NoError(t, err)
   336  
   337  		// receive block again before collection is received
   338  		err = ctx.engine.handleBlock(context.Background(), blockA.Block)
   339  		require.NoError(t, err)
   340  
   341  		ctx.mockComputeBlock(store)
   342  		wg := sync.WaitGroup{}
   343  		wg.Add(1) // wait for block A to be executed
   344  		ctx.mockSaveExecutionResults(store, &wg)
   345  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   346  
   347  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col)
   348  		require.NoError(t, err)
   349  
   350  		// receiving collection again before block is executed
   351  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col)
   352  		require.NoError(t, err)
   353  
   354  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   355  
   356  		// receiving collection again after block is executed
   357  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col)
   358  		require.NoError(t, err)
   359  
   360  		// verify collection is fetched
   361  		require.True(t, ctx.fetcher.IsFetched(col.ID()))
   362  
   363  		// verify block is executed
   364  		store.AssertExecuted(t, "A", blockA.ID())
   365  	})
   366  }
   367  
   368  // given two blocks depend on the same root block and contain same collections,
   369  // receiving all collections will trigger the execution of both blocks concurrently
   370  func TestExecuteForkConcurrently(t *testing.T) {
   371  	runWithEngine(t, func(ctx testingContext) {
   372  		store := mocks.NewMockBlockStore(t)
   373  
   374  		// create A and B that have the same collections and same parent
   375  		// Root <- A[C1, C2]
   376  		//      <- B[C1, C2]
   377  		col1 := unittest.CollectionFixture(1)
   378  		col2 := unittest.CollectionFixture(1)
   379  
   380  		blockA := makeBlockWithCollection(store.RootBlock, &col1, &col2)
   381  		blockB := makeBlockWithCollection(store.RootBlock, &col1, &col2)
   382  		resultA := store.CreateBlockAndMockResult(t, blockA)
   383  		resultB := store.CreateBlockAndMockResult(t, blockB)
   384  
   385  		ctx.mockIsBlockExecuted(store)
   386  		ctx.mockStateCommitmentByBlockID(store)
   387  		ctx.mockGetExecutionResultID(store)
   388  		ctx.mockNewStorageSnapshot(resultA)
   389  		ctx.mockNewStorageSnapshot(resultB)
   390  
   391  		// receive blocks
   392  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   393  		require.NoError(t, err)
   394  
   395  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   396  		require.NoError(t, err)
   397  
   398  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col1)
   399  		require.NoError(t, err)
   400  
   401  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   402  		ctx.mockComputeBlock(store)
   403  		wg := sync.WaitGroup{}
   404  		wg.Add(2) // wait for A and B to be executed
   405  		ctx.mockSaveExecutionResults(store, &wg)
   406  
   407  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col2)
   408  		require.NoError(t, err)
   409  
   410  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   411  
   412  		// verify block is executed
   413  		store.AssertExecuted(t, "A", blockA.ID())
   414  		store.AssertExecuted(t, "B", blockB.ID())
   415  	})
   416  }
   417  
   418  // verify block will be executed in order
   419  func TestExecuteBlockInOrder(t *testing.T) {
   420  	runWithEngine(t, func(ctx testingContext) {
   421  		store := mocks.NewMockBlockStore(t)
   422  		// create A and B that have the same collections and same parent
   423  		// Root <- A[C1, C2]
   424  		//      <- B[C2] <- C[C3]
   425  		// verify receiving C3, C1, then C2 will trigger all blocks to be executed
   426  		col1 := unittest.CollectionFixture(1)
   427  		col2 := unittest.CollectionFixture(1)
   428  		col3 := unittest.CollectionFixture(1)
   429  
   430  		blockA := makeBlockWithCollection(store.RootBlock, &col1, &col2)
   431  		blockB := makeBlockWithCollection(store.RootBlock, &col2)
   432  		blockC := makeBlockWithCollection(store.RootBlock, &col3)
   433  		resultA := store.CreateBlockAndMockResult(t, blockA)
   434  		resultB := store.CreateBlockAndMockResult(t, blockB)
   435  		resultC := store.CreateBlockAndMockResult(t, blockC)
   436  
   437  		ctx.mockIsBlockExecuted(store)
   438  		ctx.mockStateCommitmentByBlockID(store)
   439  		ctx.mockGetExecutionResultID(store)
   440  		ctx.mockNewStorageSnapshot(resultA)
   441  		ctx.mockNewStorageSnapshot(resultB)
   442  		ctx.mockNewStorageSnapshot(resultC)
   443  
   444  		// receive blocks
   445  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   446  		require.NoError(t, err)
   447  
   448  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   449  		require.NoError(t, err)
   450  
   451  		err = ctx.engine.handleBlock(context.Background(), blockC.Block)
   452  		require.NoError(t, err)
   453  
   454  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col3)
   455  		require.NoError(t, err)
   456  
   457  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col1)
   458  		require.NoError(t, err)
   459  
   460  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   461  		ctx.mockComputeBlock(store)
   462  		wg := sync.WaitGroup{}
   463  		wg.Add(3) // waiting for A, B, C to be executed
   464  		ctx.mockSaveExecutionResults(store, &wg)
   465  
   466  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col2)
   467  		require.NoError(t, err)
   468  
   469  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   470  
   471  		// verify block is executed
   472  		store.AssertExecuted(t, "A", blockA.ID())
   473  		store.AssertExecuted(t, "B", blockB.ID())
   474  		store.AssertExecuted(t, "C", blockC.ID())
   475  	})
   476  }
   477  
   478  func logBlocks(blocks map[string]*entity.ExecutableBlock) {
   479  	log := unittest.Logger()
   480  	for name, b := range blocks {
   481  		log.Debug().Msgf("creating blocks for testing, block %v's ID:%v", name, b.ID())
   482  	}
   483  }
   484  
   485  // verify that when blocks above the stop height are finalized, they won't
   486  // be executed
   487  func TestStopAtHeightWhenFinalizedBeforeExecuted(t *testing.T) {
   488  	runWithEngine(t, func(ctx testingContext) {
   489  		store := mocks.NewMockBlockStore(t)
   490  
   491  		// this collection is used as trigger of execution
   492  		executionTrigger := unittest.CollectionFixture(1)
   493  		blockA := makeBlockWithCollection(store.RootBlock, &executionTrigger)
   494  		blockB := makeBlockWithCollection(blockA.Block.Header)
   495  		blockC := makeBlockWithCollection(blockB.Block.Header)
   496  		blockD := makeBlockWithCollection(blockC.Block.Header)
   497  
   498  		resultA := store.CreateBlockAndMockResult(t, blockA)
   499  		resultB := store.CreateBlockAndMockResult(t, blockB)
   500  		store.CreateBlockAndMockResult(t, blockC)
   501  		store.CreateBlockAndMockResult(t, blockD)
   502  
   503  		stopHeight := store.RootBlock.Height + 3
   504  		require.Equal(t, stopHeight, blockC.Block.Header.Height) // stop at C (C will not be executed)
   505  		err := ctx.stopControl.SetStopParameters(stop.StopParameters{
   506  			StopBeforeHeight: stopHeight,
   507  		})
   508  		require.NoError(t, err)
   509  
   510  		ctx.mockIsBlockExecuted(store)
   511  		ctx.mockStateCommitmentByBlockID(store)
   512  		ctx.mockGetExecutionResultID(store)
   513  		ctx.mockNewStorageSnapshot(resultA)
   514  		ctx.mockNewStorageSnapshot(resultB)
   515  
   516  		// receive blocks
   517  		err = ctx.engine.handleBlock(context.Background(), blockA.Block)
   518  		require.NoError(t, err)
   519  
   520  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   521  		require.NoError(t, err)
   522  
   523  		err = ctx.engine.handleBlock(context.Background(), blockC.Block)
   524  		require.NoError(t, err)
   525  
   526  		err = ctx.engine.handleBlock(context.Background(), blockD.Block)
   527  		require.NoError(t, err)
   528  
   529  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   530  		ctx.mockComputeBlock(store)
   531  		wg := sync.WaitGroup{}
   532  		wg.Add(2) // only 2 blocks (A, B) will be executed
   533  		ctx.mockSaveExecutionResults(store, &wg)
   534  
   535  		// all blocks finalized
   536  		ctx.stopControl.BlockFinalizedForTesting(blockA.Block.Header)
   537  		ctx.stopControl.BlockFinalizedForTesting(blockB.Block.Header)
   538  		ctx.stopControl.BlockFinalizedForTesting(blockC.Block.Header)
   539  		ctx.stopControl.BlockFinalizedForTesting(blockD.Block.Header)
   540  
   541  		// receiving the colleciton to trigger all blocks to be executed
   542  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &executionTrigger)
   543  		require.NoError(t, err)
   544  
   545  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   546  
   547  		// since stop height is C, verify that only A and B are executed, C and D are not executed
   548  		store.AssertExecuted(t, "A", blockA.ID())
   549  		store.AssertExecuted(t, "B", blockB.ID())
   550  
   551  		store.AssertNotExecuted(t, "C", blockC.ID())
   552  		store.AssertNotExecuted(t, "D", blockD.ID())
   553  	})
   554  }
   555  
   556  // verify that blocks above the stop height won't be executed, even if they are
   557  // later they got finalized
   558  func TestStopAtHeightWhenExecutedBeforeFinalized(t *testing.T) {
   559  	runWithEngine(t, func(ctx testingContext) {
   560  		store := mocks.NewMockBlockStore(t)
   561  
   562  		blockA := makeBlockWithCollection(store.RootBlock)
   563  		blockB := makeBlockWithCollection(blockA.Block.Header)
   564  		blockC := makeBlockWithCollection(blockB.Block.Header)
   565  		blockD := makeBlockWithCollection(blockC.Block.Header)
   566  
   567  		resultA := store.CreateBlockAndMockResult(t, blockA)
   568  		resultB := store.CreateBlockAndMockResult(t, blockB)
   569  		store.CreateBlockAndMockResult(t, blockC)
   570  		store.CreateBlockAndMockResult(t, blockD)
   571  
   572  		stopHeight := store.RootBlock.Height + 3
   573  		require.Equal(t, stopHeight, blockC.Block.Header.Height) // stop at C (C will not be executed)
   574  		err := ctx.stopControl.SetStopParameters(stop.StopParameters{
   575  			StopBeforeHeight: stopHeight,
   576  		})
   577  		require.NoError(t, err)
   578  
   579  		ctx.mockIsBlockExecuted(store)
   580  		ctx.mockStateCommitmentByBlockID(store)
   581  		ctx.mockGetExecutionResultID(store)
   582  		ctx.mockNewStorageSnapshot(resultA)
   583  		ctx.mockNewStorageSnapshot(resultB)
   584  
   585  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   586  		ctx.mockComputeBlock(store)
   587  		wg := sync.WaitGroup{}
   588  		wg.Add(2) // waiting for only A, B to be executed
   589  		ctx.mockSaveExecutionResults(store, &wg)
   590  
   591  		// receive blocks
   592  		err = ctx.engine.handleBlock(context.Background(), blockA.Block)
   593  		require.NoError(t, err)
   594  
   595  		err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   596  		require.NoError(t, err)
   597  
   598  		err = ctx.engine.handleBlock(context.Background(), blockC.Block)
   599  		require.NoError(t, err)
   600  
   601  		err = ctx.engine.handleBlock(context.Background(), blockD.Block)
   602  		require.NoError(t, err)
   603  
   604  		// all blocks finalized
   605  		ctx.stopControl.BlockFinalizedForTesting(blockA.Block.Header)
   606  		ctx.stopControl.BlockFinalizedForTesting(blockB.Block.Header)
   607  		ctx.stopControl.BlockFinalizedForTesting(blockC.Block.Header)
   608  		ctx.stopControl.BlockFinalizedForTesting(blockD.Block.Header)
   609  
   610  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   611  
   612  		// since stop height is C, verify that only A and B are executed, C and D are not executed
   613  		store.AssertExecuted(t, "A", blockA.ID())
   614  		store.AssertExecuted(t, "B", blockB.ID())
   615  
   616  		store.AssertNotExecuted(t, "C", blockC.ID())
   617  		store.AssertNotExecuted(t, "D", blockD.ID())
   618  	})
   619  }
   620  
   621  // verify that when blocks execution and finalization happen concurrently
   622  func TestStopAtHeightWhenExecutionFinalization(t *testing.T) {
   623  	runWithEngine(t, func(ctx testingContext) {
   624  		store := mocks.NewMockBlockStore(t)
   625  
   626  		// Root <- A <- B (stop height, won't execute) <- C
   627  		// verify when executing A and finalizing B happens concurrently,
   628  		// still won't allow B and C to be executed
   629  		blockA := makeBlockWithCollection(store.RootBlock)
   630  		blockB := makeBlockWithCollection(blockA.Block.Header)
   631  		blockC := makeBlockWithCollection(blockB.Block.Header)
   632  
   633  		resultA := store.CreateBlockAndMockResult(t, blockA)
   634  		store.CreateBlockAndMockResult(t, blockB)
   635  		store.CreateBlockAndMockResult(t, blockC)
   636  
   637  		err := ctx.stopControl.SetStopParameters(stop.StopParameters{
   638  			StopBeforeHeight: blockB.Block.Header.Height,
   639  		})
   640  		require.NoError(t, err)
   641  
   642  		ctx.mockIsBlockExecuted(store)
   643  		ctx.mockStateCommitmentByBlockID(store)
   644  		ctx.mockGetExecutionResultID(store)
   645  		ctx.mockNewStorageSnapshot(resultA)
   646  
   647  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   648  		ctx.mockComputeBlock(store)
   649  		wg := sync.WaitGroup{}
   650  		// waiting for:
   651  		// 1. A, B, C to be handled
   652  		// 2. A, B, C to be finalized
   653  		// 3. only A to be executed
   654  		wg.Add(3)
   655  		ctx.mockSaveExecutionResults(store, &wg)
   656  
   657  		// receive blocks
   658  		go func(wg *sync.WaitGroup) {
   659  			err = ctx.engine.handleBlock(context.Background(), blockA.Block)
   660  			require.NoError(t, err)
   661  
   662  			err = ctx.engine.handleBlock(context.Background(), blockB.Block)
   663  			require.NoError(t, err)
   664  
   665  			err = ctx.engine.handleBlock(context.Background(), blockC.Block)
   666  			require.NoError(t, err)
   667  			wg.Done()
   668  		}(&wg)
   669  
   670  		go func(wg *sync.WaitGroup) {
   671  			// all blocks finalized
   672  			ctx.stopControl.BlockFinalizedForTesting(blockA.Block.Header)
   673  			ctx.stopControl.BlockFinalizedForTesting(blockB.Block.Header)
   674  			ctx.stopControl.BlockFinalizedForTesting(blockC.Block.Header)
   675  			wg.Done()
   676  		}(&wg)
   677  
   678  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   679  
   680  		// since stop height is C, verify that only A and B are executed, C and D are not executed
   681  		store.AssertExecuted(t, "A", blockA.ID())
   682  		store.AssertNotExecuted(t, "B", blockB.ID())
   683  		store.AssertNotExecuted(t, "C", blockC.ID())
   684  	})
   685  }
   686  
   687  // TestExecutedBlockUploadedFailureDoesntBlock tests that block processing continues even the
   688  // uploader fails with an error
   689  func TestExecutedBlockUploadedFailureDoesntBlock(t *testing.T) {
   690  	runWithEngine(t, func(ctx testingContext) {
   691  		store := mocks.NewMockBlockStore(t)
   692  
   693  		col := unittest.CollectionFixture(1)
   694  		// Root <- A
   695  		blockA := makeBlockWithCollection(store.RootBlock, &col)
   696  		result := store.CreateBlockAndMockResult(t, blockA)
   697  
   698  		ctx.mockIsBlockExecuted(store)
   699  		ctx.mockStateCommitmentByBlockID(store)
   700  		ctx.mockGetExecutionResultID(store)
   701  		ctx.mockNewStorageSnapshot(result)
   702  
   703  		// receive block
   704  		err := ctx.engine.handleBlock(context.Background(), blockA.Block)
   705  		require.NoError(t, err)
   706  
   707  		ctx.mockComputeBlock(store)
   708  		wg := sync.WaitGroup{}
   709  		wg.Add(1) // wait for block A to be executed
   710  		ctx.mockSaveExecutionResults(store, &wg)
   711  
   712  		// verify upload will fail
   713  		uploader1 := uploadermock.NewUploader(ctx.t)
   714  		uploader1.On("Upload", result).Return(fmt.Errorf("error uploading")).Once()
   715  		ctx.uploadMgr.AddUploader(uploader1)
   716  
   717  		// verify broadcast will be called
   718  		ctx.providerEngine.On("BroadcastExecutionReceipt", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   719  
   720  		err = ctx.engine.handleCollection(unittest.IdentifierFixture(), &col)
   721  		require.NoError(t, err)
   722  
   723  		unittest.AssertReturnsBefore(t, wg.Wait, 10*time.Second)
   724  
   725  		// verify collection is fetched
   726  		require.True(t, ctx.fetcher.IsFetched(col.ID()))
   727  
   728  		// verify block is executed
   729  		store.AssertExecuted(t, "A", blockA.ID())
   730  	})
   731  }
   732  
   733  func makeCollection() (*flow.Collection, *flow.CollectionGuarantee) {
   734  	col := unittest.CollectionFixture(1)
   735  	gua := col.Guarantee()
   736  	return &col, &gua
   737  }
   738  
   739  func makeBlockWithCollection(parent *flow.Header, cols ...*flow.Collection) *entity.ExecutableBlock {
   740  	block := unittest.BlockWithParentFixture(parent)
   741  	completeCollections := make(map[flow.Identifier]*entity.CompleteCollection, len(block.Payload.Guarantees))
   742  	for _, col := range cols {
   743  		g := col.Guarantee()
   744  		block.Payload.Guarantees = append(block.Payload.Guarantees, &g)
   745  
   746  		cc := &entity.CompleteCollection{
   747  			Guarantee:    &g,
   748  			Transactions: col.Transactions,
   749  		}
   750  		completeCollections[col.ID()] = cc
   751  	}
   752  	block.Header.PayloadHash = block.Payload.Hash()
   753  
   754  	executableBlock := &entity.ExecutableBlock{
   755  		Block:               block,
   756  		CompleteCollections: completeCollections,
   757  		StartState:          unittest.StateCommitmentPointerFixture(),
   758  	}
   759  	return executableBlock
   760  }
   761  
   762  func (ctx *testingContext) mockIsBlockExecuted(store *mocks.MockBlockStore) {
   763  	ctx.executionState.On("IsBlockExecuted", mock.Anything, mock.Anything).
   764  		Return(func(height uint64, blockID flow.Identifier) (bool, error) {
   765  			_, err := store.GetExecuted(blockID)
   766  			if err != nil {
   767  				return false, nil
   768  			}
   769  			return true, nil
   770  		})
   771  }
   772  
   773  func (ctx *testingContext) mockStateCommitmentByBlockID(store *mocks.MockBlockStore) {
   774  	ctx.executionState.On("StateCommitmentByBlockID", mock.Anything).
   775  		Return(func(blockID flow.Identifier) (flow.StateCommitment, error) {
   776  			result, err := store.GetExecuted(blockID)
   777  			if err != nil {
   778  				return flow.StateCommitment{}, storageerr.ErrNotFound
   779  			}
   780  			return result.Result.CurrentEndState(), nil
   781  		})
   782  }
   783  
   784  func (ctx *testingContext) mockGetExecutionResultID(store *mocks.MockBlockStore) {
   785  	ctx.executionState.On("GetExecutionResultID", mock.Anything, mock.Anything).
   786  		Return(func(ctx context.Context, blockID flow.Identifier) (flow.Identifier, error) {
   787  			blockResult, err := store.GetExecuted(blockID)
   788  			if err != nil {
   789  				return flow.ZeroID, storageerr.ErrNotFound
   790  			}
   791  
   792  			return blockResult.Result.ExecutionReceipt.ExecutionResult.ID(), nil
   793  		})
   794  }
   795  
   796  func (ctx *testingContext) mockNewStorageSnapshot(result *execution.ComputationResult) {
   797  	// the result is the mocked result for the block, in other words, if the ingestion executes this block,
   798  	// the mocked computationManager will produce this result.
   799  	// so when mocking the StorageSnapshot method, it must be called with the StartState, as well as its
   800  	// parent block, which is used for retrieving the storage state at the end of the parent block.
   801  	ctx.executionState.On("NewStorageSnapshot",
   802  		*result.ExecutableBlock.StartState,
   803  		result.ExecutableBlock.Block.Header.ParentID,
   804  		result.ExecutableBlock.Block.Header.Height-1).Return(nil)
   805  }
   806  
   807  func (ctx *testingContext) mockComputeBlock(store *mocks.MockBlockStore) {
   808  	ctx.computationManager.On("ComputeBlock", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
   809  		Return(func(ctx context.Context,
   810  			parentBlockExecutionResultID flow.Identifier,
   811  			block *entity.ExecutableBlock,
   812  			snapshot snapshot.StorageSnapshot) (
   813  			*execution.ComputationResult, error) {
   814  			blockResult, ok := store.ResultByBlock[block.ID()]
   815  			if !ok {
   816  				return nil, fmt.Errorf("block %s not found", block.ID())
   817  			}
   818  			return blockResult.Result, nil
   819  		})
   820  }
   821  
   822  func (ctx *testingContext) mockSaveExecutionResults(store *mocks.MockBlockStore, wg *sync.WaitGroup) {
   823  	ctx.executionState.On("SaveExecutionResults", mock.Anything, mock.Anything).
   824  		Return(func(ctx context.Context, result *execution.ComputationResult) error {
   825  			defer wg.Done()
   826  			err := store.MarkExecuted(result)
   827  			if err != nil {
   828  				return err
   829  			}
   830  			return nil
   831  		})
   832  }