github.com/onflow/flow-go@v0.33.17/module/builder/consensus/builder_test.go (about) 1 package consensus 2 3 import ( 4 "math/rand" 5 "os" 6 "testing" 7 8 "github.com/dgraph-io/badger/v2" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/require" 12 "github.com/stretchr/testify/suite" 13 14 "github.com/onflow/flow-go/model/flow" 15 mempoolAPIs "github.com/onflow/flow-go/module/mempool" 16 mempoolImpl "github.com/onflow/flow-go/module/mempool/consensus" 17 mempool "github.com/onflow/flow-go/module/mempool/mock" 18 "github.com/onflow/flow-go/module/metrics" 19 "github.com/onflow/flow-go/module/trace" 20 realproto "github.com/onflow/flow-go/state/protocol" 21 protocol "github.com/onflow/flow-go/state/protocol/mock" 22 storerr "github.com/onflow/flow-go/storage" 23 "github.com/onflow/flow-go/storage/badger/operation" 24 storage "github.com/onflow/flow-go/storage/mock" 25 "github.com/onflow/flow-go/utils/unittest" 26 ) 27 28 func TestConsensusBuilder(t *testing.T) { 29 suite.Run(t, new(BuilderSuite)) 30 } 31 32 type BuilderSuite struct { 33 suite.Suite 34 35 // test helpers 36 firstID flow.Identifier // first block in the range we look at 37 finalID flow.Identifier // last finalized block 38 parentID flow.Identifier // Parent block we build on 39 finalizedBlockIDs []flow.Identifier // blocks between first and final 40 pendingBlockIDs []flow.Identifier // blocks between final and parent 41 resultForBlock map[flow.Identifier]*flow.ExecutionResult // map: BlockID -> Execution Result 42 resultByID map[flow.Identifier]*flow.ExecutionResult // map: result ID -> Execution Result 43 receiptsByID map[flow.Identifier]*flow.ExecutionReceipt // map: receipt ID -> ExecutionReceipt 44 receiptsByBlockID map[flow.Identifier]flow.ExecutionReceiptList // map: block ID -> flow.ExecutionReceiptList 45 46 // used to populate and test the seal mempool 47 chain []*flow.Seal // chain of seals starting first 48 irsList []*flow.IncorporatedResultSeal // chain of IncorporatedResultSeals 49 irsMap map[flow.Identifier]*flow.IncorporatedResultSeal // index for irsList 50 51 // mempools consumed by builder 52 pendingGuarantees []*flow.CollectionGuarantee 53 pendingReceipts []*flow.ExecutionReceipt 54 pendingSeals map[flow.Identifier]*flow.IncorporatedResultSeal // storage for the seal mempool 55 56 // storage for dbs 57 headers map[flow.Identifier]*flow.Header 58 index map[flow.Identifier]*flow.Index 59 blocks map[flow.Identifier]*flow.Block 60 blockChildren map[flow.Identifier][]flow.Identifier // ids of children blocks 61 62 lastSeal *flow.Seal 63 64 // real dependencies 65 dir string 66 db *badger.DB 67 sentinel uint64 68 setter func(*flow.Header) error 69 70 // mocked dependencies 71 state *protocol.ParticipantState 72 headerDB *storage.Headers 73 sealDB *storage.Seals 74 indexDB *storage.Index 75 blockDB *storage.Blocks 76 resultDB *storage.ExecutionResults 77 receiptsDB *storage.ExecutionReceipts 78 79 guarPool *mempool.Guarantees 80 sealPool *mempool.IncorporatedResultSeals 81 recPool *mempool.ExecutionTree 82 83 // tracking behaviour 84 assembled *flow.Payload // built payload 85 86 // component under test 87 build *Builder 88 } 89 90 func (bs *BuilderSuite) storeBlock(block *flow.Block) { 91 bs.headers[block.ID()] = block.Header 92 bs.blocks[block.ID()] = block 93 bs.index[block.ID()] = block.Payload.Index() 94 bs.blockChildren[block.Header.ParentID] = append(bs.blockChildren[block.Header.ParentID], block.ID()) 95 for _, result := range block.Payload.Results { 96 bs.resultByID[result.ID()] = result 97 } 98 } 99 100 // createAndRecordBlock creates a new block chained to the previous block. 101 // The new block contains a receipt for a result of the previous 102 // block, which is also used to create a seal for the previous block. The seal 103 // and the result are combined in an IncorporatedResultSeal which is a candidate 104 // for the seals mempool. 105 func (bs *BuilderSuite) createAndRecordBlock(parentBlock *flow.Block, candidateSealForParent bool) *flow.Block { 106 block := unittest.BlockWithParentFixture(parentBlock.Header) 107 108 // Create a receipt for a result of the parentBlock block, 109 // and add it to the payload. The corresponding IncorporatedResult will be used to 110 // seal the parentBlock, and to create an IncorporatedResultSeal for the seal mempool. 111 var incorporatedResultForPrevBlock *flow.IncorporatedResult 112 previousResult, found := bs.resultForBlock[parentBlock.ID()] 113 if !found { 114 panic("missing execution result for parent") 115 } 116 receipt := unittest.ExecutionReceiptFixture(unittest.WithResult(previousResult)) 117 block.Payload.Receipts = append(block.Payload.Receipts, receipt.Meta()) 118 block.Payload.Results = append(block.Payload.Results, &receipt.ExecutionResult) 119 120 incorporatedResultForPrevBlock = unittest.IncorporatedResult.Fixture( 121 unittest.IncorporatedResult.WithResult(previousResult), 122 unittest.IncorporatedResult.WithIncorporatedBlockID(block.ID()), 123 ) 124 125 result := unittest.ExecutionResultFixture( 126 unittest.WithBlock(block), 127 unittest.WithPreviousResult(*previousResult), 128 ) 129 130 bs.resultForBlock[result.BlockID] = result 131 bs.resultByID[result.ID()] = result 132 bs.receiptsByID[receipt.ID()] = receipt 133 bs.receiptsByBlockID[receipt.ExecutionResult.BlockID] = append(bs.receiptsByBlockID[receipt.ExecutionResult.BlockID], receipt) 134 135 // record block in dbs 136 bs.storeBlock(block) 137 138 if candidateSealForParent { 139 // seal the parentBlock block with the result included in this block. 140 bs.chainSeal(incorporatedResultForPrevBlock) 141 } 142 143 return block 144 } 145 146 // Create a seal for the result's block. The corresponding 147 // IncorporatedResultSeal, which ties the seal to the incorporated result it 148 // seals, is also recorded for future access. 149 func (bs *BuilderSuite) chainSeal(incorporatedResult *flow.IncorporatedResult) { 150 incorporatedResultSeal := unittest.IncorporatedResultSeal.Fixture( 151 unittest.IncorporatedResultSeal.WithResult(incorporatedResult.Result), 152 unittest.IncorporatedResultSeal.WithIncorporatedBlockID(incorporatedResult.IncorporatedBlockID), 153 ) 154 155 bs.chain = append(bs.chain, incorporatedResultSeal.Seal) 156 bs.irsMap[incorporatedResultSeal.ID()] = incorporatedResultSeal 157 bs.irsList = append(bs.irsList, incorporatedResultSeal) 158 } 159 160 // SetupTest constructs the following chain of blocks: 161 // 162 // [first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] <- [A3] <- [parent] 163 // 164 // Where block 165 // - [first] is sealed and finalized 166 // - [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool 167 // - [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool 168 // - [A3] and [parent] are non-finalized, unsealed blocks _without_ candidate seals 169 // 170 // Each block incorporates the result for its immediate parent. 171 // 172 // Note: In the happy path, the blocks [A3] and [parent] will not have candidate seal for the following reason: 173 // For the verifiers to start checking a result R, they need a source of randomness for the block _incorporating_ 174 // result R. The result for block [A3] is incorporated in [parent], which does _not_ have a child yet. 175 func (bs *BuilderSuite) SetupTest() { 176 177 // set up no-op dependencies 178 noopMetrics := metrics.NewNoopCollector() 179 noopTracer := trace.NewNoopTracer() 180 181 // set up test parameters 182 numFinalizedBlocks := 4 183 numPendingBlocks := 4 184 185 // reset test helpers 186 bs.pendingBlockIDs = nil 187 bs.finalizedBlockIDs = nil 188 bs.resultForBlock = make(map[flow.Identifier]*flow.ExecutionResult) 189 bs.resultByID = make(map[flow.Identifier]*flow.ExecutionResult) 190 bs.receiptsByID = make(map[flow.Identifier]*flow.ExecutionReceipt) 191 bs.receiptsByBlockID = make(map[flow.Identifier]flow.ExecutionReceiptList) 192 193 bs.chain = nil 194 bs.irsMap = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 195 bs.irsList = nil 196 197 // initialize the pools 198 bs.pendingGuarantees = nil 199 bs.pendingSeals = nil 200 bs.pendingReceipts = nil 201 202 // initialise the dbs 203 bs.lastSeal = nil 204 bs.headers = make(map[flow.Identifier]*flow.Header) 205 //bs.heights = make(map[uint64]*flow.Header) 206 bs.index = make(map[flow.Identifier]*flow.Index) 207 bs.blocks = make(map[flow.Identifier]*flow.Block) 208 bs.blockChildren = make(map[flow.Identifier][]flow.Identifier) 209 210 // initialize behaviour tracking 211 bs.assembled = nil 212 213 // Construct the [first] block: 214 first := unittest.BlockFixture() 215 bs.storeBlock(&first) 216 bs.firstID = first.ID() 217 firstResult := unittest.ExecutionResultFixture(unittest.WithBlock(&first)) 218 bs.lastSeal = unittest.Seal.Fixture(unittest.Seal.WithResult(firstResult)) 219 bs.resultForBlock[firstResult.BlockID] = firstResult 220 bs.resultByID[firstResult.ID()] = firstResult 221 222 // Construct finalized blocks [F0] ... [F4] 223 previous := &first 224 for n := 0; n < numFinalizedBlocks; n++ { 225 finalized := bs.createAndRecordBlock(previous, n > 0) // Do not construct candidate seal for [first], as it is already sealed 226 bs.finalizedBlockIDs = append(bs.finalizedBlockIDs, finalized.ID()) 227 previous = finalized 228 } 229 230 // Construct the last finalized block [final] 231 final := bs.createAndRecordBlock(previous, true) 232 bs.finalID = final.ID() 233 234 // Construct the pending (i.e. unfinalized) ancestors [A0], ..., [A3] 235 previous = final 236 for n := 0; n < numPendingBlocks; n++ { 237 pending := bs.createAndRecordBlock(previous, true) 238 bs.pendingBlockIDs = append(bs.pendingBlockIDs, pending.ID()) 239 previous = pending 240 } 241 242 // Construct [parent] block; but do _not_ add candidate seal for its parent 243 parent := bs.createAndRecordBlock(previous, false) 244 bs.parentID = parent.ID() 245 246 // set up temporary database for tests 247 bs.db, bs.dir = unittest.TempBadgerDB(bs.T()) 248 249 err := bs.db.Update(operation.InsertFinalizedHeight(final.Header.Height)) 250 bs.Require().NoError(err) 251 err = bs.db.Update(operation.IndexBlockHeight(final.Header.Height, bs.finalID)) 252 bs.Require().NoError(err) 253 254 err = bs.db.Update(operation.InsertRootHeight(13)) 255 bs.Require().NoError(err) 256 257 err = bs.db.Update(operation.InsertSealedHeight(first.Header.Height)) 258 bs.Require().NoError(err) 259 err = bs.db.Update(operation.IndexBlockHeight(first.Header.Height, first.ID())) 260 bs.Require().NoError(err) 261 262 bs.sentinel = 1337 263 264 bs.setter = func(header *flow.Header) error { 265 header.View = 1337 266 return nil 267 } 268 269 bs.state = &protocol.ParticipantState{} 270 bs.state.On("Extend", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 271 block := args.Get(1).(*flow.Block) 272 bs.Assert().Equal(bs.sentinel, block.Header.View) 273 bs.assembled = block.Payload 274 }).Return(nil) 275 bs.state.On("Final").Return(func() realproto.Snapshot { 276 if block, ok := bs.blocks[bs.finalID]; ok { 277 snapshot := unittest.StateSnapshotForKnownBlock(block.Header, nil) 278 snapshot.On("Descendants").Return(bs.blockChildren[bs.finalID], nil) 279 return snapshot 280 } 281 return unittest.StateSnapshotForUnknownBlock() 282 }) 283 284 // set up storage mocks for tests 285 bs.sealDB = &storage.Seals{} 286 bs.sealDB.On("HighestInFork", mock.Anything).Return(bs.lastSeal, nil) 287 288 bs.headerDB = &storage.Headers{} 289 bs.headerDB.On("ByBlockID", mock.Anything).Return( 290 func(blockID flow.Identifier) *flow.Header { 291 return bs.headers[blockID] 292 }, 293 func(blockID flow.Identifier) error { 294 _, exists := bs.headers[blockID] 295 if !exists { 296 return storerr.ErrNotFound 297 } 298 return nil 299 }, 300 ) 301 302 bs.indexDB = &storage.Index{} 303 bs.indexDB.On("ByBlockID", mock.Anything).Return( 304 func(blockID flow.Identifier) *flow.Index { 305 return bs.index[blockID] 306 }, 307 func(blockID flow.Identifier) error { 308 _, exists := bs.index[blockID] 309 if !exists { 310 return storerr.ErrNotFound 311 } 312 return nil 313 }, 314 ) 315 316 bs.blockDB = &storage.Blocks{} 317 bs.blockDB.On("ByID", mock.Anything).Return( 318 func(blockID flow.Identifier) *flow.Block { 319 return bs.blocks[blockID] 320 }, 321 func(blockID flow.Identifier) error { 322 _, exists := bs.blocks[blockID] 323 if !exists { 324 return storerr.ErrNotFound 325 } 326 return nil 327 }, 328 ) 329 330 bs.resultDB = &storage.ExecutionResults{} 331 bs.resultDB.On("ByID", mock.Anything).Return( 332 func(resultID flow.Identifier) *flow.ExecutionResult { 333 return bs.resultByID[resultID] 334 }, 335 func(resultID flow.Identifier) error { 336 _, exists := bs.resultByID[resultID] 337 if !exists { 338 return storerr.ErrNotFound 339 } 340 return nil 341 }, 342 ) 343 344 bs.receiptsDB = &storage.ExecutionReceipts{} 345 bs.receiptsDB.On("ByID", mock.Anything).Return( 346 func(receiptID flow.Identifier) *flow.ExecutionReceipt { 347 return bs.receiptsByID[receiptID] 348 }, 349 func(receiptID flow.Identifier) error { 350 _, exists := bs.receiptsByID[receiptID] 351 if !exists { 352 return storerr.ErrNotFound 353 } 354 return nil 355 }, 356 ) 357 bs.receiptsDB.On("ByBlockID", mock.Anything).Return( 358 func(blockID flow.Identifier) flow.ExecutionReceiptList { 359 return bs.receiptsByBlockID[blockID] 360 }, 361 func(blockID flow.Identifier) error { 362 _, exists := bs.receiptsByBlockID[blockID] 363 if !exists { 364 return storerr.ErrNotFound 365 } 366 return nil 367 }, 368 ) 369 370 // set up memory pool mocks for tests 371 bs.guarPool = &mempool.Guarantees{} 372 bs.guarPool.On("Size").Return(uint(0)) // only used by metrics 373 bs.guarPool.On("All").Return( 374 func() []*flow.CollectionGuarantee { 375 return bs.pendingGuarantees 376 }, 377 ) 378 379 bs.sealPool = &mempool.IncorporatedResultSeals{} 380 bs.sealPool.On("Size").Return(uint(0)) // only used by metrics 381 bs.sealPool.On("All").Return( 382 func() []*flow.IncorporatedResultSeal { 383 res := make([]*flow.IncorporatedResultSeal, 0, len(bs.pendingSeals)) 384 for _, ps := range bs.pendingSeals { 385 res = append(res, ps) 386 } 387 return res 388 }, 389 ) 390 bs.sealPool.On("ByID", mock.Anything).Return( 391 func(id flow.Identifier) *flow.IncorporatedResultSeal { 392 return bs.pendingSeals[id] 393 }, 394 func(id flow.Identifier) bool { 395 _, exists := bs.pendingSeals[id] 396 return exists 397 }, 398 ) 399 400 bs.recPool = &mempool.ExecutionTree{} 401 bs.recPool.On("PruneUpToHeight", mock.Anything).Return(nil).Maybe() 402 bs.recPool.On("Size").Return(uint(0)).Maybe() // used for metrics only 403 bs.recPool.On("AddResult", mock.Anything, mock.Anything).Return(nil).Maybe() 404 bs.recPool.On("AddReceipt", mock.Anything, mock.Anything).Return(false, nil).Maybe() 405 bs.recPool.On("ReachableReceipts", mock.Anything, mock.Anything, mock.Anything).Return( 406 func(resultID flow.Identifier, blockFilter mempoolAPIs.BlockFilter, receiptFilter mempoolAPIs.ReceiptFilter) []*flow.ExecutionReceipt { 407 return bs.pendingReceipts 408 }, 409 nil, 410 ) 411 412 // initialize the builder 413 bs.build, err = NewBuilder( 414 noopMetrics, 415 bs.db, 416 bs.state, 417 bs.headerDB, 418 bs.sealDB, 419 bs.indexDB, 420 bs.blockDB, 421 bs.resultDB, 422 bs.receiptsDB, 423 bs.guarPool, 424 bs.sealPool, 425 bs.recPool, 426 noopTracer, 427 ) 428 require.NoError(bs.T(), err) 429 430 bs.build.cfg.expiry = 11 431 } 432 433 func (bs *BuilderSuite) TearDownTest() { 434 err := bs.db.Close() 435 bs.Assert().NoError(err) 436 err = os.RemoveAll(bs.dir) 437 bs.Assert().NoError(err) 438 } 439 440 func (bs *BuilderSuite) TestPayloadEmptyValid() { 441 442 // we should build an empty block with default setup 443 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 444 bs.Require().NoError(err) 445 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 446 bs.Assert().Empty(bs.assembled.Seals, "should have no seals in payload with empty mempool") 447 } 448 449 func (bs *BuilderSuite) TestPayloadGuaranteeValid() { 450 451 // add sixteen guarantees to the pool 452 bs.pendingGuarantees = unittest.CollectionGuaranteesFixture(16, unittest.WithCollRef(bs.finalID)) 453 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 454 bs.Require().NoError(err) 455 bs.Assert().ElementsMatch(bs.pendingGuarantees, bs.assembled.Guarantees, "should have guarantees from mempool in payload") 456 } 457 458 func (bs *BuilderSuite) TestPayloadGuaranteeDuplicate() { 459 460 // create some valid guarantees 461 valid := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(bs.finalID)) 462 463 forkBlocks := append(bs.finalizedBlockIDs, bs.pendingBlockIDs...) 464 465 // create some duplicate guarantees and add to random blocks on the fork 466 duplicated := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID)) 467 for _, guarantee := range duplicated { 468 blockID := forkBlocks[rand.Intn(len(forkBlocks))] 469 index := bs.index[blockID] 470 index.CollectionIDs = append(index.CollectionIDs, guarantee.ID()) 471 bs.index[blockID] = index 472 } 473 474 // add sixteen guarantees to the pool 475 bs.pendingGuarantees = append(valid, duplicated...) 476 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 477 bs.Require().NoError(err) 478 bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid guarantees from mempool in payload") 479 } 480 481 func (bs *BuilderSuite) TestPayloadGuaranteeReferenceUnknown() { 482 483 // create 12 valid guarantees 484 valid := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID)) 485 486 // create 4 guarantees with unknown reference 487 unknown := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(unittest.IdentifierFixture())) 488 489 // add all guarantees to the pool 490 bs.pendingGuarantees = append(valid, unknown...) 491 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 492 bs.Require().NoError(err) 493 bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid from mempool in payload") 494 } 495 496 func (bs *BuilderSuite) TestPayloadGuaranteeReferenceExpired() { 497 498 // create 12 valid guarantees 499 valid := unittest.CollectionGuaranteesFixture(12, unittest.WithCollRef(bs.finalID)) 500 501 // create 4 expired guarantees 502 header := unittest.BlockHeaderFixture() 503 header.Height = bs.headers[bs.finalID].Height - 12 504 bs.headers[header.ID()] = header 505 expired := unittest.CollectionGuaranteesFixture(4, unittest.WithCollRef(header.ID())) 506 507 // add all guarantees to the pool 508 bs.pendingGuarantees = append(valid, expired...) 509 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 510 bs.Require().NoError(err) 511 bs.Assert().ElementsMatch(valid, bs.assembled.Guarantees, "should have valid from mempool in payload") 512 } 513 514 // TestPayloadSeals_AllValid checks that builder seals as many blocks as possible (happy path): 515 // 516 // [first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] <- [A3] <- [parent] 517 // 518 // Where block 519 // - [first] is sealed and finalized 520 // - [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool 521 // - [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool 522 // - [A3] and [parent] are non-finalized, unsealed blocks _without_ candidate seals 523 // 524 // Expected behaviour: 525 // - builder should include seals [F0], ..., [A4] 526 // - note: Block [A3] will not have a seal in the happy path for the following reason: 527 // In our example, the result for block A3 is incorporated in block A4. But, for the verifiers to start 528 // their work, they need a child block of A4, because the child contains the source of randomness for 529 // A4. But we are just constructing this child right now. Hence, the verifiers couldn't have checked 530 // the result for A3. 531 func (bs *BuilderSuite) TestPayloadSeals_AllValid() { 532 // Populate seals mempool with valid chain of seals for blocks [F0], ..., [A2] 533 bs.pendingSeals = bs.irsMap 534 535 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 536 bs.Require().NoError(err) 537 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 538 bs.Assert().ElementsMatch(bs.chain, bs.assembled.Seals, "should have included valid chain of seals") 539 } 540 541 // TestPayloadSeals_Limit verifies that builder does not exceed maxSealLimit 542 func (bs *BuilderSuite) TestPayloadSeals_Limit() { 543 // use valid chain of seals in mempool 544 bs.pendingSeals = bs.irsMap 545 546 // change maxSealCount to one less than the number of items in the mempool 547 limit := uint(2) 548 bs.build.cfg.maxSealCount = limit 549 550 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 551 bs.Require().NoError(err) 552 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 553 bs.Assert().Equal(bs.chain[:limit], bs.assembled.Seals, "should have excluded seals above maxSealCount") 554 } 555 556 // TestPayloadSeals_OnlyFork checks that the builder only includes seals corresponding 557 // to blocks on the current fork (and _not_ seals for sealable blocks on other forks) 558 func (bs *BuilderSuite) TestPayloadSeals_OnlyFork() { 559 // in the test setup, we already created a single fork 560 // [first] <- [F0] <- [F1] <- [F2] <- [F3] <- [final] <- [A0] <- [A1] <- [A2] .. 561 // For this test, we add fork: ^ 562 // └--- [B0] <- [B1] <- ....<- [B6] <- [B7] 563 // Where block 564 // * [first] is sealed and finalized 565 // * [F0] ... [F4] and [final] are finalized, unsealed blocks with candidate seals are included in mempool 566 // * [A0] ... [A2] are non-finalized, unsealed blocks with candidate seals are included in mempool 567 forkHead := bs.blocks[bs.finalID] 568 for i := 0; i < 8; i++ { 569 // Usually, the blocks [B6] and [B7] will not have candidate seal for the following reason: 570 // For the verifiers to start checking a result R, they need a source of randomness for the block _incorporating_ 571 // result R. The result for block [B6] is incorporated in [B7], which does _not_ have a child yet. 572 forkHead = bs.createAndRecordBlock(forkHead, i < 6) 573 } 574 575 bs.pendingSeals = bs.irsMap 576 _, err := bs.build.BuildOn(forkHead.ID(), bs.setter) 577 bs.Require().NoError(err) 578 579 // expected seals: [F0] <- ... <- [final] <- [B0] <- ... <- [B5] 580 // Note: bs.chain contains seals for blocks [F0]...[A2] followed by seals for [final], [B0]...[B5] 581 bs.Assert().Equal(10, len(bs.assembled.Seals), "unexpected number of seals") 582 bs.Assert().ElementsMatch(bs.chain[:4], bs.assembled.Seals[:4], "should have included only valid chain of seals") 583 bs.Assert().ElementsMatch(bs.chain[8:], bs.assembled.Seals[4:], "should have included only valid chain of seals") 584 585 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 586 } 587 588 // TestPayloadSeals_EnforceGap checks that builder leaves a 1-block gap between block incorporating the result 589 // and the block sealing the result. Without this gap, some nodes might not be able to compute the Verifier 590 // assignment for the seal and therefore reject the block. This edge case only occurs in a very specific situation: 591 // 592 // ┌---- [A5] (orphaned fork) 593 // v 594 // ...<- [B0] <- [B1] <- [B2] <- [B3] <- [B4{incorporates result R for B1}] <- ░newBlock░ 595 // 596 // SCENARIO: 597 // - block B0 is sealed 598 // Proposer for ░newBlock░ knows block A5. Hence, it knows a QC for block B4, which contains the Source Of Randomness (SOR) for B4. 599 // Therefore, the proposer can construct the verifier assignment for [B4{incorporates result R for B1}] 600 // - Assume that verification was fast enough, so the proposer has sufficient approvals for result R. 601 // Therefore, the proposer has a candidate seal, sealing result R for block B4, in its mempool. 602 // 603 // Replica trying to verify ░newBlock░: 604 // 605 // - Assume that the replica does _not_ know A5. Therefore, it _cannot_ compute the verifier assignment for B4. 606 // 607 // Problem: If the proposer included the seal for B1, the replica could not check it. 608 // Solution: There must be a gap between the block incorporating the result (here B4) and 609 // the block sealing the result. A gap of one block is sufficient. 610 // 611 // ┌---- [A5] (orphaned fork) 612 // v 613 // ...<- [B0] <- [B1] <- [B2] <- [B3] <- [B4{incorporates result R for B1}] <- [B5] <- [B6{seals B1}] 614 // ~~~~~~ 615 // gap 616 // 617 // We test the two distinct cases: 618 // 619 // (i) Builder does _not_ include seal for B1 when constructing block B5 620 // (ii) Builder _includes_ seal for B1 when constructing block B6 621 func (bs *BuilderSuite) TestPayloadSeals_EnforceGap() { 622 // we use bs.parentID as block B0 623 b0result := bs.resultForBlock[bs.parentID] 624 b0seal := unittest.Seal.Fixture(unittest.Seal.WithResult(b0result)) 625 626 // create blocks B1 to B4: 627 b1 := bs.createAndRecordBlock(bs.blocks[bs.parentID], true) 628 bchain := unittest.ChainFixtureFrom(3, b1.Header) // creates blocks b2, b3, b4 629 b4 := bchain[2] 630 631 // Incorporate result for block B1 into payload of block B4 632 resultB1 := bs.resultForBlock[b1.ID()] 633 receiptB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultB1)) 634 b4.SetPayload( 635 flow.Payload{ 636 Results: []*flow.ExecutionResult{&receiptB1.ExecutionResult}, 637 Receipts: []*flow.ExecutionReceiptMeta{receiptB1.Meta()}, 638 }) 639 640 // add blocks B2, B3, B4, A5 to the mocked storage layer (block b0 and b1 are already added): 641 a5 := unittest.BlockWithParentFixture(b4.Header) 642 for _, b := range append(bchain, a5) { 643 bs.storeBlock(b) 644 } 645 646 // mock for of candidate seal mempool: 647 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 648 b1seal := storeSealForIncorporatedResult(resultB1, b4.ID(), bs.pendingSeals) 649 650 // mock for seals storage layer: 651 bs.sealDB = &storage.Seals{} 652 bs.build.seals = bs.sealDB 653 654 bs.T().Run("Build on top of B4 and check that no seals are included", func(t *testing.T) { 655 bs.sealDB.On("HighestInFork", b4.ID()).Return(b0seal, nil) 656 657 _, err := bs.build.BuildOn(b4.ID(), bs.setter) 658 require.NoError(t, err) 659 bs.recPool.AssertExpectations(t) 660 require.Empty(t, bs.assembled.Seals, "should not include any seals") 661 }) 662 663 bs.T().Run("Build on top of B5 and check that seals for B1 is included", func(t *testing.T) { 664 b5 := unittest.BlockWithParentFixture(b4.Header) // creating block b5 665 bs.storeBlock(b5) 666 bs.sealDB.On("HighestInFork", b5.ID()).Return(b0seal, nil) 667 668 _, err := bs.build.BuildOn(b5.ID(), bs.setter) 669 require.NoError(t, err) 670 bs.recPool.AssertExpectations(t) 671 require.Equal(t, 1, len(bs.assembled.Seals), "only seal for B1 expected") 672 require.Equal(t, b1seal.Seal, bs.assembled.Seals[0]) 673 }) 674 } 675 676 // TestPayloadSeals_Duplicates verifies that the builder does not duplicate seals for already sealed blocks: 677 // 678 // ... <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 679 // 680 // Where block 681 // - [F0] ... [F3] sealed blocks but their candidate seals are still included in mempool 682 // - [A0] ... [A3] unsealed blocks with candidate seals are included in mempool 683 // 684 // Expected behaviour: 685 // - builder should only include seals [A0], ..., [A3] 686 func (bs *BuilderSuite) TestPayloadSeals_Duplicate() { 687 // Pretend that the first n blocks are already sealed 688 n := 4 689 lastSeal := bs.chain[n-1] 690 mockSealDB := &storage.Seals{} 691 mockSealDB.On("HighestInFork", mock.Anything).Return(lastSeal, nil) 692 bs.build.seals = mockSealDB 693 694 // seals for all blocks [F0], ..., [A3] are still in the mempool: 695 bs.pendingSeals = bs.irsMap 696 697 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 698 bs.Require().NoError(err) 699 bs.Assert().Equal(bs.chain[n:], bs.assembled.Seals, "should have rejected duplicate seals") 700 } 701 702 // TestPayloadSeals_MissingNextSeal checks how the builder handles the fork 703 // 704 // [S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 705 // 706 // Where block 707 // - [S] is sealed and finalized 708 // - [F0] finalized, unsealed block but _without_ candidate seal in mempool 709 // - [F1] ... [F3] are finalized, unsealed blocks with candidate seals are included in mempool 710 // - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool 711 // 712 // Expected behaviour: 713 // - builder should not include any seals as the immediately next seal is not in mempool 714 func (bs *BuilderSuite) TestPayloadSeals_MissingNextSeal() { 715 // remove the seal for block [F0] 716 firstSeal := bs.irsList[0] 717 delete(bs.irsMap, firstSeal.ID()) 718 bs.pendingSeals = bs.irsMap 719 720 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 721 bs.Require().NoError(err) 722 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 723 bs.Assert().Empty(bs.assembled.Seals, "should not have included any seals from cutoff chain") 724 } 725 726 // TestPayloadSeals_MissingInterimSeal checks how the builder handles the fork 727 // 728 // [S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 729 // 730 // Where block 731 // - [S] is sealed and finalized 732 // - [F0] ... [F2] are finalized, unsealed blocks with candidate seals are included in mempool 733 // - [F4] finalized, unsealed block but _without_ candidate seal in mempool 734 // - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool 735 // 736 // Expected behaviour: 737 // - builder should only include candidate seals for [F0], [F1], [F2] 738 func (bs *BuilderSuite) TestPayloadSeals_MissingInterimSeal() { 739 // remove a seal for block [F4] 740 seal := bs.irsList[3] 741 delete(bs.irsMap, seal.ID()) 742 bs.pendingSeals = bs.irsMap 743 744 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 745 bs.Require().NoError(err) 746 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 747 bs.Assert().ElementsMatch(bs.chain[:3], bs.assembled.Seals, "should have included only beginning of broken chain") 748 } 749 750 // TestValidatePayloadSeals_ExecutionForks checks how the builder's seal-inclusion logic 751 // handles execution forks. 752 // 753 // we have the chain in storage: 754 // 755 // F <- A{Result[F]_1, Result[F]_2, ReceiptMeta[F]_1, ReceiptMeta[F]_2} 756 // <- B{Result[A]_1, Result[A]_2, ReceiptMeta[A]_1, ReceiptMeta[A]_2} 757 // <- C{Result[B]_1, Result[B]_2, ReceiptMeta[B]_1, ReceiptMeta[B]_2} 758 // <- D{Seal for Result[F]_1} 759 // 760 // here F is the latest finalized block (with ID bs.finalID) 761 // 762 // Note that we are explicitly testing the handling of an execution fork that 763 // was incorporated _before_ the seal 764 // 765 // Blocks: F <----------- A <----------- B 766 // Results: Result[F]_1 <- Result[A]_1 <- Result[B]_1 :: the root of this execution tree is sealed 767 // Result[F]_2 <- Result[A]_2 <- Result[B]_2 :: the root of this execution tree conflicts with sealed result 768 // 769 // The builder is tasked with creating the payload for block X: 770 // 771 // F <- A{..} <- B{..} <- C{..} <- D{..} <- X 772 // 773 // We test the two distinct cases: 774 // 775 // (i) verify that execution fork conflicting with sealed result is not sealed 776 // (ii) verify that multiple execution forks are properly handled 777 func (bs *BuilderSuite) TestValidatePayloadSeals_ExecutionForks() { 778 bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past [lastSeal] 779 780 blockF := bs.blocks[bs.finalID] 781 blocks := []*flow.Block{blockF} 782 blocks = append(blocks, unittest.ChainFixtureFrom(4, blockF.Header)...) // elements [F, A, B, C, D] 783 receiptChain1 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[F]_1, Result[A]_1, Result[B]_1, ...] 784 receiptChain2 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[F]_2, Result[A]_2, Result[B]_2, ...] 785 786 for i := 1; i <= 3; i++ { // set payload for blocks A, B, C 787 blocks[i].SetPayload(flow.Payload{ 788 Results: []*flow.ExecutionResult{&receiptChain1[i-1].ExecutionResult, &receiptChain2[i-1].ExecutionResult}, 789 Receipts: []*flow.ExecutionReceiptMeta{receiptChain1[i-1].Meta(), receiptChain2[i-1].Meta()}, 790 }) 791 } 792 sealedResult := receiptChain1[0].ExecutionResult 793 sealF := unittest.Seal.Fixture(unittest.Seal.WithResult(&sealedResult)) 794 blocks[4].SetPayload(flow.Payload{ // set payload for block D 795 Seals: []*flow.Seal{sealF}, 796 }) 797 for i := 0; i <= 4; i++ { 798 // we need to run this several times, as in each iteration as we have _multiple_ execution chains. 799 // In each iteration, we only mange to reconnect one additional height 800 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain1) 801 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain2) 802 } 803 804 for _, b := range blocks { 805 bs.storeBlock(b) 806 } 807 bs.sealDB = &storage.Seals{} 808 bs.build.seals = bs.sealDB 809 bs.sealDB.On("HighestInFork", mock.Anything).Return(sealF, nil) 810 bs.resultByID[sealedResult.ID()] = &sealedResult 811 812 bs.T().Run("verify that execution fork conflicting with sealed result is not sealed", func(t *testing.T) { 813 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 814 storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 815 816 _, err := bs.build.BuildOn(blocks[4].ID(), bs.setter) 817 require.NoError(t, err) 818 require.Empty(t, bs.assembled.Seals, "should not have included seal for conflicting execution fork") 819 }) 820 821 bs.T().Run("verify that multiple execution forks are properly handled", func(t *testing.T) { 822 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 823 sealResultA_1 := storeSealForIncorporatedResult(&receiptChain1[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 824 sealResultB_1 := storeSealForIncorporatedResult(&receiptChain1[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals) 825 storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 826 storeSealForIncorporatedResult(&receiptChain2[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals) 827 828 _, err := bs.build.BuildOn(blocks[4].ID(), bs.setter) 829 require.NoError(t, err) 830 require.ElementsMatch(t, []*flow.Seal{sealResultA_1.Seal, sealResultB_1.Seal}, bs.assembled.Seals, "valid fork should have been sealed") 831 }) 832 } 833 834 // TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult tests the receipt selection: 835 // Expectation: Builder should trigger ExecutionTree to search Execution Tree from 836 // last sealed result on respective fork. 837 // 838 // We test with the following main chain tree 839 // 840 // ┌-[X0] <- [X1{seals ..F4}] 841 // v 842 // [lastSeal] <- [F0] <- [F1] <- [F2] <- [F3] <- [F4] <- [A0] <- [A1{seals ..F2}] <- [A2] <- [A3] 843 // 844 // Where 845 // * blocks [lastSeal], [F1], ... [F4], [A0], ... [A4], are created by BuilderSuite 846 // * latest sealed block for a specific fork is provided by test-local seals storage mock 847 func (bs *BuilderSuite) TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult() { 848 bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past [lastSeal] 849 x0 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 850 x1 := bs.createAndRecordBlock(x0, true) 851 852 // set last sealed blocks: 853 f2 := bs.blocks[bs.finalizedBlockIDs[2]] 854 f2eal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[f2.ID()])) 855 f4Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[bs.finalID])) 856 bs.sealDB = &storage.Seals{} 857 bs.build.seals = bs.sealDB 858 859 // reset receipts mempool to verify calls made by Builder 860 bs.recPool = &mempool.ExecutionTree{} 861 bs.recPool.On("Size").Return(uint(0)).Maybe() 862 bs.build.recPool = bs.recPool 863 864 // building on top of X0: latest finalized block in fork is [lastSeal]; expect search to start with sealed result 865 bs.sealDB.On("HighestInFork", x0.ID()).Return(bs.lastSeal, nil) 866 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 867 _, err := bs.build.BuildOn(x0.ID(), bs.setter) 868 bs.Require().NoError(err) 869 bs.recPool.AssertExpectations(bs.T()) 870 871 // building on top of X1: latest finalized block in fork is [F4]; expect search to start with sealed result 872 bs.sealDB.On("HighestInFork", x1.ID()).Return(f4Seal, nil) 873 bs.recPool.On("ReachableReceipts", f4Seal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 874 _, err = bs.build.BuildOn(x1.ID(), bs.setter) 875 bs.Require().NoError(err) 876 bs.recPool.AssertExpectations(bs.T()) 877 878 // building on top of A3 (with ID bs.parentID): latest finalized block in fork is [F4]; expect search to start with sealed result 879 bs.sealDB.On("HighestInFork", bs.parentID).Return(f2eal, nil) 880 bs.recPool.On("ReachableReceipts", f2eal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 881 _, err = bs.build.BuildOn(bs.parentID, bs.setter) 882 bs.Require().NoError(err) 883 bs.recPool.AssertExpectations(bs.T()) 884 } 885 886 // TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork tests the receipt selection: 887 // In this test, we check that the Builder provides a BlockFilter which only allows 888 // blocks on the fork, which we are extending. We construct the following chain tree: 889 // 890 // ┌--[X1] ┌-[Y2] ┌-- [A6] 891 // v v v 892 // <- [Final] <- [*B1*] <- [*B2*] <- [*B3*] <- [*B4{seals B1}*] <- [*B5*] <- ░newBlock░ 893 // ^ 894 // └-- [C3] <- [C4] 895 // ^--- [D4] 896 // 897 // Expectation: BlockFilter should pass blocks marked with star: B1, ... ,B5 898 // All other blocks should be filtered out. 899 // 900 // Context: 901 // While the receipt selection itself is performed by the ExecutionTree, the Builder 902 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 903 func (bs *BuilderSuite) TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork() { 904 b1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 905 b2 := bs.createAndRecordBlock(b1, true) 906 b3 := bs.createAndRecordBlock(b2, true) 907 b4 := bs.createAndRecordBlock(b3, true) 908 b5 := bs.createAndRecordBlock(b4, true) 909 910 x1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 911 y2 := bs.createAndRecordBlock(b1, true) 912 a6 := bs.createAndRecordBlock(b5, true) 913 914 c3 := bs.createAndRecordBlock(b2, true) 915 c4 := bs.createAndRecordBlock(c3, true) 916 d4 := bs.createAndRecordBlock(c3, true) 917 918 // set last sealed blocks: 919 b1Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[b1.ID()])) 920 bs.sealDB = &storage.Seals{} 921 bs.sealDB.On("HighestInFork", b5.ID()).Return(b1Seal, nil) 922 bs.build.seals = bs.sealDB 923 924 // setup mock to test the BlockFilter provided by Builder 925 bs.recPool = &mempool.ExecutionTree{} 926 bs.recPool.On("Size").Return(uint(0)).Maybe() 927 bs.recPool.On("ReachableReceipts", b1Seal.ResultID, mock.Anything, mock.Anything).Run( 928 func(args mock.Arguments) { 929 blockFilter := args[1].(mempoolAPIs.BlockFilter) 930 for _, h := range []*flow.Header{b1.Header, b2.Header, b3.Header, b4.Header, b5.Header} { 931 assert.True(bs.T(), blockFilter(h)) 932 } 933 for _, h := range []*flow.Header{bs.blocks[bs.finalID].Header, x1.Header, y2.Header, a6.Header, c3.Header, c4.Header, d4.Header} { 934 assert.False(bs.T(), blockFilter(h)) 935 } 936 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 937 bs.build.recPool = bs.recPool 938 939 _, err := bs.build.BuildOn(b5.ID(), bs.setter) 940 bs.Require().NoError(err) 941 bs.recPool.AssertExpectations(bs.T()) 942 } 943 944 // TestPayloadReceipts_SkipDuplicatedReceipts tests the receipt selection: 945 // Expectation: we check that the Builder provides a ReceiptFilter which 946 // filters out duplicated receipts. 947 // Comment: 948 // While the receipt selection itself is performed by the ExecutionTree, the Builder 949 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 950 func (bs *BuilderSuite) TestPayloadReceipts_SkipDuplicatedReceipts() { 951 // setup mock to test the ReceiptFilter provided by Builder 952 bs.recPool = &mempool.ExecutionTree{} 953 bs.recPool.On("Size").Return(uint(0)).Maybe() 954 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run( 955 func(args mock.Arguments) { 956 receiptFilter := args[2].(mempoolAPIs.ReceiptFilter) 957 // verify that all receipts already included in blocks are filtered out: 958 for _, block := range bs.blocks { 959 resultByID := block.Payload.Results.Lookup() 960 for _, meta := range block.Payload.Receipts { 961 result := resultByID[meta.ResultID] 962 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 963 assert.False(bs.T(), receiptFilter(rcpt)) 964 } 965 } 966 // Verify that receipts for unsealed blocks, which are _not_ already incorporated are accepted: 967 for _, block := range bs.blocks { 968 if block.ID() != bs.firstID { // block with ID bs.firstID is already sealed 969 rcpt := unittest.ReceiptForBlockFixture(block) 970 assert.True(bs.T(), receiptFilter(rcpt)) 971 } 972 } 973 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 974 bs.build.recPool = bs.recPool 975 976 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 977 bs.Require().NoError(err) 978 bs.recPool.AssertExpectations(bs.T()) 979 } 980 981 // TestPayloadReceipts_SkipReceiptsForSealedBlock tests the receipt selection: 982 // Expectation: we check that the Builder provides a ReceiptFilter which 983 // filters out _any_ receipt for the sealed block. 984 // 985 // Comment: 986 // While the receipt selection itself is performed by the ExecutionTree, the Builder 987 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 988 func (bs *BuilderSuite) TestPayloadReceipts_SkipReceiptsForSealedBlock() { 989 // setup mock to test the ReceiptFilter provided by Builder 990 bs.recPool = &mempool.ExecutionTree{} 991 bs.recPool.On("Size").Return(uint(0)).Maybe() 992 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run( 993 func(args mock.Arguments) { 994 receiptFilter := args[2].(mempoolAPIs.ReceiptFilter) 995 996 // receipt for sealed block committing to same result as the sealed result 997 rcpt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.firstID])) 998 assert.False(bs.T(), receiptFilter(rcpt)) 999 1000 // receipt for sealed block committing to different result as the sealed result 1001 rcpt = unittest.ReceiptForBlockFixture(bs.blocks[bs.firstID]) 1002 assert.False(bs.T(), receiptFilter(rcpt)) 1003 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 1004 bs.build.recPool = bs.recPool 1005 1006 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1007 bs.Require().NoError(err) 1008 bs.recPool.AssertExpectations(bs.T()) 1009 } 1010 1011 // TestPayloadReceipts_BlockLimit tests that the builder does not include more 1012 // receipts than the configured maxReceiptCount. 1013 func (bs *BuilderSuite) TestPayloadReceipts_BlockLimit() { 1014 1015 // Populate the mempool with 5 valid receipts 1016 receipts := []*flow.ExecutionReceipt{} 1017 metas := []*flow.ExecutionReceiptMeta{} 1018 expectedResults := []*flow.ExecutionResult{} 1019 var i uint64 1020 for i = 0; i < 5; i++ { 1021 blockOnFork := bs.blocks[bs.irsList[i].Seal.BlockID] 1022 pendingReceipt := unittest.ReceiptForBlockFixture(blockOnFork) 1023 receipts = append(receipts, pendingReceipt) 1024 metas = append(metas, pendingReceipt.Meta()) 1025 expectedResults = append(expectedResults, &pendingReceipt.ExecutionResult) 1026 } 1027 bs.pendingReceipts = receipts 1028 1029 // set maxReceiptCount to 3 1030 var limit uint = 3 1031 bs.build.cfg.maxReceiptCount = limit 1032 1033 // ensure that only 3 of the 5 receipts were included 1034 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1035 bs.Require().NoError(err) 1036 bs.Assert().ElementsMatch(metas[:limit], bs.assembled.Receipts, "should have excluded receipts above maxReceiptCount") 1037 bs.Assert().ElementsMatch(expectedResults[:limit], bs.assembled.Results, "should have excluded results above maxReceiptCount") 1038 } 1039 1040 // TestPayloadReceipts_AsProvidedByReceiptForest tests the receipt selection. 1041 // Expectation: Builder should embed the Receipts as provided by the ExecutionTree 1042 func (bs *BuilderSuite) TestPayloadReceipts_AsProvidedByReceiptForest() { 1043 var expectedReceipts []*flow.ExecutionReceipt 1044 var expectedMetas []*flow.ExecutionReceiptMeta 1045 var expectedResults []*flow.ExecutionResult 1046 for i := 0; i < 10; i++ { 1047 expectedReceipts = append(expectedReceipts, unittest.ExecutionReceiptFixture()) 1048 expectedMetas = append(expectedMetas, expectedReceipts[i].Meta()) 1049 expectedResults = append(expectedResults, &expectedReceipts[i].ExecutionResult) 1050 } 1051 bs.recPool = &mempool.ExecutionTree{} 1052 bs.recPool.On("Size").Return(uint(0)).Maybe() 1053 bs.recPool.On("AddResult", mock.Anything, mock.Anything).Return(nil).Maybe() 1054 bs.recPool.On("ReachableReceipts", mock.Anything, mock.Anything, mock.Anything).Return(expectedReceipts, nil).Once() 1055 bs.build.recPool = bs.recPool 1056 1057 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1058 bs.Require().NoError(err) 1059 bs.Assert().ElementsMatch(expectedMetas, bs.assembled.Receipts, "should include receipts as returned by ExecutionTree") 1060 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "should include results as returned by ExecutionTree") 1061 bs.recPool.AssertExpectations(bs.T()) 1062 } 1063 1064 // TestIntegration_PayloadReceiptNoParentResult is a mini-integration test combining the 1065 // Builder with a full ExecutionTree mempool. We check that the builder does not include 1066 // receipts whose PreviousResult is not already incorporated in the chain. 1067 // 1068 // Here we create 4 consecutive blocks S, A, B, and C, where A contains a valid 1069 // receipt for block S, but blocks B and C have empty payloads. 1070 // 1071 // We populate the mempool with valid receipts for blocks A, and C, but NOT for 1072 // block B. 1073 // 1074 // The expected behaviour is that the builder should not include the receipt for 1075 // block C, because the chain and the mempool do not contain a valid receipt for 1076 // the parent result (block B's result). 1077 // 1078 // ... <- S[ER{parent}] <- A[ER{S}] <- B <- C <- X (candidate) 1079 func (bs *BuilderSuite) TestIntegration_PayloadReceiptNoParentResult() { 1080 // make blocks S, A, B, C 1081 parentReceipt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1082 blockSABC := unittest.ChainFixtureFrom(4, bs.blocks[bs.parentID].Header) 1083 resultS := unittest.ExecutionResultFixture(unittest.WithBlock(blockSABC[0]), unittest.WithPreviousResult(*bs.resultForBlock[bs.parentID])) 1084 receiptSABC := unittest.ReceiptChainFor(blockSABC, resultS) 1085 blockSABC[0].Payload.Receipts = []*flow.ExecutionReceiptMeta{parentReceipt.Meta()} 1086 blockSABC[0].Payload.Results = []*flow.ExecutionResult{&parentReceipt.ExecutionResult} 1087 blockSABC[1].Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptSABC[0].Meta()} 1088 blockSABC[1].Payload.Results = []*flow.ExecutionResult{&receiptSABC[0].ExecutionResult} 1089 blockSABC[2].Payload.Receipts = []*flow.ExecutionReceiptMeta{} 1090 blockSABC[3].Payload.Receipts = []*flow.ExecutionReceiptMeta{} 1091 unittest.ReconnectBlocksAndReceipts(blockSABC, receiptSABC) // update block header so that blocks are chained together 1092 1093 bs.storeBlock(blockSABC[0]) 1094 bs.storeBlock(blockSABC[1]) 1095 bs.storeBlock(blockSABC[2]) 1096 bs.storeBlock(blockSABC[3]) 1097 1098 // Instantiate real Execution Tree mempool; 1099 bs.build.recPool = mempoolImpl.NewExecutionTree() 1100 for _, block := range bs.blocks { 1101 resultByID := block.Payload.Results.Lookup() 1102 for _, meta := range block.Payload.Receipts { 1103 result := resultByID[meta.ResultID] 1104 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1105 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1106 bs.NoError(err) 1107 } 1108 } 1109 // for receipts _not_ included in blocks, add only receipt for A and C but NOT B 1110 _, _ = bs.build.recPool.AddReceipt(receiptSABC[1], blockSABC[1].Header) 1111 _, _ = bs.build.recPool.AddReceipt(receiptSABC[3], blockSABC[3].Header) 1112 1113 _, err := bs.build.BuildOn(blockSABC[3].ID(), bs.setter) 1114 bs.Require().NoError(err) 1115 expectedReceipts := flow.ExecutionReceiptMetaList{receiptSABC[1].Meta()} 1116 expectedResults := flow.ExecutionResultList{&receiptSABC[1].ExecutionResult} 1117 bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain only receipt for block a") 1118 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain only result for block a") 1119 } 1120 1121 // TestIntegration_ExtendDifferentExecutionPathsOnSameFork tests that the 1122 // builder includes receipts that form different valid execution paths contained 1123 // on the current fork. 1124 // 1125 // candidate 1126 // P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- X[ER{B}, ER{B}'] 1127 func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnSameFork() { 1128 1129 // A is a block containing a valid receipt for block P 1130 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1131 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1132 A.SetPayload(flow.Payload{ 1133 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1134 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1135 }) 1136 1137 // B is a block containing two valid receipts, with different results, for 1138 // block A 1139 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1140 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1141 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1142 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1143 B := unittest.BlockWithParentFixture(A.Header) 1144 B.SetPayload(flow.Payload{ 1145 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()}, 1146 Results: []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult}, 1147 }) 1148 1149 bs.storeBlock(A) 1150 bs.storeBlock(B) 1151 1152 // Instantiate real Execution Tree mempool; 1153 bs.build.recPool = mempoolImpl.NewExecutionTree() 1154 for _, block := range bs.blocks { 1155 resultByID := block.Payload.Results.Lookup() 1156 for _, meta := range block.Payload.Receipts { 1157 result := resultByID[meta.ResultID] 1158 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1159 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1160 bs.NoError(err) 1161 } 1162 } 1163 1164 // Create two valid receipts for block B which build on different receipts 1165 // for the parent block (A); recB1 builds on top of RecA1, whilst recB2 1166 // builds on top of RecA2. 1167 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1168 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1169 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1170 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1171 1172 // Add recB1 and recB2 to the mempool for inclusion in the next candidate 1173 _, _ = bs.build.recPool.AddReceipt(recB1, B.Header) 1174 _, _ = bs.build.recPool.AddReceipt(recB2, B.Header) 1175 1176 _, err := bs.build.BuildOn(B.ID(), bs.setter) 1177 bs.Require().NoError(err) 1178 expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta()} 1179 expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult} 1180 bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks") 1181 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks") 1182 } 1183 1184 // TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks tests that the 1185 // builder picks up receipts that were already included in a different fork. 1186 // 1187 // candidate 1188 // P <- A[ER{P}] <- B[ER{A}] <- X[ER{A}',ER{B}, ER{B}'] 1189 // | 1190 // < ------ C[ER{A}'] 1191 // 1192 // Where: 1193 // - ER{A} and ER{A}' are receipts for block A that don't have the same 1194 // result. 1195 // - ER{B} is a receipt for B with parent result ER{A} 1196 // - ER{B}' is a receipt for B with parent result ER{A}' 1197 // 1198 // When buiding on top of B, we expect the candidate payload to contain ER{A}', 1199 // ER{B}, and ER{B}' 1200 // 1201 // ER{P} <- ER{A} <- ER{B} 1202 // | 1203 // < ER{A}' <- ER{B}' 1204 func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks() { 1205 // A is a block containing a valid receipt for block P 1206 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1207 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1208 A.SetPayload(flow.Payload{ 1209 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1210 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1211 }) 1212 1213 // B is a block that builds on A containing a valid receipt for A 1214 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1215 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1216 B := unittest.BlockWithParentFixture(A.Header) 1217 B.SetPayload(flow.Payload{ 1218 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()}, 1219 Results: []*flow.ExecutionResult{&recA1.ExecutionResult}, 1220 }) 1221 1222 // C is another block that builds on A containing a valid receipt for A but 1223 // different from the receipt contained in B 1224 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1225 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1226 C := unittest.BlockWithParentFixture(A.Header) 1227 C.SetPayload(flow.Payload{ 1228 Receipts: []*flow.ExecutionReceiptMeta{recA2.Meta()}, 1229 Results: []*flow.ExecutionResult{&recA2.ExecutionResult}, 1230 }) 1231 1232 bs.storeBlock(A) 1233 bs.storeBlock(B) 1234 bs.storeBlock(C) 1235 1236 // Instantiate real Execution Tree mempool; 1237 bs.build.recPool = mempoolImpl.NewExecutionTree() 1238 for _, block := range bs.blocks { 1239 resultByID := block.Payload.Results.Lookup() 1240 for _, meta := range block.Payload.Receipts { 1241 result := resultByID[meta.ResultID] 1242 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1243 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1244 bs.NoError(err) 1245 } 1246 } 1247 1248 // create and add a receipt for block B which builds on top of recA2, which 1249 // is not on the same execution fork 1250 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1251 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1252 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1253 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1254 1255 _, err := bs.build.recPool.AddReceipt(recB1, B.Header) 1256 bs.Require().NoError(err) 1257 _, err = bs.build.recPool.AddReceipt(recB2, B.Header) 1258 bs.Require().NoError(err) 1259 1260 _, err = bs.build.BuildOn(B.ID(), bs.setter) 1261 bs.Require().NoError(err) 1262 expectedReceipts := []*flow.ExecutionReceiptMeta{recA2.Meta(), recB1.Meta(), recB2.Meta()} 1263 expectedResults := []*flow.ExecutionResult{&recA2.ExecutionResult, &recB1.ExecutionResult, &recB2.ExecutionResult} 1264 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should extend different execution paths") 1265 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should extend different execution paths") 1266 } 1267 1268 // TestIntegration_DuplicateReceipts checks that the builder does not re-include 1269 // receipts that are already incorporated in blocks on the fork. 1270 // 1271 // P <- A(r_P) <- B(r_A) <- X (candidate) 1272 func (bs *BuilderSuite) TestIntegration_DuplicateReceipts() { 1273 // A is a block containing a valid receipt for block P 1274 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1275 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1276 A.SetPayload(flow.Payload{ 1277 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1278 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1279 }) 1280 1281 // B is a block that builds on A containing a valid receipt for A 1282 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1283 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1284 B := unittest.BlockWithParentFixture(A.Header) 1285 B.SetPayload(flow.Payload{ 1286 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()}, 1287 Results: []*flow.ExecutionResult{&recA1.ExecutionResult}, 1288 }) 1289 1290 bs.storeBlock(A) 1291 bs.storeBlock(B) 1292 1293 // Instantiate real Execution Tree mempool; 1294 bs.build.recPool = mempoolImpl.NewExecutionTree() 1295 for _, block := range bs.blocks { 1296 resultByID := block.Payload.Results.Lookup() 1297 for _, meta := range block.Payload.Receipts { 1298 result := resultByID[meta.ResultID] 1299 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1300 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1301 bs.NoError(err) 1302 } 1303 } 1304 1305 _, err := bs.build.BuildOn(B.ID(), bs.setter) 1306 bs.Require().NoError(err) 1307 expectedReceipts := []*flow.ExecutionReceiptMeta{} 1308 expectedResults := []*flow.ExecutionResult{} 1309 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should not include receipts that are already incorporated in the current fork") 1310 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated") 1311 } 1312 1313 // TestIntegration_ResultAlreadyIncorporated checks that the builder includes 1314 // receipts for results that were already incorporated in blocks on the fork. 1315 // 1316 // P <- A(ER[P]) <- X (candidate) 1317 func (bs *BuilderSuite) TestIntegration_ResultAlreadyIncorporated() { 1318 // A is a block containing a valid receipt for block P 1319 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1320 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1321 A.SetPayload(flow.Payload{ 1322 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1323 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1324 }) 1325 1326 recP_B := unittest.ExecutionReceiptFixture(unittest.WithResult(&recP.ExecutionResult)) 1327 1328 bs.storeBlock(A) 1329 1330 // Instantiate real Execution Tree mempool; 1331 bs.build.recPool = mempoolImpl.NewExecutionTree() 1332 for _, block := range bs.blocks { 1333 resultByID := block.Payload.Results.Lookup() 1334 for _, meta := range block.Payload.Receipts { 1335 result := resultByID[meta.ResultID] 1336 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1337 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1338 bs.NoError(err) 1339 } 1340 } 1341 1342 _, err := bs.build.recPool.AddReceipt(recP_B, bs.blocks[recP_B.ExecutionResult.BlockID].Header) 1343 bs.NoError(err) 1344 1345 _, err = bs.build.BuildOn(A.ID(), bs.setter) 1346 bs.Require().NoError(err) 1347 expectedReceipts := []*flow.ExecutionReceiptMeta{recP_B.Meta()} 1348 expectedResults := []*flow.ExecutionResult{} 1349 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should include receipt metas for results that were already incorporated") 1350 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated") 1351 } 1352 1353 func storeSealForIncorporatedResult(result *flow.ExecutionResult, incorporatingBlockID flow.Identifier, pendingSeals map[flow.Identifier]*flow.IncorporatedResultSeal) *flow.IncorporatedResultSeal { 1354 incorporatedResultSeal := unittest.IncorporatedResultSeal.Fixture( 1355 unittest.IncorporatedResultSeal.WithResult(result), 1356 unittest.IncorporatedResultSeal.WithIncorporatedBlockID(incorporatingBlockID), 1357 ) 1358 pendingSeals[incorporatedResultSeal.ID()] = incorporatedResultSeal 1359 return incorporatedResultSeal 1360 } 1361 1362 // TestIntegration_RepopulateExecutionTreeAtStartup tests that the 1363 // builder includes receipts for candidate block after fresh start, meaning 1364 // it will repopulate execution tree in constructor 1365 // 1366 // P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- C <- X[ER{B}, ER{B}', ER{C} ] 1367 // | 1368 // finalized 1369 func (bs *BuilderSuite) TestIntegration_RepopulateExecutionTreeAtStartup() { 1370 // setup initial state 1371 // A is a block containing a valid receipt for block P 1372 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1373 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1374 A.SetPayload(flow.Payload{ 1375 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1376 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1377 }) 1378 1379 // B is a block containing two valid receipts, with different results, for 1380 // block A 1381 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1382 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1383 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1384 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1385 B := unittest.BlockWithParentFixture(A.Header) 1386 B.SetPayload(flow.Payload{ 1387 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()}, 1388 Results: []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult}, 1389 }) 1390 1391 C := unittest.BlockWithParentFixture(B.Header) 1392 1393 bs.storeBlock(A) 1394 bs.storeBlock(B) 1395 bs.storeBlock(C) 1396 1397 // store execution results 1398 for _, block := range []*flow.Block{A, B, C} { 1399 // for current block create empty receipts list 1400 bs.receiptsByBlockID[block.ID()] = flow.ExecutionReceiptList{} 1401 1402 for _, result := range block.Payload.Results { 1403 bs.resultByID[result.ID()] = result 1404 } 1405 for _, meta := range block.Payload.Receipts { 1406 receipt := flow.ExecutionReceiptFromMeta(*meta, *bs.resultByID[meta.ResultID]) 1407 bs.receiptsByID[meta.ID()] = receipt 1408 bs.receiptsByBlockID[receipt.ExecutionResult.BlockID] = append(bs.receiptsByBlockID[receipt.ExecutionResult.BlockID], receipt) 1409 } 1410 } 1411 1412 // mark A as finalized 1413 bs.finalID = A.ID() 1414 1415 // set up no-op dependencies 1416 noopMetrics := metrics.NewNoopCollector() 1417 noopTracer := trace.NewNoopTracer() 1418 1419 // Instantiate real Execution Tree mempool; 1420 recPool := mempoolImpl.NewExecutionTree() 1421 1422 // create builder which has to repopulate execution tree 1423 var err error 1424 bs.build, err = NewBuilder( 1425 noopMetrics, 1426 bs.db, 1427 bs.state, 1428 bs.headerDB, 1429 bs.sealDB, 1430 bs.indexDB, 1431 bs.blockDB, 1432 bs.resultDB, 1433 bs.receiptsDB, 1434 bs.guarPool, 1435 bs.sealPool, 1436 recPool, 1437 noopTracer, 1438 ) 1439 require.NoError(bs.T(), err) 1440 bs.build.cfg.expiry = 11 1441 1442 // Create two valid receipts for block B which build on different receipts 1443 // for the parent block (A); recB1 builds on top of RecA1, whilst recB2 1444 // builds on top of RecA2. 1445 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1446 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1447 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1448 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1449 resC := unittest.ExecutionResultFixture(unittest.WithBlock(C), unittest.WithPreviousResult(recB1.ExecutionResult)) 1450 recC := unittest.ExecutionReceiptFixture(unittest.WithResult(resC)) 1451 1452 // Add recB1 and recB2 to the mempool for inclusion in the next candidate 1453 _, _ = bs.build.recPool.AddReceipt(recB1, B.Header) 1454 _, _ = bs.build.recPool.AddReceipt(recB2, B.Header) 1455 _, _ = bs.build.recPool.AddReceipt(recC, C.Header) 1456 1457 _, err = bs.build.BuildOn(C.ID(), bs.setter) 1458 bs.Require().NoError(err) 1459 expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta(), recC.Meta()} 1460 expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult, &recC.ExecutionResult} 1461 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks") 1462 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks") 1463 }