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 }