github.com/koko1123/flow-go-1@v0.29.6/module/state_synchronization/requester/execution_data_requester_test.go (about) 1 package requester_test 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "os" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/dgraph-io/badger/v3" 13 "github.com/ipfs/go-datastore" 14 dssync "github.com/ipfs/go-datastore/sync" 15 "github.com/rs/zerolog" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/mock" 18 "github.com/stretchr/testify/require" 19 "github.com/stretchr/testify/suite" 20 21 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 22 "github.com/koko1123/flow-go-1/consensus/hotstuff/notifications/pubsub" 23 "github.com/koko1123/flow-go-1/model/flow" 24 "github.com/koko1123/flow-go-1/module" 25 "github.com/koko1123/flow-go-1/module/blobs" 26 "github.com/koko1123/flow-go-1/module/executiondatasync/execution_data" 27 exedatamock "github.com/koko1123/flow-go-1/module/executiondatasync/execution_data/mock" 28 "github.com/koko1123/flow-go-1/module/irrecoverable" 29 "github.com/koko1123/flow-go-1/module/metrics" 30 "github.com/koko1123/flow-go-1/module/state_synchronization" 31 "github.com/koko1123/flow-go-1/module/state_synchronization/requester" 32 synctest "github.com/koko1123/flow-go-1/module/state_synchronization/requester/unittest" 33 "github.com/koko1123/flow-go-1/state/protocol" 34 statemock "github.com/koko1123/flow-go-1/state/protocol/mock" 35 bstorage "github.com/koko1123/flow-go-1/storage/badger" 36 "github.com/koko1123/flow-go-1/utils/unittest" 37 ) 38 39 type ExecutionDataRequesterSuite struct { 40 suite.Suite 41 42 blobstore blobs.Blobstore 43 datastore datastore.Batching 44 db *badger.DB 45 downloader *exedatamock.Downloader 46 47 run edTestRun 48 49 mockSnapshot *mockSnapshot 50 } 51 52 func TestExecutionDataRequesterSuite(t *testing.T) { 53 t.Parallel() 54 rand.Seed(time.Now().UnixMilli()) 55 suite.Run(t, new(ExecutionDataRequesterSuite)) 56 } 57 58 func (suite *ExecutionDataRequesterSuite) SetupTest() { 59 suite.datastore = dssync.MutexWrap(datastore.NewMapDatastore()) 60 suite.blobstore = blobs.NewBlobstore(suite.datastore) 61 62 suite.run = edTestRun{ 63 "", 64 100, 65 func(_ int) map[uint64]testExecutionDataCallback { 66 return map[uint64]testExecutionDataCallback{} 67 }, 68 } 69 } 70 71 type testExecutionDataServiceEntry struct { 72 // When set, the response from this call back will be returned for any calls to Get 73 // Note: this callback is called twice by mockery, once for the execution data and once for the error 74 fn testExecutionDataCallback 75 // When set (and fn is unset), this error will be returned for any calls to Get for this ED 76 Err error 77 // Otherwise, the execution data will be returned directly with no error 78 ExecutionData *execution_data.BlockExecutionData 79 } 80 81 type specialBlockGenerator func(int) map[uint64]testExecutionDataCallback 82 type edTestRun struct { 83 name string 84 blockCount int 85 specialBlocks specialBlockGenerator 86 } 87 88 type testExecutionDataCallback func(*execution_data.BlockExecutionData) (*execution_data.BlockExecutionData, error) 89 90 func mockDownloader(edStore map[flow.Identifier]*testExecutionDataServiceEntry) *exedatamock.Downloader { 91 downloader := new(exedatamock.Downloader) 92 93 get := func(id flow.Identifier) (*execution_data.BlockExecutionData, error) { 94 ed, has := edStore[id] 95 96 // return not found 97 if !has { 98 return nil, execution_data.NewBlobNotFoundError(flow.IdToCid(id)) 99 } 100 101 // use a callback. this is useful for injecting a pause or custom error behavior 102 if ed.fn != nil { 103 return ed.fn(ed.ExecutionData) 104 } 105 106 // return a custom error 107 if ed.Err != nil { 108 return nil, ed.Err 109 } 110 111 // return the specific execution data 112 return ed.ExecutionData, nil 113 } 114 115 downloader.On("Download", mock.Anything, mock.AnythingOfType("flow.Identifier")). 116 Return( 117 func(ctx context.Context, id flow.Identifier) *execution_data.BlockExecutionData { 118 ed, _ := get(id) 119 return ed 120 }, 121 func(ctx context.Context, id flow.Identifier) error { 122 _, err := get(id) 123 return err 124 }, 125 ). 126 Maybe() // Maybe() needed to get call count 127 128 noop := module.NoopReadyDoneAware{} 129 downloader.On("Ready"). 130 Return(func() <-chan struct{} { return noop.Ready() }). 131 Maybe() // Maybe() needed to get call count 132 133 return downloader 134 } 135 136 func (suite *ExecutionDataRequesterSuite) mockProtocolState(blocksByHeight map[uint64]*flow.Block) *statemock.State { 137 state := new(statemock.State) 138 139 suite.mockSnapshot = new(mockSnapshot) 140 suite.mockSnapshot.set(blocksByHeight[0].Header, nil) // genesis block 141 142 state.On("Sealed").Return(suite.mockSnapshot).Maybe() 143 return state 144 } 145 146 // TestRequesterProcessesBlocks tests that the requester processes all blocks and sends notifications 147 // in order. 148 func (suite *ExecutionDataRequesterSuite) TestRequesterProcessesBlocks() { 149 150 tests := []edTestRun{ 151 // Test that blocks are processed in order 152 { 153 "happy path", 154 100, 155 func(_ int) map[uint64]testExecutionDataCallback { 156 return map[uint64]testExecutionDataCallback{} 157 }, 158 }, 159 // Tests that blocks that are missed are properly retried and notifications are received in order 160 { 161 "requests blocks with some missed", 162 100, 163 generateBlocksWithSomeMissed, 164 }, 165 // Tests that blocks that are missed are properly retried and backfilled 166 { 167 "requests blocks with some delayed", 168 100, 169 generateBlocksWithRandomDelays, 170 }, 171 } 172 173 for _, run := range tests { 174 suite.Run(run.name, func() { 175 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 176 suite.db = db 177 178 suite.datastore = dssync.MutexWrap(datastore.NewMapDatastore()) 179 suite.blobstore = blobs.NewBlobstore(suite.datastore) 180 181 testData := suite.generateTestData(run.blockCount, run.specialBlocks(run.blockCount)) 182 edr, fd := suite.prepareRequesterTest(testData) 183 fetchedExecutionData := suite.runRequesterTest(edr, fd, testData) 184 185 verifyFetchedExecutionData(suite.T(), fetchedExecutionData, testData) 186 187 suite.T().Log("Shutting down test") 188 }) 189 }) 190 } 191 } 192 193 // TestRequesterResumesAfterRestart tests that the requester will pick up where it left off after a 194 // restart, without skipping any blocks 195 func (suite *ExecutionDataRequesterSuite) TestRequesterResumesAfterRestart() { 196 suite.datastore = dssync.MutexWrap(datastore.NewMapDatastore()) 197 suite.blobstore = blobs.NewBlobstore(suite.datastore) 198 199 testData := suite.generateTestData(suite.run.blockCount, suite.run.specialBlocks(suite.run.blockCount)) 200 201 test := func(stopHeight, resumeHeight uint64) { 202 testData.fetchedExecutionData = nil 203 204 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 205 suite.db = db 206 207 // Process half of the blocks 208 edr, fd := suite.prepareRequesterTest(testData) 209 testData.stopHeight = stopHeight 210 testData.resumeHeight = 0 211 testData.fetchedExecutionData = suite.runRequesterTest(edr, fd, testData) 212 213 // Stand up a new component using the same datastore, and make sure all remaining 214 // blocks are processed 215 edr, fd = suite.prepareRequesterTest(testData) 216 testData.stopHeight = 0 217 testData.resumeHeight = resumeHeight 218 fetchedExecutionData := suite.runRequesterTest(edr, fd, testData) 219 220 verifyFetchedExecutionData(suite.T(), fetchedExecutionData, testData) 221 222 suite.T().Log("Shutting down test") 223 }) 224 } 225 226 suite.Run("requester resumes processing with no gap", func() { 227 stopHeight := testData.startHeight + uint64(suite.run.blockCount)/2 228 resumeHeight := stopHeight + 1 229 test(stopHeight, resumeHeight) 230 }) 231 232 suite.Run("requester resumes processing with gap", func() { 233 stopHeight := testData.startHeight + uint64(suite.run.blockCount)/2 234 resumeHeight := testData.endHeight 235 test(stopHeight, resumeHeight) 236 }) 237 } 238 239 // TestRequesterCatchesUp tests that the requester processes all heights when it starts with a 240 // backlog of sealed blocks. 241 func (suite *ExecutionDataRequesterSuite) TestRequesterCatchesUp() { 242 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 243 suite.db = db 244 245 suite.datastore = dssync.MutexWrap(datastore.NewMapDatastore()) 246 suite.blobstore = blobs.NewBlobstore(suite.datastore) 247 248 testData := suite.generateTestData(suite.run.blockCount, suite.run.specialBlocks(suite.run.blockCount)) 249 250 // start processing with all seals available 251 edr, fd := suite.prepareRequesterTest(testData) 252 testData.resumeHeight = testData.endHeight 253 fetchedExecutionData := suite.runRequesterTest(edr, fd, testData) 254 255 verifyFetchedExecutionData(suite.T(), fetchedExecutionData, testData) 256 257 suite.T().Log("Shutting down test") 258 }) 259 } 260 261 // TestRequesterPausesAndResumes tests that the requester pauses when it downloads maxSearchAhead 262 // blocks beyond the last processed block, and resumes when it catches up. 263 func (suite *ExecutionDataRequesterSuite) TestRequesterPausesAndResumes() { 264 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 265 suite.db = db 266 267 pauseHeight := uint64(10) 268 maxSearchAhead := uint64(5) 269 270 // Downloads will succeed immediately for all blocks except pauseHeight, which will hang 271 // until the resume() is called. 272 generate, resume := generatePauseResume(pauseHeight) 273 274 testData := suite.generateTestData(suite.run.blockCount, generate(suite.run.blockCount)) 275 testData.maxSearchAhead = maxSearchAhead 276 testData.waitTimeout = time.Second * 10 277 278 // calculate the expected number of blocks that should be downloaded before resuming 279 expectedDownloads := maxSearchAhead + (pauseHeight-1)*2 280 281 edr, fd := suite.prepareRequesterTest(testData) 282 fetchedExecutionData := suite.runRequesterTestPauseResume(edr, fd, testData, int(expectedDownloads), resume) 283 284 verifyFetchedExecutionData(suite.T(), fetchedExecutionData, testData) 285 286 suite.T().Log("Shutting down test") 287 }) 288 } 289 290 // TestRequesterHalts tests that the requester handles halting correctly when it encounters an 291 // invalid block 292 func (suite *ExecutionDataRequesterSuite) TestRequesterHalts() { 293 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 294 suite.db = db 295 296 suite.run.blockCount = 10 297 suite.datastore = dssync.MutexWrap(datastore.NewMapDatastore()) 298 suite.blobstore = blobs.NewBlobstore(suite.datastore) 299 300 // generate a block that will return a malformed blob error. causing the requester to halt 301 generate, expectedErr := generateBlocksWithHaltingError(suite.run.blockCount) 302 testData := suite.generateTestData(suite.run.blockCount, generate(suite.run.blockCount)) 303 304 // start processing with all seals available 305 edr, finalizationDistributor := suite.prepareRequesterTest(testData) 306 testData.resumeHeight = testData.endHeight 307 testData.expectedIrrecoverable = expectedErr 308 fetchedExecutionData := suite.runRequesterTestHalts(edr, finalizationDistributor, testData) 309 assert.Less(suite.T(), len(fetchedExecutionData), testData.sealedCount) 310 311 suite.T().Log("Shutting down test") 312 }) 313 } 314 315 func generateBlocksWithSomeMissed(blockCount int) map[uint64]testExecutionDataCallback { 316 missing := map[uint64]testExecutionDataCallback{} 317 318 // every 5th block fails to download n times before succeeding 319 for i := uint64(0); i < uint64(blockCount); i++ { 320 if i%5 > 0 { 321 continue 322 } 323 324 failures := rand.Intn(3) + 1 325 attempts := 0 326 missing[i] = func(ed *execution_data.BlockExecutionData) (*execution_data.BlockExecutionData, error) { 327 if attempts < failures*2 { // this func is run twice for every attempt by the mock (once for ExecutionData one for errors) 328 attempts++ 329 // This should fail the first n fetch attempts 330 time.Sleep(time.Duration(rand.Intn(25)) * time.Millisecond) 331 return nil, &execution_data.BlobNotFoundError{} 332 } 333 334 return ed, nil 335 } 336 } 337 338 return missing 339 } 340 341 func generateBlocksWithRandomDelays(blockCount int) map[uint64]testExecutionDataCallback { 342 // delay every third block by a random amount 343 delays := map[uint64]testExecutionDataCallback{} 344 for i := uint64(0); i < uint64(blockCount); i++ { 345 if i%5 > 0 { 346 continue 347 } 348 349 delays[i] = func(ed *execution_data.BlockExecutionData) (*execution_data.BlockExecutionData, error) { 350 time.Sleep(time.Duration(rand.Intn(25)) * time.Millisecond) 351 return ed, nil 352 } 353 } 354 355 return delays 356 } 357 358 func generateBlocksWithHaltingError(blockCount int) (specialBlockGenerator, error) { 359 // return a MalformedDataError on the second to last block 360 height := uint64(blockCount - 5) 361 err := fmt.Errorf("halting error: %w", &execution_data.MalformedDataError{}) 362 363 generate := func(int) map[uint64]testExecutionDataCallback { 364 return map[uint64]testExecutionDataCallback{ 365 height: func(ed *execution_data.BlockExecutionData) (*execution_data.BlockExecutionData, error) { 366 return nil, err 367 }, 368 } 369 } 370 return generate, err 371 } 372 373 func generatePauseResume(pauseHeight uint64) (specialBlockGenerator, func()) { 374 pause := make(chan struct{}) 375 376 blocks := map[uint64]testExecutionDataCallback{} 377 blocks[pauseHeight] = func(ed *execution_data.BlockExecutionData) (*execution_data.BlockExecutionData, error) { 378 <-pause 379 return ed, nil 380 } 381 382 generate := func(int) map[uint64]testExecutionDataCallback { return blocks } 383 resume := func() { close(pause) } 384 385 return generate, resume 386 } 387 388 func (suite *ExecutionDataRequesterSuite) prepareRequesterTest(cfg *fetchTestRun) (state_synchronization.ExecutionDataRequester, *pubsub.FinalizationDistributor) { 389 headers := synctest.MockBlockHeaderStorage( 390 synctest.WithByID(cfg.blocksByID), 391 synctest.WithByHeight(cfg.blocksByHeight), 392 ) 393 results := synctest.MockResultsStorage( 394 synctest.WithResultByID(cfg.resultsByID), 395 ) 396 seals := synctest.MockSealsStorage( 397 synctest.WithSealsByBlockID(cfg.sealsByBlockID), 398 ) 399 state := suite.mockProtocolState(cfg.blocksByHeight) 400 401 suite.downloader = mockDownloader(cfg.executionDataEntries) 402 403 finalizationDistributor := pubsub.NewFinalizationDistributor() 404 processedHeight := bstorage.NewConsumerProgress(suite.db, module.ConsumeProgressExecutionDataRequesterBlockHeight) 405 processedNotification := bstorage.NewConsumerProgress(suite.db, module.ConsumeProgressExecutionDataRequesterNotification) 406 407 edr := requester.New( 408 zerolog.New(os.Stdout).With().Timestamp().Logger(), 409 metrics.NewNoopCollector(), 410 suite.downloader, 411 processedHeight, 412 processedNotification, 413 state, 414 headers, 415 results, 416 seals, 417 requester.ExecutionDataConfig{ 418 InitialBlockHeight: cfg.startHeight - 1, 419 MaxSearchAhead: cfg.maxSearchAhead, 420 FetchTimeout: cfg.fetchTimeout, 421 RetryDelay: cfg.retryDelay, 422 MaxRetryDelay: cfg.maxRetryDelay, 423 }, 424 ) 425 426 finalizationDistributor.AddOnBlockFinalizedConsumer(edr.OnBlockFinalized) 427 428 return edr, finalizationDistributor 429 } 430 431 func (suite *ExecutionDataRequesterSuite) runRequesterTestHalts(edr state_synchronization.ExecutionDataRequester, finalizationDistributor *pubsub.FinalizationDistributor, cfg *fetchTestRun) receivedExecutionData { 432 // make sure test helper goroutines are cleaned up 433 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 434 defer cancel() 435 436 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 437 438 testDone := make(chan struct{}) 439 fetchedExecutionData := cfg.FetchedExecutionData() 440 441 // collect all execution data notifications 442 edr.AddOnExecutionDataFetchedConsumer(suite.consumeExecutionDataNotifications(cfg, func() { close(testDone) }, fetchedExecutionData)) 443 444 edr.Start(signalerCtx) 445 unittest.RequireCloseBefore(suite.T(), edr.Ready(), cfg.waitTimeout, "timed out waiting for requester to be ready") 446 447 // Send blocks through finalizationDistributor 448 suite.finalizeBlocks(cfg, finalizationDistributor) 449 450 // testDone should never close because the requester paused 451 unittest.RequireNeverClosedWithin(suite.T(), testDone, 100*time.Millisecond, "finished sending notifications unexpectedly") 452 suite.T().Log("All notifications received") 453 454 cancel() 455 unittest.RequireCloseBefore(suite.T(), edr.Done(), cfg.waitTimeout, "timed out waiting for requester to shutdown") 456 457 return fetchedExecutionData 458 } 459 460 func (suite *ExecutionDataRequesterSuite) runRequesterTestPauseResume(edr state_synchronization.ExecutionDataRequester, finalizationDistributor *pubsub.FinalizationDistributor, cfg *fetchTestRun, expectedDownloads int, resume func()) receivedExecutionData { 461 // make sure test helper goroutines are cleaned up 462 ctx, cancel := context.WithCancel(context.Background()) 463 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 464 465 testDone := make(chan struct{}) 466 fetchedExecutionData := cfg.FetchedExecutionData() 467 468 // collect all execution data notifications 469 edr.AddOnExecutionDataFetchedConsumer(suite.consumeExecutionDataNotifications(cfg, func() { close(testDone) }, fetchedExecutionData)) 470 471 edr.Start(signalerCtx) 472 unittest.RequireCloseBefore(suite.T(), edr.Ready(), cfg.waitTimeout, "timed out waiting for requester to be ready") 473 474 // Send all blocks through finalizationDistributor 475 suite.finalizeBlocks(cfg, finalizationDistributor) 476 477 // requester should pause downloads until resume is called, so testDone should not be closed 478 unittest.RequireNeverClosedWithin(suite.T(), testDone, 500*time.Millisecond, "finished unexpectedly") 479 480 // confirm the expected number of downloads were attempted 481 suite.downloader.AssertNumberOfCalls(suite.T(), "Download", expectedDownloads) 482 483 suite.T().Log("Resuming") 484 resume() 485 486 // Pause until we've received all of the expected notifications 487 unittest.RequireCloseBefore(suite.T(), testDone, cfg.waitTimeout, "timed out waiting for notifications") 488 suite.T().Log("All notifications received") 489 490 cancel() 491 unittest.RequireCloseBefore(suite.T(), edr.Done(), cfg.waitTimeout, "timed out waiting for requester to shutdown") 492 493 return fetchedExecutionData 494 } 495 496 func (suite *ExecutionDataRequesterSuite) runRequesterTest(edr state_synchronization.ExecutionDataRequester, finalizationDistributor *pubsub.FinalizationDistributor, cfg *fetchTestRun) receivedExecutionData { 497 // make sure test helper goroutines are cleaned up 498 ctx, cancel := context.WithCancel(context.Background()) 499 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 500 501 // wait for all notifications 502 testDone := make(chan struct{}) 503 504 fetchedExecutionData := cfg.FetchedExecutionData() 505 506 // collect all execution data notifications 507 edr.AddOnExecutionDataFetchedConsumer(suite.consumeExecutionDataNotifications(cfg, func() { close(testDone) }, fetchedExecutionData)) 508 509 edr.Start(signalerCtx) 510 unittest.RequireCloseBefore(suite.T(), edr.Ready(), cfg.waitTimeout, "timed out waiting for requester to be ready") 511 512 // Send blocks through finalizationDistributor 513 suite.finalizeBlocks(cfg, finalizationDistributor) 514 515 // Pause until we've received all of the expected notifications 516 unittest.RequireCloseBefore(suite.T(), testDone, cfg.waitTimeout, "timed out waiting for notifications") 517 suite.T().Log("All notifications received") 518 519 cancel() 520 unittest.RequireCloseBefore(suite.T(), edr.Done(), cfg.waitTimeout, "timed out waiting for requester to shutdown") 521 522 return fetchedExecutionData 523 } 524 525 func (suite *ExecutionDataRequesterSuite) consumeExecutionDataNotifications(cfg *fetchTestRun, done func(), fetchedExecutionData map[flow.Identifier]*execution_data.BlockExecutionData) func(ed *execution_data.BlockExecutionData) { 526 return func(ed *execution_data.BlockExecutionData) { 527 if _, has := fetchedExecutionData[ed.BlockID]; has { 528 suite.T().Errorf("duplicate execution data for block %s", ed.BlockID) 529 return 530 } 531 532 fetchedExecutionData[ed.BlockID] = ed 533 suite.T().Logf("notified of execution data for block %v height %d (%d/%d)", ed.BlockID, cfg.blocksByID[ed.BlockID].Header.Height, len(fetchedExecutionData), cfg.sealedCount) 534 535 if cfg.IsLastSeal(ed.BlockID) { 536 done() 537 } 538 } 539 } 540 541 func (suite *ExecutionDataRequesterSuite) finalizeBlocks(cfg *fetchTestRun, finalizationDistributor *pubsub.FinalizationDistributor) { 542 for i := cfg.StartHeight(); i <= cfg.endHeight; i++ { 543 b := cfg.blocksByHeight[i] 544 545 suite.T().Log(">>>> Finalizing block", b.ID(), b.Header.Height) 546 547 if len(b.Payload.Seals) > 0 { 548 seal := b.Payload.Seals[0] 549 sealedHeader := cfg.blocksByID[seal.BlockID].Header 550 551 suite.mockSnapshot.set(sealedHeader, nil) 552 suite.T().Log(">>>> Sealing block", sealedHeader.ID(), sealedHeader.Height) 553 } 554 555 finalizationDistributor.OnFinalizedBlock(&model.Block{}) // actual block is unused 556 557 if cfg.stopHeight == i { 558 break 559 } 560 } 561 } 562 563 type receivedExecutionData map[flow.Identifier]*execution_data.BlockExecutionData 564 type fetchTestRun struct { 565 sealedCount int 566 startHeight uint64 567 endHeight uint64 568 blocksByHeight map[uint64]*flow.Block 569 blocksByID map[flow.Identifier]*flow.Block 570 resultsByID map[flow.Identifier]*flow.ExecutionResult 571 resultsByBlockID map[flow.Identifier]*flow.ExecutionResult 572 sealsByBlockID map[flow.Identifier]*flow.Seal 573 executionDataByID map[flow.Identifier]*execution_data.BlockExecutionData 574 executionDataEntries map[flow.Identifier]*testExecutionDataServiceEntry 575 executionDataIDByBlockID map[flow.Identifier]flow.Identifier 576 expectedIrrecoverable error 577 578 stopHeight uint64 579 resumeHeight uint64 580 fetchedExecutionData map[flow.Identifier]*execution_data.BlockExecutionData 581 waitTimeout time.Duration 582 583 maxSearchAhead uint64 584 fetchTimeout time.Duration 585 retryDelay time.Duration 586 maxRetryDelay time.Duration 587 } 588 589 func (r *fetchTestRun) StartHeight() uint64 { 590 if r.resumeHeight > 0 { 591 return r.resumeHeight 592 } 593 return r.startHeight 594 } 595 596 func (r *fetchTestRun) StopHeight() uint64 { 597 if r.stopHeight > 0 { 598 return r.stopHeight 599 } 600 return r.endHeight 601 } 602 603 func (r *fetchTestRun) FetchedExecutionData() receivedExecutionData { 604 if r.fetchedExecutionData == nil { 605 return make(receivedExecutionData, r.sealedCount) 606 } 607 return r.fetchedExecutionData 608 } 609 610 // IsLastSeal returns true if the provided blockID is the last expected sealed block for the test 611 func (r *fetchTestRun) IsLastSeal(blockID flow.Identifier) bool { 612 stopHeight := r.StopHeight() 613 lastSeal := r.blocksByHeight[stopHeight].Payload.Seals[0].BlockID 614 return lastSeal == r.blocksByID[blockID].ID() 615 } 616 617 func (suite *ExecutionDataRequesterSuite) generateTestData(blockCount int, specialHeightFuncs map[uint64]testExecutionDataCallback) *fetchTestRun { 618 edsEntries := map[flow.Identifier]*testExecutionDataServiceEntry{} 619 blocksByHeight := map[uint64]*flow.Block{} 620 blocksByID := map[flow.Identifier]*flow.Block{} 621 resultsByID := map[flow.Identifier]*flow.ExecutionResult{} 622 resultsByBlockID := map[flow.Identifier]*flow.ExecutionResult{} 623 sealsByBlockID := map[flow.Identifier]*flow.Seal{} 624 executionDataByID := map[flow.Identifier]*execution_data.BlockExecutionData{} 625 executionDataIDByBlockID := map[flow.Identifier]flow.Identifier{} 626 627 sealedCount := blockCount - 4 // seals for blocks 1-96 628 firstSeal := blockCount - sealedCount 629 630 // genesis is block 0, we start syncing from block 1 631 startHeight := uint64(1) 632 endHeight := uint64(blockCount) - 1 633 634 // instantiate ExecutionDataService to generate correct CIDs 635 eds := execution_data.NewExecutionDataStore(suite.blobstore, execution_data.DefaultSerializer) 636 637 var previousBlock *flow.Block 638 var previousResult *flow.ExecutionResult 639 for i := 0; i < blockCount; i++ { 640 var seals []*flow.Header 641 642 if i >= firstSeal { 643 sealedBlock := blocksByHeight[uint64(i-firstSeal+1)] 644 seals = []*flow.Header{ 645 sealedBlock.Header, // block 0 doesn't get sealed (it's pre-sealed in the genesis state) 646 } 647 648 sealsByBlockID[sealedBlock.ID()] = unittest.Seal.Fixture( 649 unittest.Seal.WithBlockID(sealedBlock.ID()), 650 unittest.Seal.WithResult(resultsByBlockID[sealedBlock.ID()]), 651 ) 652 653 suite.T().Logf("block %d has seals for %d", i, seals[0].Height) 654 } 655 656 height := uint64(i) 657 block := buildBlock(height, previousBlock, seals) 658 659 ed := synctest.ExecutionDataFixture(block.ID()) 660 661 cid, err := eds.AddExecutionData(context.Background(), ed) 662 require.NoError(suite.T(), err) 663 664 result := buildResult(block, cid, previousResult) 665 666 blocksByHeight[height] = block 667 blocksByID[block.ID()] = block 668 resultsByBlockID[block.ID()] = result 669 resultsByID[result.ID()] = result 670 671 // ignore all the data we don't need to verify the test 672 if i > 0 && i <= sealedCount { 673 executionDataByID[block.ID()] = ed 674 edsEntries[cid] = &testExecutionDataServiceEntry{ExecutionData: ed} 675 if fn, has := specialHeightFuncs[height]; has { 676 edsEntries[cid].fn = fn 677 } 678 679 executionDataIDByBlockID[block.ID()] = cid 680 } 681 682 previousBlock = block 683 previousResult = result 684 } 685 686 return &fetchTestRun{ 687 sealedCount: sealedCount, 688 startHeight: startHeight, 689 endHeight: endHeight, 690 blocksByHeight: blocksByHeight, 691 blocksByID: blocksByID, 692 resultsByBlockID: resultsByBlockID, 693 resultsByID: resultsByID, 694 sealsByBlockID: sealsByBlockID, 695 executionDataByID: executionDataByID, 696 executionDataEntries: edsEntries, 697 executionDataIDByBlockID: executionDataIDByBlockID, 698 waitTimeout: time.Second * 5, 699 700 maxSearchAhead: requester.DefaultMaxSearchAhead, 701 fetchTimeout: requester.DefaultFetchTimeout, 702 retryDelay: 1 * time.Millisecond, 703 maxRetryDelay: 15 * time.Millisecond, 704 } 705 } 706 707 func buildBlock(height uint64, parent *flow.Block, seals []*flow.Header) *flow.Block { 708 if parent == nil { 709 return unittest.GenesisFixture() 710 } 711 712 if len(seals) == 0 { 713 return unittest.BlockWithParentFixture(parent.Header) 714 } 715 716 return unittest.BlockWithParentAndSeals(parent.Header, seals) 717 } 718 719 func buildResult(block *flow.Block, cid flow.Identifier, previousResult *flow.ExecutionResult) *flow.ExecutionResult { 720 opts := []func(result *flow.ExecutionResult){ 721 unittest.WithBlock(block), 722 unittest.WithExecutionDataID(cid), 723 } 724 725 if previousResult != nil { 726 opts = append(opts, unittest.WithPreviousResult(*previousResult)) 727 } 728 729 return unittest.ExecutionResultFixture(opts...) 730 } 731 732 func verifyFetchedExecutionData(t *testing.T, actual receivedExecutionData, cfg *fetchTestRun) { 733 expected := cfg.executionDataByID 734 assert.Len(t, actual, len(expected)) 735 736 for i := 0; i < cfg.sealedCount; i++ { 737 height := cfg.startHeight + uint64(i) 738 block := cfg.blocksByHeight[height] 739 blockID := block.ID() 740 741 expectedED := expected[blockID] 742 actualED, has := actual[blockID] 743 assert.True(t, has, "missing execution data for block %v height %d", blockID, height) 744 if has { 745 assert.Equal(t, expectedED, actualED, "execution data for block %v doesn't match", blockID) 746 } 747 } 748 } 749 750 type mockSnapshot struct { 751 header *flow.Header 752 err error 753 mu sync.Mutex 754 } 755 756 func (m *mockSnapshot) set(header *flow.Header, err error) { 757 m.mu.Lock() 758 defer m.mu.Unlock() 759 760 m.header = header 761 m.err = err 762 } 763 764 func (m *mockSnapshot) Head() (*flow.Header, error) { 765 m.mu.Lock() 766 defer m.mu.Unlock() 767 768 return m.header, m.err 769 } 770 771 // none of these are used in this test 772 func (m *mockSnapshot) QuorumCertificate() (*flow.QuorumCertificate, error) { return nil, nil } 773 func (m *mockSnapshot) Identities(selector flow.IdentityFilter) (flow.IdentityList, error) { 774 return nil, nil 775 } 776 func (m *mockSnapshot) Identity(nodeID flow.Identifier) (*flow.Identity, error) { return nil, nil } 777 func (m *mockSnapshot) SealedResult() (*flow.ExecutionResult, *flow.Seal, error) { 778 return nil, nil, nil 779 } 780 func (m *mockSnapshot) Commit() (flow.StateCommitment, error) { return flow.DummyStateCommitment, nil } 781 func (m *mockSnapshot) SealingSegment() (*flow.SealingSegment, error) { return nil, nil } 782 func (m *mockSnapshot) Descendants() ([]flow.Identifier, error) { return nil, nil } 783 func (m *mockSnapshot) ValidDescendants() ([]flow.Identifier, error) { return nil, nil } 784 func (m *mockSnapshot) RandomSource() ([]byte, error) { return nil, nil } 785 func (m *mockSnapshot) Phase() (flow.EpochPhase, error) { return flow.EpochPhaseUndefined, nil } 786 func (m *mockSnapshot) Epochs() protocol.EpochQuery { return nil } 787 func (m *mockSnapshot) Params() protocol.GlobalParams { return nil }