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