github.com/koko1123/flow-go-1@v0.29.6/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/v3" 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/koko1123/flow-go-1/model/flow" 15 mempoolAPIs "github.com/koko1123/flow-go-1/module/mempool" 16 mempoolImpl "github.com/koko1123/flow-go-1/module/mempool/consensus" 17 mempool "github.com/koko1123/flow-go-1/module/mempool/mock" 18 "github.com/koko1123/flow-go-1/module/metrics" 19 "github.com/koko1123/flow-go-1/module/trace" 20 realproto "github.com/koko1123/flow-go-1/state/protocol" 21 protocol "github.com/koko1123/flow-go-1/state/protocol/mock" 22 storerr "github.com/koko1123/flow-go-1/storage" 23 "github.com/koko1123/flow-go-1/storage/badger/operation" 24 storage "github.com/koko1123/flow-go-1/storage/mock" 25 "github.com/koko1123/flow-go-1/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.MutableState 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.MutableState{} 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("ValidDescendants").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░: 599 // - Knows block A5. Hence, it knows a QC for block B4, which contains the Source Of Randomness (SOR) for B4. 600 // Therefore, the proposer can construct the verifier assignment for [B4{incorporates result R for B1}] 601 // - Assume that verification was fast enough, so the proposer has sufficient approvals for result R. 602 // Therefore, the proposer has a candidate seal, sealing result R for block B4, in its mempool. 603 // 604 // Replica trying to verify ░newBlock░: 605 // 606 // - Assume that the replica does _not_ know A5. Therefore, it _cannot_ compute the verifier assignment for B4. 607 // 608 // Problem: If the proposer included the seal for B1, the replica could not check it. 609 // 610 // Solution: There must be a gap between the block incorporating the result (here B4) and 611 // the block sealing the result. A gap of one block is sufficient. 612 // 613 // ┌---- [A5] (orphaned fork) 614 // v 615 // ...<- [B0] <- [B1] <- [B2] <- [B3] <- [B4{incorporates result R for B1}] <- [B5] <- [B6{seals B1}] 616 // ~~~~~~ 617 // gap 618 // 619 // We test the two distinct cases: 620 // 621 // (i) Builder does _not_ include seal for B1 when constructing block B5 622 // (ii) Builder _includes_ seal for B1 when constructing block B6 623 func (bs *BuilderSuite) TestPayloadSeals_EnforceGap() { 624 // we use bs.parentID as block B0 625 b0result := bs.resultForBlock[bs.parentID] 626 b0seal := unittest.Seal.Fixture(unittest.Seal.WithResult(b0result)) 627 628 // create blocks B1 to B4: 629 b1 := bs.createAndRecordBlock(bs.blocks[bs.parentID], true) 630 bchain := unittest.ChainFixtureFrom(3, b1.Header) // creates blocks b2, b3, b4 631 b4 := bchain[2] 632 633 // Incorporate result for block B1 into payload of block B4 634 resultB1 := bs.resultForBlock[b1.ID()] 635 receiptB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultB1)) 636 b4.SetPayload( 637 flow.Payload{ 638 Results: []*flow.ExecutionResult{&receiptB1.ExecutionResult}, 639 Receipts: []*flow.ExecutionReceiptMeta{receiptB1.Meta()}, 640 }) 641 642 // add blocks B2, B3, B4, A5 to the mocked storage layer (block b0 and b1 are already added): 643 a5 := unittest.BlockWithParentFixture(b4.Header) 644 for _, b := range append(bchain, a5) { 645 bs.storeBlock(b) 646 } 647 648 // mock for of candidate seal mempool: 649 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 650 b1seal := storeSealForIncorporatedResult(resultB1, b4.ID(), bs.pendingSeals) 651 652 // mock for seals storage layer: 653 bs.sealDB = &storage.Seals{} 654 bs.build.seals = bs.sealDB 655 656 bs.T().Run("Build on top of B4 and check that no seals are included", func(t *testing.T) { 657 bs.sealDB.On("HighestInFork", b4.ID()).Return(b0seal, nil) 658 659 _, err := bs.build.BuildOn(b4.ID(), bs.setter) 660 require.NoError(t, err) 661 bs.recPool.AssertExpectations(t) 662 require.Empty(t, bs.assembled.Seals, "should not include any seals") 663 }) 664 665 bs.T().Run("Build on top of B5 and check that seals for B1 is included", func(t *testing.T) { 666 b5 := unittest.BlockWithParentFixture(b4.Header) // creating block b5 667 bs.storeBlock(b5) 668 bs.sealDB.On("HighestInFork", b5.ID()).Return(b0seal, nil) 669 670 _, err := bs.build.BuildOn(b5.ID(), bs.setter) 671 require.NoError(t, err) 672 bs.recPool.AssertExpectations(t) 673 require.Equal(t, 1, len(bs.assembled.Seals), "only seal for B1 expected") 674 require.Equal(t, b1seal.Seal, bs.assembled.Seals[0]) 675 }) 676 } 677 678 // TestPayloadSeals_Duplicates verifies that the builder does not duplicate seals for already sealed blocks: 679 // 680 // ... <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 681 // 682 // Where block 683 // - [F0] ... [F3] sealed blocks but their candidate seals are still included in mempool 684 // - [A0] ... [A3] unsealed blocks with candidate seals are included in mempool 685 // 686 // Expected behaviour: 687 // - builder should only include seals [A0], ..., [A3] 688 func (bs *BuilderSuite) TestPayloadSeals_Duplicate() { 689 // Pretend that the first n blocks are already sealed 690 n := 4 691 lastSeal := bs.chain[n-1] 692 mockSealDB := &storage.Seals{} 693 mockSealDB.On("HighestInFork", mock.Anything).Return(lastSeal, nil) 694 bs.build.seals = mockSealDB 695 696 // seals for all blocks [F0], ..., [A3] are still in the mempool: 697 bs.pendingSeals = bs.irsMap 698 699 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 700 bs.Require().NoError(err) 701 bs.Assert().Equal(bs.chain[n:], bs.assembled.Seals, "should have rejected duplicate seals") 702 } 703 704 // TestPayloadSeals_MissingNextSeal checks how the builder handles the fork 705 // 706 // [S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 707 // 708 // Where block 709 // - [S] is sealed and finalized 710 // - [F0] finalized, unsealed block but _without_ candidate seal in mempool 711 // - [F1] ... [F3] are finalized, unsealed blocks with candidate seals are included in mempool 712 // - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool 713 // 714 // Expected behaviour: 715 // - builder should not include any seals as the immediately next seal is not in mempool 716 func (bs *BuilderSuite) TestPayloadSeals_MissingNextSeal() { 717 // remove the seal for block [F0] 718 firstSeal := bs.irsList[0] 719 delete(bs.irsMap, firstSeal.ID()) 720 bs.pendingSeals = bs.irsMap 721 722 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 723 bs.Require().NoError(err) 724 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 725 bs.Assert().Empty(bs.assembled.Seals, "should not have included any seals from cutoff chain") 726 } 727 728 // TestPayloadSeals_MissingInterimSeal checks how the builder handles the fork 729 // 730 // [S] <- [F0] <- [F1] <- [F2] <- [F3] <- [A0] <- [A1] <- [A2] <- [A3] 731 // 732 // Where block 733 // - [S] is sealed and finalized 734 // - [F0] ... [F2] are finalized, unsealed blocks with candidate seals are included in mempool 735 // - [F4] finalized, unsealed block but _without_ candidate seal in mempool 736 // - [A0] ... [A3] non-finalized, unsealed blocks with candidate seals are included in mempool 737 // 738 // Expected behaviour: 739 // - builder should only include candidate seals for [F0], [F1], [F2] 740 func (bs *BuilderSuite) TestPayloadSeals_MissingInterimSeal() { 741 // remove a seal for block [F4] 742 seal := bs.irsList[3] 743 delete(bs.irsMap, seal.ID()) 744 bs.pendingSeals = bs.irsMap 745 746 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 747 bs.Require().NoError(err) 748 bs.Assert().Empty(bs.assembled.Guarantees, "should have no guarantees in payload with empty mempool") 749 bs.Assert().ElementsMatch(bs.chain[:3], bs.assembled.Seals, "should have included only beginning of broken chain") 750 } 751 752 // TestValidatePayloadSeals_ExecutionForks checks how the builder's seal-inclusion logic 753 // handles execution forks. 754 // 755 // we have the chain in storage: 756 // 757 // F <- A{Result[F]_1, Result[F]_2, ReceiptMeta[F]_1, ReceiptMeta[F]_2} 758 // <- B{Result[A]_1, Result[A]_2, ReceiptMeta[A]_1, ReceiptMeta[A]_2} 759 // <- C{Result[B]_1, Result[B]_2, ReceiptMeta[B]_1, ReceiptMeta[B]_2} 760 // <- D{Seal for Result[F]_1} 761 // 762 // here F is the latest finalized block (with ID bs.finalID) 763 // 764 // Note that we are explicitly testing the handling of an execution fork that 765 // was incorporated _before_ the seal 766 // 767 // Blocks: F <----------- A <----------- B 768 // Results: Result[F]_1 <- Result[A]_1 <- Result[B]_1 :: the root of this execution tree is sealed 769 // Result[F]_2 <- Result[A]_2 <- Result[B]_2 :: the root of this execution tree conflicts with sealed result 770 // 771 // The builder is tasked with creating the payload for block X: 772 // 773 // F <- A{..} <- B{..} <- C{..} <- D{..} <- X 774 // 775 // We test the two distinct cases: 776 // 777 // (i) verify that execution fork conflicting with sealed result is not sealed 778 // (ii) verify that multiple execution forks are properly handled 779 func (bs *BuilderSuite) TestValidatePayloadSeals_ExecutionForks() { 780 bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past [lastSeal] 781 782 blockF := bs.blocks[bs.finalID] 783 blocks := []*flow.Block{blockF} 784 blocks = append(blocks, unittest.ChainFixtureFrom(4, blockF.Header)...) // elements [F, A, B, C, D] 785 receiptChain1 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[F]_1, Result[A]_1, Result[B]_1, ...] 786 receiptChain2 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[F]_2, Result[A]_2, Result[B]_2, ...] 787 788 for i := 1; i <= 3; i++ { // set payload for blocks A, B, C 789 blocks[i].SetPayload(flow.Payload{ 790 Results: []*flow.ExecutionResult{&receiptChain1[i-1].ExecutionResult, &receiptChain2[i-1].ExecutionResult}, 791 Receipts: []*flow.ExecutionReceiptMeta{receiptChain1[i-1].Meta(), receiptChain2[i-1].Meta()}, 792 }) 793 } 794 sealedResult := receiptChain1[0].ExecutionResult 795 sealF := unittest.Seal.Fixture(unittest.Seal.WithResult(&sealedResult)) 796 blocks[4].SetPayload(flow.Payload{ // set payload for block D 797 Seals: []*flow.Seal{sealF}, 798 }) 799 for i := 0; i <= 4; i++ { 800 // we need to run this several times, as in each iteration as we have _multiple_ execution chains. 801 // In each iteration, we only mange to reconnect one additional height 802 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain1) 803 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain2) 804 } 805 806 for _, b := range blocks { 807 bs.storeBlock(b) 808 } 809 bs.sealDB = &storage.Seals{} 810 bs.build.seals = bs.sealDB 811 bs.sealDB.On("HighestInFork", mock.Anything).Return(sealF, nil) 812 bs.resultByID[sealedResult.ID()] = &sealedResult 813 814 bs.T().Run("verify that execution fork conflicting with sealed result is not sealed", func(t *testing.T) { 815 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 816 storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 817 818 _, err := bs.build.BuildOn(blocks[4].ID(), bs.setter) 819 require.NoError(t, err) 820 require.Empty(t, bs.assembled.Seals, "should not have included seal for conflicting execution fork") 821 }) 822 823 bs.T().Run("verify that multiple execution forks are properly handled", func(t *testing.T) { 824 bs.pendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 825 sealResultA_1 := storeSealForIncorporatedResult(&receiptChain1[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 826 sealResultB_1 := storeSealForIncorporatedResult(&receiptChain1[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals) 827 storeSealForIncorporatedResult(&receiptChain2[1].ExecutionResult, blocks[2].ID(), bs.pendingSeals) 828 storeSealForIncorporatedResult(&receiptChain2[2].ExecutionResult, blocks[3].ID(), bs.pendingSeals) 829 830 _, err := bs.build.BuildOn(blocks[4].ID(), bs.setter) 831 require.NoError(t, err) 832 require.ElementsMatch(t, []*flow.Seal{sealResultA_1.Seal, sealResultB_1.Seal}, bs.assembled.Seals, "valid fork should have been sealed") 833 }) 834 } 835 836 // TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult tests the receipt selection: 837 // Expectation: Builder should trigger ExecutionTree to search Execution Tree from 838 // last sealed result on respective fork. 839 // 840 // We test with the following main chain tree 841 // 842 // ┌-[X0] <- [X1{seals ..F4}] 843 // v 844 // [lastSeal] <- [F0] <- [F1] <- [F2] <- [F3] <- [F4] <- [A0] <- [A1{seals ..F2}] <- [A2] <- [A3] 845 // 846 // Where 847 // * blocks [lastSeal], [F1], ... [F4], [A0], ... [A4], are created by BuilderSuite 848 // * latest sealed block for a specific fork is provided by test-local seals storage mock 849 func (bs *BuilderSuite) TestPayloadReceipts_TraverseExecutionTreeFromLastSealedResult() { 850 bs.build.cfg.expiry = 4 // reduce expiry so collection dedup algorithm doesn't walk past [lastSeal] 851 x0 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 852 x1 := bs.createAndRecordBlock(x0, true) 853 854 // set last sealed blocks: 855 f2 := bs.blocks[bs.finalizedBlockIDs[2]] 856 f2eal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[f2.ID()])) 857 f4Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[bs.finalID])) 858 bs.sealDB = &storage.Seals{} 859 bs.build.seals = bs.sealDB 860 861 // reset receipts mempool to verify calls made by Builder 862 bs.recPool = &mempool.ExecutionTree{} 863 bs.recPool.On("Size").Return(uint(0)).Maybe() 864 bs.build.recPool = bs.recPool 865 866 // building on top of X0: latest finalized block in fork is [lastSeal]; expect search to start with sealed result 867 bs.sealDB.On("HighestInFork", x0.ID()).Return(bs.lastSeal, nil) 868 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 869 _, err := bs.build.BuildOn(x0.ID(), bs.setter) 870 bs.Require().NoError(err) 871 bs.recPool.AssertExpectations(bs.T()) 872 873 // building on top of X1: latest finalized block in fork is [F4]; expect search to start with sealed result 874 bs.sealDB.On("HighestInFork", x1.ID()).Return(f4Seal, nil) 875 bs.recPool.On("ReachableReceipts", f4Seal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 876 _, err = bs.build.BuildOn(x1.ID(), bs.setter) 877 bs.Require().NoError(err) 878 bs.recPool.AssertExpectations(bs.T()) 879 880 // building on top of A3 (with ID bs.parentID): latest finalized block in fork is [F4]; expect search to start with sealed result 881 bs.sealDB.On("HighestInFork", bs.parentID).Return(f2eal, nil) 882 bs.recPool.On("ReachableReceipts", f2eal.ResultID, mock.Anything, mock.Anything).Return([]*flow.ExecutionReceipt{}, nil).Once() 883 _, err = bs.build.BuildOn(bs.parentID, bs.setter) 884 bs.Require().NoError(err) 885 bs.recPool.AssertExpectations(bs.T()) 886 } 887 888 // TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork tests the receipt selection: 889 // In this test, we check that the Builder provides a BlockFilter which only allows 890 // blocks on the fork, which we are extending. We construct the following chain tree: 891 // 892 // ┌--[X1] ┌-[Y2] ┌-- [A6] 893 // v v v 894 // <- [Final] <- [*B1*] <- [*B2*] <- [*B3*] <- [*B4{seals B1}*] <- [*B5*] <- ░newBlock░ 895 // ^ 896 // └-- [C3] <- [C4] 897 // ^--- [D4] 898 // 899 // Expectation: BlockFilter should pass blocks marked with star: B1, ... ,B5 900 // All other blocks should be filtered out. 901 // 902 // Context: 903 // While the receipt selection itself is performed by the ExecutionTree, the Builder 904 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 905 func (bs *BuilderSuite) TestPayloadReceipts_IncludeOnlyReceiptsForCurrentFork() { 906 b1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 907 b2 := bs.createAndRecordBlock(b1, true) 908 b3 := bs.createAndRecordBlock(b2, true) 909 b4 := bs.createAndRecordBlock(b3, true) 910 b5 := bs.createAndRecordBlock(b4, true) 911 912 x1 := bs.createAndRecordBlock(bs.blocks[bs.finalID], true) 913 y2 := bs.createAndRecordBlock(b1, true) 914 a6 := bs.createAndRecordBlock(b5, true) 915 916 c3 := bs.createAndRecordBlock(b2, true) 917 c4 := bs.createAndRecordBlock(c3, true) 918 d4 := bs.createAndRecordBlock(c3, true) 919 920 // set last sealed blocks: 921 b1Seal := unittest.Seal.Fixture(unittest.Seal.WithResult(bs.resultForBlock[b1.ID()])) 922 bs.sealDB = &storage.Seals{} 923 bs.sealDB.On("HighestInFork", b5.ID()).Return(b1Seal, nil) 924 bs.build.seals = bs.sealDB 925 926 // setup mock to test the BlockFilter provided by Builder 927 bs.recPool = &mempool.ExecutionTree{} 928 bs.recPool.On("Size").Return(uint(0)).Maybe() 929 bs.recPool.On("ReachableReceipts", b1Seal.ResultID, mock.Anything, mock.Anything).Run( 930 func(args mock.Arguments) { 931 blockFilter := args[1].(mempoolAPIs.BlockFilter) 932 for _, h := range []*flow.Header{b1.Header, b2.Header, b3.Header, b4.Header, b5.Header} { 933 assert.True(bs.T(), blockFilter(h)) 934 } 935 for _, h := range []*flow.Header{bs.blocks[bs.finalID].Header, x1.Header, y2.Header, a6.Header, c3.Header, c4.Header, d4.Header} { 936 assert.False(bs.T(), blockFilter(h)) 937 } 938 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 939 bs.build.recPool = bs.recPool 940 941 _, err := bs.build.BuildOn(b5.ID(), bs.setter) 942 bs.Require().NoError(err) 943 bs.recPool.AssertExpectations(bs.T()) 944 } 945 946 // TestPayloadReceipts_SkipDuplicatedReceipts tests the receipt selection: 947 // Expectation: we check that the Builder provides a ReceiptFilter which 948 // filters out duplicated receipts. 949 // Comment: 950 // While the receipt selection itself is performed by the ExecutionTree, the Builder 951 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 952 func (bs *BuilderSuite) TestPayloadReceipts_SkipDuplicatedReceipts() { 953 // setup mock to test the ReceiptFilter provided by Builder 954 bs.recPool = &mempool.ExecutionTree{} 955 bs.recPool.On("Size").Return(uint(0)).Maybe() 956 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run( 957 func(args mock.Arguments) { 958 receiptFilter := args[2].(mempoolAPIs.ReceiptFilter) 959 // verify that all receipts already included in blocks are filtered out: 960 for _, block := range bs.blocks { 961 resultByID := block.Payload.Results.Lookup() 962 for _, meta := range block.Payload.Receipts { 963 result := resultByID[meta.ResultID] 964 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 965 assert.False(bs.T(), receiptFilter(rcpt)) 966 } 967 } 968 // Verify that receipts for unsealed blocks, which are _not_ already incorporated are accepted: 969 for _, block := range bs.blocks { 970 if block.ID() != bs.firstID { // block with ID bs.firstID is already sealed 971 rcpt := unittest.ReceiptForBlockFixture(block) 972 assert.True(bs.T(), receiptFilter(rcpt)) 973 } 974 } 975 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 976 bs.build.recPool = bs.recPool 977 978 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 979 bs.Require().NoError(err) 980 bs.recPool.AssertExpectations(bs.T()) 981 } 982 983 // TestPayloadReceipts_SkipReceiptsForSealedBlock tests the receipt selection: 984 // Expectation: we check that the Builder provides a ReceiptFilter which 985 // filters out _any_ receipt for the sealed block. 986 // 987 // Comment: 988 // While the receipt selection itself is performed by the ExecutionTree, the Builder 989 // controls the selection by providing suitable BlockFilter and ReceiptFilter. 990 func (bs *BuilderSuite) TestPayloadReceipts_SkipReceiptsForSealedBlock() { 991 // setup mock to test the ReceiptFilter provided by Builder 992 bs.recPool = &mempool.ExecutionTree{} 993 bs.recPool.On("Size").Return(uint(0)).Maybe() 994 bs.recPool.On("ReachableReceipts", bs.lastSeal.ResultID, mock.Anything, mock.Anything).Run( 995 func(args mock.Arguments) { 996 receiptFilter := args[2].(mempoolAPIs.ReceiptFilter) 997 998 // receipt for sealed block committing to same result as the sealed result 999 rcpt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.firstID])) 1000 assert.False(bs.T(), receiptFilter(rcpt)) 1001 1002 // receipt for sealed block committing to different result as the sealed result 1003 rcpt = unittest.ReceiptForBlockFixture(bs.blocks[bs.firstID]) 1004 assert.False(bs.T(), receiptFilter(rcpt)) 1005 }).Return([]*flow.ExecutionReceipt{}, nil).Once() 1006 bs.build.recPool = bs.recPool 1007 1008 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1009 bs.Require().NoError(err) 1010 bs.recPool.AssertExpectations(bs.T()) 1011 } 1012 1013 // TestPayloadReceipts_BlockLimit tests that the builder does not include more 1014 // receipts than the configured maxReceiptCount. 1015 func (bs *BuilderSuite) TestPayloadReceipts_BlockLimit() { 1016 1017 // Populate the mempool with 5 valid receipts 1018 receipts := []*flow.ExecutionReceipt{} 1019 metas := []*flow.ExecutionReceiptMeta{} 1020 expectedResults := []*flow.ExecutionResult{} 1021 var i uint64 1022 for i = 0; i < 5; i++ { 1023 blockOnFork := bs.blocks[bs.irsList[i].Seal.BlockID] 1024 pendingReceipt := unittest.ReceiptForBlockFixture(blockOnFork) 1025 receipts = append(receipts, pendingReceipt) 1026 metas = append(metas, pendingReceipt.Meta()) 1027 expectedResults = append(expectedResults, &pendingReceipt.ExecutionResult) 1028 } 1029 bs.pendingReceipts = receipts 1030 1031 // set maxReceiptCount to 3 1032 var limit uint = 3 1033 bs.build.cfg.maxReceiptCount = limit 1034 1035 // ensure that only 3 of the 5 receipts were included 1036 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1037 bs.Require().NoError(err) 1038 bs.Assert().ElementsMatch(metas[:limit], bs.assembled.Receipts, "should have excluded receipts above maxReceiptCount") 1039 bs.Assert().ElementsMatch(expectedResults[:limit], bs.assembled.Results, "should have excluded results above maxReceiptCount") 1040 } 1041 1042 // TestPayloadReceipts_AsProvidedByReceiptForest tests the receipt selection. 1043 // Expectation: Builder should embed the Receipts as provided by the ExecutionTree 1044 func (bs *BuilderSuite) TestPayloadReceipts_AsProvidedByReceiptForest() { 1045 var expectedReceipts []*flow.ExecutionReceipt 1046 var expectedMetas []*flow.ExecutionReceiptMeta 1047 var expectedResults []*flow.ExecutionResult 1048 for i := 0; i < 10; i++ { 1049 expectedReceipts = append(expectedReceipts, unittest.ExecutionReceiptFixture()) 1050 expectedMetas = append(expectedMetas, expectedReceipts[i].Meta()) 1051 expectedResults = append(expectedResults, &expectedReceipts[i].ExecutionResult) 1052 } 1053 bs.recPool = &mempool.ExecutionTree{} 1054 bs.recPool.On("Size").Return(uint(0)).Maybe() 1055 bs.recPool.On("AddResult", mock.Anything, mock.Anything).Return(nil).Maybe() 1056 bs.recPool.On("ReachableReceipts", mock.Anything, mock.Anything, mock.Anything).Return(expectedReceipts, nil).Once() 1057 bs.build.recPool = bs.recPool 1058 1059 _, err := bs.build.BuildOn(bs.parentID, bs.setter) 1060 bs.Require().NoError(err) 1061 bs.Assert().ElementsMatch(expectedMetas, bs.assembled.Receipts, "should include receipts as returned by ExecutionTree") 1062 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "should include results as returned by ExecutionTree") 1063 bs.recPool.AssertExpectations(bs.T()) 1064 } 1065 1066 // TestIntegration_PayloadReceiptNoParentResult is a mini-integration test combining the 1067 // Builder with a full ExecutionTree mempool. We check that the builder does not include 1068 // receipts whose PreviousResult is not already incorporated in the chain. 1069 // 1070 // Here we create 4 consecutive blocks S, A, B, and C, where A contains a valid 1071 // receipt for block S, but blocks B and C have empty payloads. 1072 // 1073 // We populate the mempool with valid receipts for blocks A, and C, but NOT for 1074 // block B. 1075 // 1076 // The expected behaviour is that the builder should not include the receipt for 1077 // block C, because the chain and the mempool do not contain a valid receipt for 1078 // the parent result (block B's result). 1079 // 1080 // ... <- S[ER{parent}] <- A[ER{S}] <- B <- C <- X (candidate) 1081 func (bs *BuilderSuite) TestIntegration_PayloadReceiptNoParentResult() { 1082 // make blocks S, A, B, C 1083 parentReceipt := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1084 blockSABC := unittest.ChainFixtureFrom(4, bs.blocks[bs.parentID].Header) 1085 resultS := unittest.ExecutionResultFixture(unittest.WithBlock(blockSABC[0]), unittest.WithPreviousResult(*bs.resultForBlock[bs.parentID])) 1086 receiptSABC := unittest.ReceiptChainFor(blockSABC, resultS) 1087 blockSABC[0].Payload.Receipts = []*flow.ExecutionReceiptMeta{parentReceipt.Meta()} 1088 blockSABC[0].Payload.Results = []*flow.ExecutionResult{&parentReceipt.ExecutionResult} 1089 blockSABC[1].Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptSABC[0].Meta()} 1090 blockSABC[1].Payload.Results = []*flow.ExecutionResult{&receiptSABC[0].ExecutionResult} 1091 blockSABC[2].Payload.Receipts = []*flow.ExecutionReceiptMeta{} 1092 blockSABC[3].Payload.Receipts = []*flow.ExecutionReceiptMeta{} 1093 unittest.ReconnectBlocksAndReceipts(blockSABC, receiptSABC) // update block header so that blocks are chained together 1094 1095 bs.storeBlock(blockSABC[0]) 1096 bs.storeBlock(blockSABC[1]) 1097 bs.storeBlock(blockSABC[2]) 1098 bs.storeBlock(blockSABC[3]) 1099 1100 // Instantiate real Execution Tree mempool; 1101 bs.build.recPool = mempoolImpl.NewExecutionTree() 1102 for _, block := range bs.blocks { 1103 resultByID := block.Payload.Results.Lookup() 1104 for _, meta := range block.Payload.Receipts { 1105 result := resultByID[meta.ResultID] 1106 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1107 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1108 bs.NoError(err) 1109 } 1110 } 1111 // for receipts _not_ included in blocks, add only receipt for A and C but NOT B 1112 _, _ = bs.build.recPool.AddReceipt(receiptSABC[1], blockSABC[1].Header) 1113 _, _ = bs.build.recPool.AddReceipt(receiptSABC[3], blockSABC[3].Header) 1114 1115 _, err := bs.build.BuildOn(blockSABC[3].ID(), bs.setter) 1116 bs.Require().NoError(err) 1117 expectedReceipts := flow.ExecutionReceiptMetaList{receiptSABC[1].Meta()} 1118 expectedResults := flow.ExecutionResultList{&receiptSABC[1].ExecutionResult} 1119 bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain only receipt for block a") 1120 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain only result for block a") 1121 } 1122 1123 // TestIntegration_ExtendDifferentExecutionPathsOnSameFork tests that the 1124 // builder includes receipts that form different valid execution paths contained 1125 // on the current fork. 1126 // 1127 // candidate 1128 // P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- X[ER{B}, ER{B}'] 1129 func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnSameFork() { 1130 1131 // A is a block containing a valid receipt for block P 1132 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1133 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1134 A.SetPayload(flow.Payload{ 1135 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1136 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1137 }) 1138 1139 // B is a block containing two valid receipts, with different results, for 1140 // block A 1141 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1142 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1143 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1144 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1145 B := unittest.BlockWithParentFixture(A.Header) 1146 B.SetPayload(flow.Payload{ 1147 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()}, 1148 Results: []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult}, 1149 }) 1150 1151 bs.storeBlock(A) 1152 bs.storeBlock(B) 1153 1154 // Instantiate real Execution Tree mempool; 1155 bs.build.recPool = mempoolImpl.NewExecutionTree() 1156 for _, block := range bs.blocks { 1157 resultByID := block.Payload.Results.Lookup() 1158 for _, meta := range block.Payload.Receipts { 1159 result := resultByID[meta.ResultID] 1160 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1161 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1162 bs.NoError(err) 1163 } 1164 } 1165 1166 // Create two valid receipts for block B which build on different receipts 1167 // for the parent block (A); recB1 builds on top of RecA1, whilst recB2 1168 // builds on top of RecA2. 1169 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1170 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1171 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1172 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1173 1174 // Add recB1 and recB2 to the mempool for inclusion in the next candidate 1175 _, _ = bs.build.recPool.AddReceipt(recB1, B.Header) 1176 _, _ = bs.build.recPool.AddReceipt(recB2, B.Header) 1177 1178 _, err := bs.build.BuildOn(B.ID(), bs.setter) 1179 bs.Require().NoError(err) 1180 expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta()} 1181 expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult} 1182 bs.Assert().Equal(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks") 1183 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks") 1184 } 1185 1186 // TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks tests that the 1187 // builder picks up receipts that were already included in a different fork. 1188 // 1189 // candidate 1190 // P <- A[ER{P}] <- B[ER{A}] <- X[ER{A}',ER{B}, ER{B}'] 1191 // | 1192 // < ------ C[ER{A}'] 1193 // 1194 // Where: 1195 // - ER{A} and ER{A}' are receipts for block A that don't have the same 1196 // result. 1197 // - ER{B} is a receipt for B with parent result ER{A} 1198 // - ER{B}' is a receipt for B with parent result ER{A}' 1199 // 1200 // When buiding on top of B, we expect the candidate payload to contain ER{A}', 1201 // ER{B}, and ER{B}' 1202 // 1203 // ER{P} <- ER{A} <- ER{B} 1204 // | 1205 // < ER{A}' <- ER{B}' 1206 func (bs *BuilderSuite) TestIntegration_ExtendDifferentExecutionPathsOnDifferentForks() { 1207 // A is a block containing a valid receipt for block P 1208 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1209 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1210 A.SetPayload(flow.Payload{ 1211 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1212 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1213 }) 1214 1215 // B is a block that builds on A containing a valid receipt for A 1216 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1217 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1218 B := unittest.BlockWithParentFixture(A.Header) 1219 B.SetPayload(flow.Payload{ 1220 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()}, 1221 Results: []*flow.ExecutionResult{&recA1.ExecutionResult}, 1222 }) 1223 1224 // C is another block that builds on A containing a valid receipt for A but 1225 // different from the receipt contained in B 1226 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1227 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1228 C := unittest.BlockWithParentFixture(A.Header) 1229 C.SetPayload(flow.Payload{ 1230 Receipts: []*flow.ExecutionReceiptMeta{recA2.Meta()}, 1231 Results: []*flow.ExecutionResult{&recA2.ExecutionResult}, 1232 }) 1233 1234 bs.storeBlock(A) 1235 bs.storeBlock(B) 1236 bs.storeBlock(C) 1237 1238 // Instantiate real Execution Tree mempool; 1239 bs.build.recPool = mempoolImpl.NewExecutionTree() 1240 for _, block := range bs.blocks { 1241 resultByID := block.Payload.Results.Lookup() 1242 for _, meta := range block.Payload.Receipts { 1243 result := resultByID[meta.ResultID] 1244 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1245 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1246 bs.NoError(err) 1247 } 1248 } 1249 1250 // create and add a receipt for block B which builds on top of recA2, which 1251 // is not on the same execution fork 1252 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1253 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1254 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1255 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1256 1257 _, err := bs.build.recPool.AddReceipt(recB1, B.Header) 1258 bs.Require().NoError(err) 1259 _, err = bs.build.recPool.AddReceipt(recB2, B.Header) 1260 bs.Require().NoError(err) 1261 1262 _, err = bs.build.BuildOn(B.ID(), bs.setter) 1263 bs.Require().NoError(err) 1264 expectedReceipts := []*flow.ExecutionReceiptMeta{recA2.Meta(), recB1.Meta(), recB2.Meta()} 1265 expectedResults := []*flow.ExecutionResult{&recA2.ExecutionResult, &recB1.ExecutionResult, &recB2.ExecutionResult} 1266 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should extend different execution paths") 1267 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should extend different execution paths") 1268 } 1269 1270 // TestIntegration_DuplicateReceipts checks that the builder does not re-include 1271 // receipts that are already incorporated in blocks on the fork. 1272 // 1273 // P <- A(r_P) <- B(r_A) <- X (candidate) 1274 func (bs *BuilderSuite) TestIntegration_DuplicateReceipts() { 1275 // A is a block containing a valid receipt for block P 1276 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1277 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1278 A.SetPayload(flow.Payload{ 1279 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1280 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1281 }) 1282 1283 // B is a block that builds on A containing a valid receipt for A 1284 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1285 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1286 B := unittest.BlockWithParentFixture(A.Header) 1287 B.SetPayload(flow.Payload{ 1288 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta()}, 1289 Results: []*flow.ExecutionResult{&recA1.ExecutionResult}, 1290 }) 1291 1292 bs.storeBlock(A) 1293 bs.storeBlock(B) 1294 1295 // Instantiate real Execution Tree mempool; 1296 bs.build.recPool = mempoolImpl.NewExecutionTree() 1297 for _, block := range bs.blocks { 1298 resultByID := block.Payload.Results.Lookup() 1299 for _, meta := range block.Payload.Receipts { 1300 result := resultByID[meta.ResultID] 1301 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1302 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1303 bs.NoError(err) 1304 } 1305 } 1306 1307 _, err := bs.build.BuildOn(B.ID(), bs.setter) 1308 bs.Require().NoError(err) 1309 expectedReceipts := []*flow.ExecutionReceiptMeta{} 1310 expectedResults := []*flow.ExecutionResult{} 1311 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should not include receipts that are already incorporated in the current fork") 1312 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated") 1313 } 1314 1315 // TestIntegration_ResultAlreadyIncorporated checks that the builder includes 1316 // receipts for results that were already incorporated in blocks on the fork. 1317 // 1318 // P <- A(ER[P]) <- X (candidate) 1319 func (bs *BuilderSuite) TestIntegration_ResultAlreadyIncorporated() { 1320 // A is a block containing a valid receipt for block P 1321 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1322 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1323 A.SetPayload(flow.Payload{ 1324 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1325 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1326 }) 1327 1328 recP_B := unittest.ExecutionReceiptFixture(unittest.WithResult(&recP.ExecutionResult)) 1329 1330 bs.storeBlock(A) 1331 1332 // Instantiate real Execution Tree mempool; 1333 bs.build.recPool = mempoolImpl.NewExecutionTree() 1334 for _, block := range bs.blocks { 1335 resultByID := block.Payload.Results.Lookup() 1336 for _, meta := range block.Payload.Receipts { 1337 result := resultByID[meta.ResultID] 1338 rcpt := flow.ExecutionReceiptFromMeta(*meta, *result) 1339 _, err := bs.build.recPool.AddReceipt(rcpt, bs.blocks[rcpt.ExecutionResult.BlockID].Header) 1340 bs.NoError(err) 1341 } 1342 } 1343 1344 _, err := bs.build.recPool.AddReceipt(recP_B, bs.blocks[recP_B.ExecutionResult.BlockID].Header) 1345 bs.NoError(err) 1346 1347 _, err = bs.build.BuildOn(A.ID(), bs.setter) 1348 bs.Require().NoError(err) 1349 expectedReceipts := []*flow.ExecutionReceiptMeta{recP_B.Meta()} 1350 expectedResults := []*flow.ExecutionResult{} 1351 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "builder should include receipt metas for results that were already incorporated") 1352 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "builder should not include results that were already incorporated") 1353 } 1354 1355 func storeSealForIncorporatedResult(result *flow.ExecutionResult, incorporatingBlockID flow.Identifier, pendingSeals map[flow.Identifier]*flow.IncorporatedResultSeal) *flow.IncorporatedResultSeal { 1356 incorporatedResultSeal := unittest.IncorporatedResultSeal.Fixture( 1357 unittest.IncorporatedResultSeal.WithResult(result), 1358 unittest.IncorporatedResultSeal.WithIncorporatedBlockID(incorporatingBlockID), 1359 ) 1360 pendingSeals[incorporatedResultSeal.ID()] = incorporatedResultSeal 1361 return incorporatedResultSeal 1362 } 1363 1364 // TestIntegration_RepopulateExecutionTreeAtStartup tests that the 1365 // builder includes receipts for candidate block after fresh start, meaning 1366 // it will repopulate execution tree in constructor 1367 // 1368 // P <- A[ER{P}] <- B[ER{A}, ER{A}'] <- C <- X[ER{B}, ER{B}', ER{C} ] 1369 // | 1370 // finalized 1371 func (bs *BuilderSuite) TestIntegration_RepopulateExecutionTreeAtStartup() { 1372 // setup initial state 1373 // A is a block containing a valid receipt for block P 1374 recP := unittest.ExecutionReceiptFixture(unittest.WithResult(bs.resultForBlock[bs.parentID])) 1375 A := unittest.BlockWithParentFixture(bs.headers[bs.parentID]) 1376 A.SetPayload(flow.Payload{ 1377 Receipts: []*flow.ExecutionReceiptMeta{recP.Meta()}, 1378 Results: []*flow.ExecutionResult{&recP.ExecutionResult}, 1379 }) 1380 1381 // B is a block containing two valid receipts, with different results, for 1382 // block A 1383 resA1 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1384 recA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA1)) 1385 resA2 := unittest.ExecutionResultFixture(unittest.WithBlock(A), unittest.WithPreviousResult(recP.ExecutionResult)) 1386 recA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resA2)) 1387 B := unittest.BlockWithParentFixture(A.Header) 1388 B.SetPayload(flow.Payload{ 1389 Receipts: []*flow.ExecutionReceiptMeta{recA1.Meta(), recA2.Meta()}, 1390 Results: []*flow.ExecutionResult{&recA1.ExecutionResult, &recA2.ExecutionResult}, 1391 }) 1392 1393 C := unittest.BlockWithParentFixture(B.Header) 1394 1395 bs.storeBlock(A) 1396 bs.storeBlock(B) 1397 bs.storeBlock(C) 1398 1399 // store execution results 1400 for _, block := range []*flow.Block{A, B, C} { 1401 // for current block create empty receipts list 1402 bs.receiptsByBlockID[block.ID()] = flow.ExecutionReceiptList{} 1403 1404 for _, result := range block.Payload.Results { 1405 bs.resultByID[result.ID()] = result 1406 } 1407 for _, meta := range block.Payload.Receipts { 1408 receipt := flow.ExecutionReceiptFromMeta(*meta, *bs.resultByID[meta.ResultID]) 1409 bs.receiptsByID[meta.ID()] = receipt 1410 bs.receiptsByBlockID[receipt.ExecutionResult.BlockID] = append(bs.receiptsByBlockID[receipt.ExecutionResult.BlockID], receipt) 1411 } 1412 } 1413 1414 // mark A as finalized 1415 bs.finalID = A.ID() 1416 1417 // set up no-op dependencies 1418 noopMetrics := metrics.NewNoopCollector() 1419 noopTracer := trace.NewNoopTracer() 1420 1421 // Instantiate real Execution Tree mempool; 1422 recPool := mempoolImpl.NewExecutionTree() 1423 1424 // create builder which has to repopulate execution tree 1425 var err error 1426 bs.build, err = NewBuilder( 1427 noopMetrics, 1428 bs.db, 1429 bs.state, 1430 bs.headerDB, 1431 bs.sealDB, 1432 bs.indexDB, 1433 bs.blockDB, 1434 bs.resultDB, 1435 bs.receiptsDB, 1436 bs.guarPool, 1437 bs.sealPool, 1438 recPool, 1439 noopTracer, 1440 ) 1441 require.NoError(bs.T(), err) 1442 bs.build.cfg.expiry = 11 1443 1444 // Create two valid receipts for block B which build on different receipts 1445 // for the parent block (A); recB1 builds on top of RecA1, whilst recB2 1446 // builds on top of RecA2. 1447 resB1 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA1.ExecutionResult)) 1448 recB1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB1)) 1449 resB2 := unittest.ExecutionResultFixture(unittest.WithBlock(B), unittest.WithPreviousResult(recA2.ExecutionResult)) 1450 recB2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resB2)) 1451 resC := unittest.ExecutionResultFixture(unittest.WithBlock(C), unittest.WithPreviousResult(recB1.ExecutionResult)) 1452 recC := unittest.ExecutionReceiptFixture(unittest.WithResult(resC)) 1453 1454 // Add recB1 and recB2 to the mempool for inclusion in the next candidate 1455 _, _ = bs.build.recPool.AddReceipt(recB1, B.Header) 1456 _, _ = bs.build.recPool.AddReceipt(recB2, B.Header) 1457 _, _ = bs.build.recPool.AddReceipt(recC, C.Header) 1458 1459 _, err = bs.build.BuildOn(C.ID(), bs.setter) 1460 bs.Require().NoError(err) 1461 expectedReceipts := flow.ExecutionReceiptMetaList{recB1.Meta(), recB2.Meta(), recC.Meta()} 1462 expectedResults := flow.ExecutionResultList{&recB1.ExecutionResult, &recB2.ExecutionResult, &recC.ExecutionResult} 1463 bs.Assert().ElementsMatch(expectedReceipts, bs.assembled.Receipts, "payload should contain receipts from valid execution forks") 1464 bs.Assert().ElementsMatch(expectedResults, bs.assembled.Results, "payload should contain results from valid execution forks") 1465 }