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

     1  package loader_test
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  
     8  	"github.com/golang/mock/gomock"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/engine/execution/ingestion"
    13  	"github.com/onflow/flow-go/engine/execution/ingestion/loader"
    14  	stateMock "github.com/onflow/flow-go/engine/execution/state/mock"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	storageerr "github.com/onflow/flow-go/storage"
    17  	storage "github.com/onflow/flow-go/storage/mocks"
    18  	"github.com/onflow/flow-go/utils/unittest"
    19  	"github.com/onflow/flow-go/utils/unittest/mocks"
    20  )
    21  
    22  var _ ingestion.BlockLoader = (*loader.UnexecutedLoader)(nil)
    23  
    24  // ExecutionState is a mocked version of execution state that
    25  // simulates some of its behavior for testing purpose
    26  type mockExecutionState struct {
    27  	sync.Mutex
    28  	stateMock.ExecutionState
    29  	commits map[flow.Identifier]flow.StateCommitment
    30  }
    31  
    32  func newMockExecutionState(seal *flow.Seal, genesis *flow.Header) *mockExecutionState {
    33  	commits := make(map[flow.Identifier]flow.StateCommitment)
    34  	commits[seal.BlockID] = seal.FinalState
    35  	es := &mockExecutionState{
    36  		commits: commits,
    37  	}
    38  	es.On("GetHighestExecutedBlockID", mock.Anything).Return(genesis.Height, genesis.ID(), nil)
    39  	return es
    40  }
    41  
    42  func (es *mockExecutionState) StateCommitmentByBlockID(
    43  	blockID flow.Identifier,
    44  ) (
    45  	flow.StateCommitment,
    46  	error,
    47  ) {
    48  	es.Lock()
    49  	defer es.Unlock()
    50  	commit, ok := es.commits[blockID]
    51  	if !ok {
    52  		return flow.DummyStateCommitment, storageerr.ErrNotFound
    53  	}
    54  
    55  	return commit, nil
    56  }
    57  
    58  func (es *mockExecutionState) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) {
    59  	es.Lock()
    60  	defer es.Unlock()
    61  	_, ok := es.commits[blockID]
    62  	return ok, nil
    63  }
    64  
    65  func (es *mockExecutionState) ExecuteBlock(t *testing.T, block *flow.Block) {
    66  	parentExecuted, err := es.IsBlockExecuted(
    67  		block.Header.Height,
    68  		block.Header.ParentID)
    69  	require.NoError(t, err)
    70  	require.True(t, parentExecuted, "parent block not executed")
    71  
    72  	es.Lock()
    73  	defer es.Unlock()
    74  	es.commits[block.ID()] = unittest.StateCommitmentFixture()
    75  }
    76  
    77  func logChain(chain []*flow.Block) {
    78  	log := unittest.Logger()
    79  	for i, block := range chain {
    80  		log.Info().Msgf("block %v, height: %v, ID: %v", i, block.Header.Height, block.ID())
    81  	}
    82  }
    83  
    84  func TestLoadingUnexecutedBlocks(t *testing.T) {
    85  	t.Run("only genesis", func(t *testing.T) {
    86  		ps := mocks.NewProtocolState()
    87  
    88  		chain, result, seal := unittest.ChainFixture(0)
    89  		genesis := chain[0]
    90  
    91  		logChain(chain)
    92  
    93  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
    94  
    95  		es := newMockExecutionState(seal, genesis.Header)
    96  		ctrl := gomock.NewController(t)
    97  		headers := storage.NewMockHeaders(ctrl)
    98  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
    99  		log := unittest.Logger()
   100  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   101  
   102  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   103  		require.NoError(t, err)
   104  
   105  		unittest.IDsEqual(t, []flow.Identifier{}, unexecuted)
   106  	})
   107  
   108  	t.Run("no finalized, nor pending unexected", func(t *testing.T) {
   109  		ps := mocks.NewProtocolState()
   110  
   111  		chain, result, seal := unittest.ChainFixture(4)
   112  		genesis, blockA, blockB, blockC, blockD :=
   113  			chain[0], chain[1], chain[2], chain[3], chain[4]
   114  
   115  		logChain(chain)
   116  
   117  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   118  		require.NoError(t, ps.Extend(blockA))
   119  		require.NoError(t, ps.Extend(blockB))
   120  		require.NoError(t, ps.Extend(blockC))
   121  		require.NoError(t, ps.Extend(blockD))
   122  
   123  		es := newMockExecutionState(seal, genesis.Header)
   124  		ctrl := gomock.NewController(t)
   125  		headers := storage.NewMockHeaders(ctrl)
   126  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   127  		headers.EXPECT().ByBlockID(blockA.ID()).Return(blockA.Header, nil)
   128  		headers.EXPECT().ByBlockID(blockB.ID()).Return(blockB.Header, nil)
   129  		headers.EXPECT().ByBlockID(blockC.ID()).Return(blockC.Header, nil)
   130  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   131  		log := unittest.Logger()
   132  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   133  
   134  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   135  		require.NoError(t, err)
   136  
   137  		unittest.IDsEqual(t, []flow.Identifier{blockA.ID(), blockB.ID(), blockC.ID(), blockD.ID()}, unexecuted)
   138  	})
   139  
   140  	t.Run("no finalized, some pending executed", func(t *testing.T) {
   141  		ps := mocks.NewProtocolState()
   142  
   143  		chain, result, seal := unittest.ChainFixture(4)
   144  		genesis, blockA, blockB, blockC, blockD :=
   145  			chain[0], chain[1], chain[2], chain[3], chain[4]
   146  
   147  		logChain(chain)
   148  
   149  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   150  		require.NoError(t, ps.Extend(blockA))
   151  		require.NoError(t, ps.Extend(blockB))
   152  		require.NoError(t, ps.Extend(blockC))
   153  		require.NoError(t, ps.Extend(blockD))
   154  
   155  		es := newMockExecutionState(seal, genesis.Header)
   156  		ctrl := gomock.NewController(t)
   157  		headers := storage.NewMockHeaders(ctrl)
   158  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   159  		headers.EXPECT().ByBlockID(blockA.ID()).Return(blockA.Header, nil)
   160  		headers.EXPECT().ByBlockID(blockB.ID()).Return(blockB.Header, nil)
   161  		headers.EXPECT().ByBlockID(blockC.ID()).Return(blockC.Header, nil)
   162  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   163  
   164  		log := unittest.Logger()
   165  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   166  
   167  		es.ExecuteBlock(t, blockA)
   168  		es.ExecuteBlock(t, blockB)
   169  
   170  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   171  		require.NoError(t, err)
   172  
   173  		unittest.IDsEqual(t, []flow.Identifier{blockC.ID(), blockD.ID()}, unexecuted)
   174  	})
   175  
   176  	t.Run("all finalized have been executed, and no pending executed", func(t *testing.T) {
   177  		ps := mocks.NewProtocolState()
   178  
   179  		chain, result, seal := unittest.ChainFixture(4)
   180  		genesis, blockA, blockB, blockC, blockD :=
   181  			chain[0], chain[1], chain[2], chain[3], chain[4]
   182  
   183  		logChain(chain)
   184  
   185  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   186  		require.NoError(t, ps.Extend(blockA))
   187  		require.NoError(t, ps.Extend(blockB))
   188  		require.NoError(t, ps.Extend(blockC))
   189  		require.NoError(t, ps.Extend(blockD))
   190  
   191  		require.NoError(t, ps.Finalize(blockC.ID()))
   192  
   193  		es := newMockExecutionState(seal, genesis.Header)
   194  		ctrl := gomock.NewController(t)
   195  		headers := storage.NewMockHeaders(ctrl)
   196  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   197  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   198  
   199  		log := unittest.Logger()
   200  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   201  
   202  		// block C is the only finalized block, index its header by its height
   203  		headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil)
   204  
   205  		es.ExecuteBlock(t, blockA)
   206  		es.ExecuteBlock(t, blockB)
   207  		es.ExecuteBlock(t, blockC)
   208  
   209  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   210  		require.NoError(t, err)
   211  
   212  		unittest.IDsEqual(t, []flow.Identifier{blockD.ID()}, unexecuted)
   213  	})
   214  
   215  	t.Run("some finalized are executed and conflicting are executed", func(t *testing.T) {
   216  		ps := mocks.NewProtocolState()
   217  
   218  		chain, result, seal := unittest.ChainFixture(4)
   219  		genesis, blockA, blockB, blockC, blockD :=
   220  			chain[0], chain[1], chain[2], chain[3], chain[4]
   221  
   222  		logChain(chain)
   223  
   224  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   225  		require.NoError(t, ps.Extend(blockA))
   226  		require.NoError(t, ps.Extend(blockB))
   227  		require.NoError(t, ps.Extend(blockC))
   228  		require.NoError(t, ps.Extend(blockD))
   229  
   230  		require.NoError(t, ps.Finalize(blockC.ID()))
   231  
   232  		es := newMockExecutionState(seal, genesis.Header)
   233  		ctrl := gomock.NewController(t)
   234  		headers := storage.NewMockHeaders(ctrl)
   235  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   236  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   237  		log := unittest.Logger()
   238  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   239  
   240  		// block C is finalized, index its header by its height
   241  		headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil)
   242  
   243  		es.ExecuteBlock(t, blockA)
   244  		es.ExecuteBlock(t, blockB)
   245  		es.ExecuteBlock(t, blockC)
   246  
   247  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   248  		require.NoError(t, err)
   249  
   250  		unittest.IDsEqual(t, []flow.Identifier{blockD.ID()}, unexecuted)
   251  	})
   252  
   253  	t.Run("all pending executed", func(t *testing.T) {
   254  		ps := mocks.NewProtocolState()
   255  
   256  		chain, result, seal := unittest.ChainFixture(4)
   257  		genesis, blockA, blockB, blockC, blockD :=
   258  			chain[0], chain[1], chain[2], chain[3], chain[4]
   259  
   260  		logChain(chain)
   261  
   262  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   263  		require.NoError(t, ps.Extend(blockA))
   264  		require.NoError(t, ps.Extend(blockB))
   265  		require.NoError(t, ps.Extend(blockC))
   266  		require.NoError(t, ps.Extend(blockD))
   267  		require.NoError(t, ps.Finalize(blockA.ID()))
   268  
   269  		es := newMockExecutionState(seal, genesis.Header)
   270  		ctrl := gomock.NewController(t)
   271  		headers := storage.NewMockHeaders(ctrl)
   272  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   273  		headers.EXPECT().ByBlockID(blockB.ID()).Return(blockB.Header, nil)
   274  		headers.EXPECT().ByBlockID(blockC.ID()).Return(blockC.Header, nil)
   275  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   276  
   277  		log := unittest.Logger()
   278  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   279  
   280  		// block A is finalized, index its header by its height
   281  		headers.EXPECT().BlockIDByHeight(blockA.Header.Height).Return(blockA.Header.ID(), nil)
   282  
   283  		es.ExecuteBlock(t, blockA)
   284  		es.ExecuteBlock(t, blockB)
   285  		es.ExecuteBlock(t, blockC)
   286  		es.ExecuteBlock(t, blockD)
   287  
   288  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   289  		require.NoError(t, err)
   290  
   291  		unittest.IDsEqual(t, []flow.Identifier{}, unexecuted)
   292  	})
   293  
   294  	t.Run("some fork is executed", func(t *testing.T) {
   295  		ps := mocks.NewProtocolState()
   296  
   297  		// Genesis <- A <- B <- C (finalized) <- D <- E <- F
   298  		//                                       ^--- G <- H
   299  		//                      ^-- I
   300  		//						     ^--- J <- K
   301  		chain, result, seal := unittest.ChainFixture(6)
   302  		genesis, blockA, blockB, blockC, blockD, blockE, blockF :=
   303  			chain[0], chain[1], chain[2], chain[3], chain[4], chain[5], chain[6]
   304  
   305  		fork1 := unittest.ChainFixtureFrom(2, blockD.Header)
   306  		blockG, blockH := fork1[0], fork1[1]
   307  
   308  		fork2 := unittest.ChainFixtureFrom(1, blockC.Header)
   309  		blockI := fork2[0]
   310  
   311  		fork3 := unittest.ChainFixtureFrom(2, blockB.Header)
   312  		blockJ, blockK := fork3[0], fork3[1]
   313  
   314  		logChain(chain)
   315  		logChain(fork1)
   316  		logChain(fork2)
   317  		logChain(fork3)
   318  
   319  		require.NoError(t, ps.Bootstrap(genesis, result, seal))
   320  		require.NoError(t, ps.Extend(blockA))
   321  		require.NoError(t, ps.Extend(blockB))
   322  		require.NoError(t, ps.Extend(blockC))
   323  		require.NoError(t, ps.Extend(blockI))
   324  		require.NoError(t, ps.Extend(blockJ))
   325  		require.NoError(t, ps.Extend(blockK))
   326  		require.NoError(t, ps.Extend(blockD))
   327  		require.NoError(t, ps.Extend(blockE))
   328  		require.NoError(t, ps.Extend(blockF))
   329  		require.NoError(t, ps.Extend(blockG))
   330  		require.NoError(t, ps.Extend(blockH))
   331  
   332  		require.NoError(t, ps.Finalize(blockC.ID()))
   333  
   334  		es := newMockExecutionState(seal, genesis.Header)
   335  		ctrl := gomock.NewController(t)
   336  		headers := storage.NewMockHeaders(ctrl)
   337  		headers.EXPECT().ByBlockID(genesis.ID()).Return(genesis.Header, nil)
   338  		headers.EXPECT().ByBlockID(blockD.ID()).Return(blockD.Header, nil)
   339  		headers.EXPECT().ByBlockID(blockE.ID()).Return(blockE.Header, nil)
   340  		headers.EXPECT().ByBlockID(blockF.ID()).Return(blockF.Header, nil)
   341  		headers.EXPECT().ByBlockID(blockG.ID()).Return(blockG.Header, nil)
   342  		headers.EXPECT().ByBlockID(blockH.ID()).Return(blockH.Header, nil)
   343  		headers.EXPECT().ByBlockID(blockI.ID()).Return(blockI.Header, nil)
   344  
   345  		log := unittest.Logger()
   346  		loader := loader.NewUnexecutedLoader(log, ps, headers, es)
   347  
   348  		// block C is finalized, index its header by its height
   349  		headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil)
   350  
   351  		es.ExecuteBlock(t, blockA)
   352  		es.ExecuteBlock(t, blockB)
   353  		es.ExecuteBlock(t, blockC)
   354  		es.ExecuteBlock(t, blockD)
   355  		es.ExecuteBlock(t, blockG)
   356  		es.ExecuteBlock(t, blockJ)
   357  
   358  		unexecuted, err := loader.LoadUnexecuted(context.Background())
   359  		require.NoError(t, err)
   360  
   361  		unittest.IDsEqual(t, []flow.Identifier{
   362  			blockI.ID(), // I is still pending, and unexecuted
   363  			blockE.ID(),
   364  			blockF.ID(),
   365  			// note K is not a pending block, but a conflicting block, even if it's not executed,
   366  			// it won't included
   367  			blockH.ID()},
   368  			unexecuted)
   369  	})
   370  }