github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/ingestion/core_test.go (about)

     1  package ingestion
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  	"github.com/rs/zerolog/log"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	enginePkg "github.com/onflow/flow-go/engine"
    16  	"github.com/onflow/flow-go/engine/execution"
    17  	"github.com/onflow/flow-go/engine/execution/ingestion/mocks"
    18  	"github.com/onflow/flow-go/engine/execution/ingestion/stop"
    19  	stateMock "github.com/onflow/flow-go/engine/execution/state/mock"
    20  	"github.com/onflow/flow-go/engine/execution/testutil"
    21  	"github.com/onflow/flow-go/model/flow"
    22  	"github.com/onflow/flow-go/module/irrecoverable"
    23  	"github.com/onflow/flow-go/module/mempool/entity"
    24  	"github.com/onflow/flow-go/module/metrics"
    25  	storageerr "github.com/onflow/flow-go/storage"
    26  	storage "github.com/onflow/flow-go/storage/mock"
    27  	"github.com/onflow/flow-go/utils/unittest"
    28  	unittestMocks "github.com/onflow/flow-go/utils/unittest/mocks"
    29  )
    30  
    31  func TestInogestionCoreExecuteBlock(t *testing.T) {
    32  	// Given R <- 1 <- 2 (Col0) <- 3 <- 4 (Col1)
    33  	blocks, cols := makeBlocksAndCollections(t)
    34  	// create core
    35  	core, throttle, state, collectionDB, blocksDB, headers, fetcher, consumer :=
    36  		createCore(t, blocks)
    37  
    38  	// start the core
    39  	ctx, cancel := context.WithCancel(context.Background())
    40  	irrecoverableCtx, _ := irrecoverable.WithSignaler(ctx)
    41  	core.Start(irrecoverableCtx)
    42  	<-core.Ready()
    43  	defer func() {
    44  		cancel()
    45  		<-core.Done()
    46  		log.Info().Msgf("done")
    47  	}()
    48  
    49  	waitTime := 10 * time.Millisecond
    50  	// Receive Block1
    51  	// verify Block1 is executed
    52  	wg := &sync.WaitGroup{}
    53  	receiveBlock(t, throttle, state, headers, blocksDB, consumer, blocks[1], wg)
    54  	verifyBlockExecuted(t, consumer, wg, blocks[1])
    55  
    56  	// Receive Block 2 and 3, no block is executed
    57  	receiveBlock(t, throttle, state, headers, blocksDB, consumer, blocks[2], wg)
    58  	time.Sleep(waitTime)
    59  	verifyBlockNotExecuted(t, consumer, blocks[2])
    60  
    61  	receiveBlock(t, throttle, state, headers, blocksDB, consumer, blocks[3], wg)
    62  	time.Sleep(waitTime)
    63  	verifyBlockNotExecuted(t, consumer, blocks[3])
    64  
    65  	// Receive Col0
    66  	// Verify block 2 and 3 are executed
    67  	receiveCollection(t, fetcher, core, cols[0])
    68  	time.Sleep(waitTime)
    69  	verifyBlockExecuted(t, consumer, wg, blocks[2], blocks[3])
    70  
    71  	// Store Col1
    72  	// Receive block 4
    73  	// Verify block 4 is executed because Col1 can be found in local
    74  	storeCollection(t, collectionDB, cols[1])
    75  	receiveBlock(t, throttle, state, headers, blocksDB, consumer, blocks[4], wg)
    76  	verifyBlockExecuted(t, consumer, wg, blocks[4])
    77  }
    78  
    79  func createCore(t *testing.T, blocks []*flow.Block) (
    80  	*Core, Throttle, *unittestMocks.ProtocolState, *mocks.MockCollectionStore,
    81  	*storage.Blocks, *headerStore, *mockFetcher, *mockConsumer) {
    82  	headers := newHeadersWithBlocks(toHeaders(blocks))
    83  	blocksDB := storage.NewBlocks(t)
    84  	collections := mocks.NewMockCollectionStore()
    85  	state := unittestMocks.NewProtocolState()
    86  	require.NoError(t, state.Bootstrap(blocks[0], nil, nil))
    87  	execState := stateMock.NewExecutionState(t)
    88  	execState.On("GetHighestFinalizedExecuted").Return(blocks[0].Header.Height, nil)
    89  
    90  	// root block is executed
    91  	consumer := newMockConsumer(blocks[0].Header.ID())
    92  
    93  	execState.On("StateCommitmentByBlockID", mock.Anything).Return(
    94  		func(blockID flow.Identifier) (flow.StateCommitment, error) {
    95  			executed := consumer.MockIsBlockExecuted(blockID)
    96  			if executed {
    97  				return unittest.StateCommitmentFixture(), nil
    98  			}
    99  			return flow.DummyStateCommitment, storageerr.ErrNotFound
   100  		})
   101  
   102  	execState.On("IsBlockExecuted", mock.Anything, mock.Anything).Return(func(height uint64, blockID flow.Identifier) (bool, error) {
   103  		return consumer.MockIsBlockExecuted(blockID), nil
   104  	})
   105  	execState.On("SaveExecutionResults", mock.Anything, mock.Anything).Return(nil)
   106  
   107  	throttle, err := NewBlockThrottle(unittest.Logger(), state, execState, headers)
   108  	require.NoError(t, err)
   109  
   110  	unit := enginePkg.NewUnit()
   111  	stopControl := stop.NewStopControl(
   112  		unit,
   113  		time.Second,
   114  		zerolog.Nop(),
   115  		execState,
   116  		headers,
   117  		nil,
   118  		nil,
   119  		&flow.Header{Height: 1},
   120  		false,
   121  		false,
   122  	)
   123  	collectionFetcher := newMockFetcher()
   124  	executor := &mockExecutor{t: t, consumer: consumer}
   125  	metrics := metrics.NewNoopCollector()
   126  	core, err := NewCore(unittest.Logger(), throttle, execState, stopControl, blocksDB,
   127  		collections, executor, collectionFetcher, consumer, metrics)
   128  	require.NoError(t, err)
   129  	return core, throttle, state, collections, blocksDB, headers, collectionFetcher, consumer
   130  }
   131  
   132  func makeBlocksAndCollections(t *testing.T) ([]*flow.Block, []*flow.Collection) {
   133  	cs := unittest.CollectionListFixture(2)
   134  	col0, col1 := cs[0], cs[1]
   135  
   136  	genesis := unittest.GenesisFixture()
   137  	blocks := unittest.ChainFixtureFrom(4, genesis.Header)
   138  
   139  	bs := append([]*flow.Block{genesis}, blocks...)
   140  	unittest.AddCollectionsToBlock(bs[2], []*flow.Collection{col0})
   141  	unittest.AddCollectionsToBlock(bs[4], []*flow.Collection{col1})
   142  	unittest.RechainBlocks(bs)
   143  
   144  	return bs, cs
   145  }
   146  
   147  func receiveBlock(t *testing.T, throttle Throttle, state *unittestMocks.ProtocolState, headers *headerStore, blocksDB *storage.Blocks, consumer *mockConsumer, block *flow.Block, wg *sync.WaitGroup) {
   148  	require.NoError(t, state.Extend(block))
   149  	blocksDB.On("ByID", block.ID()).Return(block, nil)
   150  	require.NoError(t, throttle.OnBlock(block.ID(), block.Header.Height))
   151  	consumer.WaitForExecuted(block.ID(), wg)
   152  }
   153  
   154  func verifyBlockExecuted(t *testing.T, consumer *mockConsumer, wg *sync.WaitGroup, blocks ...*flow.Block) {
   155  	// Wait until blocks are executed
   156  	unittest.AssertReturnsBefore(t, func() { wg.Wait() }, time.Millisecond*20)
   157  	for _, block := range blocks {
   158  		require.True(t, consumer.MockIsBlockExecuted(block.ID()))
   159  	}
   160  }
   161  
   162  func verifyBlockNotExecuted(t *testing.T, consumer *mockConsumer, blocks ...*flow.Block) {
   163  	for _, block := range blocks {
   164  		require.False(t, consumer.MockIsBlockExecuted(block.ID()))
   165  	}
   166  }
   167  
   168  func storeCollection(t *testing.T, collectionDB *mocks.MockCollectionStore, collection *flow.Collection) {
   169  	log.Info().Msgf("collectionDB: store collection %v", collection.ID())
   170  	require.NoError(t, collectionDB.Store(collection))
   171  }
   172  
   173  func receiveCollection(t *testing.T, fetcher *mockFetcher, core *Core, collection *flow.Collection) {
   174  	require.True(t, fetcher.IsFetched(collection.ID()))
   175  	core.OnCollection(collection)
   176  }
   177  
   178  type mockExecutor struct {
   179  	t        *testing.T
   180  	consumer *mockConsumer
   181  }
   182  
   183  func (m *mockExecutor) ExecuteBlock(ctx context.Context, block *entity.ExecutableBlock) (*execution.ComputationResult, error) {
   184  	result := testutil.ComputationResultFixture(m.t)
   185  	result.ExecutableBlock = block
   186  	result.ExecutionResult.BlockID = block.ID()
   187  	log.Info().Msgf("mockExecutor: block %v executed", block.Block.Header.Height)
   188  	return result, nil
   189  }
   190  
   191  type mockConsumer struct {
   192  	sync.Mutex
   193  	executed map[flow.Identifier]struct{}
   194  	wgs      map[flow.Identifier]*sync.WaitGroup
   195  }
   196  
   197  func newMockConsumer(executed flow.Identifier) *mockConsumer {
   198  	return &mockConsumer{
   199  		executed: map[flow.Identifier]struct{}{
   200  			executed: {},
   201  		},
   202  		wgs: make(map[flow.Identifier]*sync.WaitGroup),
   203  	}
   204  }
   205  
   206  func (m *mockConsumer) BeforeComputationResultSaved(ctx context.Context, result *execution.ComputationResult) {
   207  }
   208  
   209  func (m *mockConsumer) OnComputationResultSaved(ctx context.Context, result *execution.ComputationResult) string {
   210  	m.Lock()
   211  	defer m.Unlock()
   212  	blockID := result.BlockExecutionResult.ExecutableBlock.ID()
   213  	if _, ok := m.executed[blockID]; ok {
   214  		return fmt.Sprintf("block %v is already executed", blockID)
   215  	}
   216  	m.executed[blockID] = struct{}{}
   217  	log.Info().Uint64("height", result.BlockExecutionResult.ExecutableBlock.Block.Header.Height).Msg("mockConsumer: block result saved")
   218  	m.wgs[blockID].Done()
   219  	return ""
   220  }
   221  
   222  func (m *mockConsumer) WaitForExecuted(blockID flow.Identifier, wg *sync.WaitGroup) {
   223  	m.Lock()
   224  	defer m.Unlock()
   225  	wg.Add(1)
   226  	m.wgs[blockID] = wg
   227  }
   228  
   229  func (m *mockConsumer) MockIsBlockExecuted(id flow.Identifier) bool {
   230  	m.Lock()
   231  	defer m.Unlock()
   232  	_, ok := m.executed[id]
   233  	return ok
   234  }
   235  
   236  type mockFetcher struct {
   237  	sync.Mutex
   238  	fetching map[flow.Identifier]struct{}
   239  }
   240  
   241  func newMockFetcher() *mockFetcher {
   242  	return &mockFetcher{
   243  		fetching: make(map[flow.Identifier]struct{}),
   244  	}
   245  }
   246  
   247  func (f *mockFetcher) FetchCollection(blockID flow.Identifier, height uint64, guarantee *flow.CollectionGuarantee) error {
   248  	f.Lock()
   249  	defer f.Unlock()
   250  
   251  	if _, ok := f.fetching[guarantee.ID()]; ok {
   252  		return fmt.Errorf("collection %v is already fetching", guarantee.ID())
   253  	}
   254  
   255  	f.fetching[guarantee.ID()] = struct{}{}
   256  	log.Info().Msgf("mockFetcher: fetching collection %v for block %v", guarantee.ID(), height)
   257  	return nil
   258  }
   259  
   260  func (f *mockFetcher) Force() {
   261  	f.Lock()
   262  	defer f.Unlock()
   263  }
   264  
   265  func (f *mockFetcher) IsFetched(colID flow.Identifier) bool {
   266  	f.Lock()
   267  	defer f.Unlock()
   268  	_, ok := f.fetching[colID]
   269  	return ok
   270  }