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 }