github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/model/flow/sealing_segment_test.go (about) 1 package flow_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 "github.com/stretchr/testify/suite" 10 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/utils/unittest" 13 ) 14 15 // SealingSegmentSuite is the test suite for sealing segment construction and validation. 16 // Notation: 17 // B1 - block 1 18 // R1 - execution result+receipt for block 1 19 // S1 - seal for block 1 20 // R* - execution result+receipt for some block before the segment 21 // S* - seal for some block before the segment 22 type SealingSegmentSuite struct { 23 suite.Suite 24 25 results map[flow.Identifier]*flow.ExecutionResult 26 sealsByBlockID map[flow.Identifier]*flow.Seal 27 protocolStateEntries map[flow.Identifier]*flow.ProtocolStateEntryWrapper 28 // bootstrap each test case with a block which is before, and receipt+seal for the block 29 priorBlock *flow.Block 30 priorReceipt *flow.ExecutionReceipt 31 priorSeal *flow.Seal 32 defaultProtocolStateID flow.Identifier 33 34 builder *flow.SealingSegmentBuilder 35 } 36 37 func TestSealingSegmentSuite(t *testing.T) { 38 suite.Run(t, new(SealingSegmentSuite)) 39 } 40 41 // addResult adds the result to the suite mapping. 42 func (suite *SealingSegmentSuite) addResult(result *flow.ExecutionResult) { 43 suite.results[result.ID()] = result 44 } 45 46 // addSeal adds the seal as being the latest w.r.t. the block ID. 47 func (suite *SealingSegmentSuite) addSeal(blockID flow.Identifier, seal *flow.Seal) { 48 suite.sealsByBlockID[blockID] = seal 49 } 50 51 func (suite *SealingSegmentSuite) addProtocolStateEntry(protocolStateID flow.Identifier, entry *flow.ProtocolStateEntryWrapper) { 52 suite.protocolStateEntries[protocolStateID] = entry 53 } 54 55 // GetResult gets a result by ID from the map in the suite. 56 func (suite *SealingSegmentSuite) GetResult(resultID flow.Identifier) (*flow.ExecutionResult, error) { 57 result, ok := suite.results[resultID] 58 if !ok { 59 return nil, fmt.Errorf("not found") 60 } 61 return result, nil 62 } 63 64 // GetSealByBlockID gets a seal by block ID from the map in the suite. 65 func (suite *SealingSegmentSuite) GetSealByBlockID(blockID flow.Identifier) (*flow.Seal, error) { 66 seal, ok := suite.sealsByBlockID[blockID] 67 if !ok { 68 return nil, fmt.Errorf("not found") 69 } 70 return seal, nil 71 } 72 73 // GetProtocolStateEntry gets a protocol state entry from the map in the suite. 74 func (suite *SealingSegmentSuite) GetProtocolStateEntry(protocolStateID flow.Identifier) (*flow.ProtocolStateEntryWrapper, error) { 75 entry, ok := suite.protocolStateEntries[protocolStateID] 76 if !ok { 77 return nil, fmt.Errorf("not found") 78 } 79 return entry, nil 80 } 81 82 // SetupTest resets maps and creates a new builder for a new test case. 83 func (suite *SealingSegmentSuite) SetupTest() { 84 suite.results = make(map[flow.Identifier]*flow.ExecutionResult) 85 suite.sealsByBlockID = make(map[flow.Identifier]*flow.Seal) 86 suite.protocolStateEntries = make(map[flow.Identifier]*flow.ProtocolStateEntryWrapper) 87 suite.builder = flow.NewSealingSegmentBuilder(suite.GetResult, suite.GetSealByBlockID, suite.GetProtocolStateEntry) 88 89 suite.defaultProtocolStateID = unittest.IdentifierFixture() 90 suite.protocolStateEntries[suite.defaultProtocolStateID] = suite.ProtocolStateEntryWrapperFixture() 91 92 priorBlock := suite.BlockFixture() 93 priorReceipt, priorSeal := unittest.ReceiptAndSealForBlock(&priorBlock) 94 suite.results[priorReceipt.ExecutionResult.ID()] = &priorReceipt.ExecutionResult 95 suite.priorBlock = &priorBlock 96 suite.priorReceipt = priorReceipt 97 suite.priorSeal = priorSeal 98 } 99 100 // BlockFixture returns a Block fixture with the default protocol state ID. 101 func (suite *SealingSegmentSuite) BlockFixture() flow.Block { 102 block := unittest.BlockFixture() 103 block.SetPayload(suite.PayloadFixture()) 104 return block 105 } 106 107 // PayloadFixture returns a Payload fixture with the default protocol state ID. 108 func (suite *SealingSegmentSuite) PayloadFixture(opts ...func(payload *flow.Payload)) flow.Payload { 109 opts = append(opts, unittest.WithProtocolStateID(suite.defaultProtocolStateID)) 110 return unittest.PayloadFixture(opts...) 111 } 112 113 // BlockWithParentFixture returns a Block fixtures with the default protocol state. 114 func (suite *SealingSegmentSuite) BlockWithParentFixture(parent *flow.Header) *flow.Block { 115 block := unittest.BlockWithParentFixture(parent) 116 block.SetPayload(suite.PayloadFixture()) 117 return block 118 } 119 120 // ProtocolStateEntryWrapperFixture returns a ProtocolStateEntryWrapper. 121 // For these tests, only the ID matters, so we can just return an empty struct. 122 func (suite *SealingSegmentSuite) ProtocolStateEntryWrapperFixture() *flow.ProtocolStateEntryWrapper { 123 return &flow.ProtocolStateEntryWrapper{} 124 } 125 126 // FirstBlock returns a first block which contains a seal and receipt referencing 127 // priorBlock (this is the simplest case for a sealing segment). 128 func (suite *SealingSegmentSuite) FirstBlock() *flow.Block { 129 block := suite.BlockFixture() 130 block.SetPayload(suite.PayloadFixture( 131 unittest.WithSeals(suite.priorSeal), 132 unittest.WithReceipts(suite.priorReceipt), 133 )) 134 suite.addSeal(block.ID(), suite.priorSeal) 135 return &block 136 } 137 138 // AddBlocks is a short-hand for adding a sequence of blocks, in order. 139 // No errors are expected. 140 func (suite *SealingSegmentSuite) AddBlocks(blocks ...*flow.Block) { 141 latestSeal := suite.priorSeal 142 for _, block := range blocks { 143 // before adding block, ensure its latest seal is indexed in suite 144 // convention for this test: seals are ordered by height of the sealed block 145 for _, seal := range block.Payload.Seals { 146 latestSeal = seal 147 } 148 suite.addSeal(block.ID(), latestSeal) 149 err := suite.builder.AddBlock(block) 150 require.NoError(suite.T(), err) 151 } 152 } 153 154 // Tests the case where a receipt in the segment references a result outside it. 155 // The result should still be included in the sealing segment. 156 // 157 // B1(R*,S*) <- B2(R1) <- B4(S1) 158 func (suite *SealingSegmentSuite) TestBuild_MissingResultFromReceipt() { 159 160 // B1 contains a receipt (but no result) and seal for a prior block 161 block1 := suite.BlockFixture() 162 block1.SetPayload(suite.PayloadFixture(unittest.WithReceiptsAndNoResults(suite.priorReceipt), unittest.WithSeals(suite.priorSeal))) 163 164 block2 := suite.BlockWithParentFixture(block1.Header) 165 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 166 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 167 168 block3 := suite.BlockWithParentFixture(block2.Header) 169 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1))) 170 171 suite.AddBlocks(&block1, block2, block3) 172 173 segment, err := suite.builder.SealingSegment() 174 require.NoError(suite.T(), err) 175 require.NoError(suite.T(), segment.Validate()) 176 177 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 178 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 179 require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID()) 180 } 181 182 // Tests the case where the first block contains no seal. 183 // The latest seal as of the first block should still be included in the segment. 184 // 185 // B1 <- B2(R1) <- B3(S1) 186 func (suite *SealingSegmentSuite) TestBuild_MissingFirstBlockSeal() { 187 188 // B1 contains an empty payload 189 block1 := suite.BlockFixture() 190 // latest seal as of B1 is priorSeal 191 suite.sealsByBlockID[block1.ID()] = suite.priorSeal 192 193 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 194 block2 := suite.BlockWithParentFixture(block1.Header) 195 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 196 197 block3 := suite.BlockWithParentFixture(block2.Header) 198 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1))) 199 200 suite.AddBlocks(&block1, block2, block3) 201 202 segment, err := suite.builder.SealingSegment() 203 require.NoError(suite.T(), err) 204 require.NoError(suite.T(), segment.Validate()) 205 206 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 207 // should contain priorSeal as first seal 208 require.Equal(suite.T(), suite.priorSeal, segment.FirstSeal) 209 // should contain result referenced by first seal 210 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 211 require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID()) 212 } 213 214 // Tests the case where a seal contained in a segment block payloads references 215 // a missing result. The result should still be included in the segment. 216 // 217 // B1(S*,R*) <- B2(R1,S**) <- B3(S1) 218 func (suite *SealingSegmentSuite) TestBuild_MissingResultFromPayloadSeal() { 219 220 block1 := suite.FirstBlock() 221 222 // create a seal referencing some past receipt/block 223 pastResult := unittest.ExecutionResultFixture() 224 suite.addResult(pastResult) 225 pastSeal := unittest.Seal.Fixture() 226 pastSeal.ResultID = pastResult.ID() 227 228 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 229 block2 := suite.BlockWithParentFixture(block1.Header) 230 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1), unittest.WithSeals(pastSeal))) 231 232 block3 := suite.BlockWithParentFixture(block2.Header) 233 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1))) 234 235 suite.AddBlocks(block1, block2, block3) 236 237 segment, err := suite.builder.SealingSegment() 238 require.NoError(suite.T(), err) 239 require.NoError(suite.T(), segment.Validate()) 240 241 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks) 242 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 243 require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID()) 244 } 245 246 // Tests the case where the final block in the segment contains both a seal 247 // for lowest, and a seal for a block above lowest. This should be considered 248 // an invalid segment. 249 // 250 // B1(S*,R*) <- B2 <- B3(R1,R2) <- B4(S1,S2) 251 func (suite *SealingSegmentSuite) TestBuild_WrongLatestSeal() { 252 253 block1 := suite.FirstBlock() 254 block2 := suite.BlockWithParentFixture(block1.Header) 255 256 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 257 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 258 259 block3 := suite.BlockWithParentFixture(block2.Header) 260 block3.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1, receipt2))) 261 262 block4 := suite.BlockWithParentFixture(block3.Header) 263 block4.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1, seal2))) 264 265 suite.AddBlocks(block1, block2, block3, block4) 266 267 _, err := suite.builder.SealingSegment() 268 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 269 } 270 271 // Tests the case where the final block in the segment seals multiple 272 // blocks, but the latest sealed is still lowest, hence it is a valid 273 // sealing segment. 274 // 275 // B1(S*,R*) <- B2(R1) <- B3(S**,S1) 276 func (suite *SealingSegmentSuite) TestBuild_MultipleFinalBlockSeals() { 277 278 block1 := suite.FirstBlock() 279 280 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 281 // create a seal referencing some past receipt/block 282 pastResult := unittest.ExecutionResultFixture() 283 suite.addResult(pastResult) 284 pastSeal := unittest.Seal.Fixture() 285 pastSeal.ResultID = pastResult.ID() 286 287 block2 := suite.BlockWithParentFixture(block1.Header) 288 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 289 290 block3 := suite.BlockWithParentFixture(block2.Header) 291 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(pastSeal, seal1))) 292 293 suite.AddBlocks(block1, block2, block3) 294 295 segment, err := suite.builder.SealingSegment() 296 require.NoError(suite.T(), err) 297 298 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks) 299 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 300 require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID()) 301 require.NoError(suite.T(), segment.Validate()) 302 } 303 304 // TestBuild_RootSegment tests we can build a valid root sealing segment. 305 func (suite *SealingSegmentSuite) TestBuild_RootSegment() { 306 307 root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles())) 308 suite.sealsByBlockID[root.ID()] = seal 309 suite.addProtocolStateEntry(root.Payload.ProtocolStateID, suite.ProtocolStateEntryWrapperFixture()) 310 suite.addResult(result) 311 err := suite.builder.AddBlock(root) 312 require.NoError(suite.T(), err) 313 314 segment, err := suite.builder.SealingSegment() 315 require.NoError(suite.T(), err) 316 require.NoError(suite.T(), segment.Validate()) 317 318 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{root}, segment.Blocks) 319 require.Equal(suite.T(), segment.Highest().ID(), root.ID()) 320 require.Equal(suite.T(), segment.Sealed().ID(), root.ID()) 321 } 322 323 // TestBuild_RootSegmentWrongView tests that we return ErrSegmentInvalidRootView for 324 // a single-block sealing segment with a block view not equal to 0. 325 func (suite *SealingSegmentSuite) TestBuild_RootSegmentWrongView() { 326 327 root, result, seal := unittest.BootstrapFixture( 328 unittest.IdentityListFixture(5, unittest.WithAllRoles()), 329 func(block *flow.Block) { 330 block.Header.View = 10 // invalid root block view 331 }) 332 suite.sealsByBlockID[root.ID()] = seal 333 suite.addProtocolStateEntry(root.Payload.ProtocolStateID, suite.ProtocolStateEntryWrapperFixture()) 334 suite.addResult(result) 335 err := suite.builder.AddBlock(root) 336 require.NoError(suite.T(), err) 337 338 _, err = suite.builder.SealingSegment() 339 require.Error(suite.T(), err) 340 } 341 342 // Test the case when the highest block in the segment does not contain seals but 343 // the first ancestor of the highest block does contain a seal for lowest, 344 // we return a valid sealing segment. 345 // 346 // B1(S*) <- B2(R1) <- B3(S1) <- B4 347 func (suite *SealingSegmentSuite) TestBuild_HighestContainsNoSeals() { 348 block1 := suite.FirstBlock() 349 350 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 351 block2 := suite.BlockWithParentFixture(block1.Header) 352 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 353 354 block3 := suite.BlockWithParentFixture(block2.Header) 355 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1))) 356 357 block4 := suite.BlockWithParentFixture(block3.Header) 358 359 suite.AddBlocks(block1, block2, block3, block4) 360 361 segment, err := suite.builder.SealingSegment() 362 require.NoError(suite.T(), err) 363 require.NoError(suite.T(), segment.Validate()) 364 365 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3, block4}, segment.Blocks) 366 } 367 368 // Test that we should return InvalidSealingSegmentError if highest block contains 369 // seals but does not contain seal for lowest, when sealing segment is built. 370 // 371 // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2) 372 func (suite *SealingSegmentSuite) TestBuild_HighestContainsWrongSeal() { 373 block1 := suite.FirstBlock() 374 375 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 376 block2 := suite.BlockWithParentFixture(block1.Header) 377 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 378 379 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 380 block3 := suite.BlockWithParentFixture(block2.Header) 381 block3.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1))) 382 383 // highest block contains wrong seal - invalid 384 block4 := suite.BlockWithParentFixture(block3.Header) 385 block4.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal2))) 386 387 suite.AddBlocks(block1, block2, block3, block4) 388 389 _, err := suite.builder.SealingSegment() 390 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 391 } 392 393 // Test that we should return InvalidSealingSegmentError if highest block contains 394 // no seals and first ancestor with seals does not seal lowest, when sealing 395 // segment is built 396 // 397 // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2) <- B5 398 func (suite *SealingSegmentSuite) TestBuild_HighestAncestorContainsWrongSeal() { 399 block1 := suite.FirstBlock() 400 401 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 402 block2 := suite.BlockWithParentFixture(block1.Header) 403 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 404 405 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 406 block3 := suite.BlockWithParentFixture(block2.Header) 407 block3.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1))) 408 409 // ancestor of highest block contains wrong seal - invalid 410 block4 := suite.BlockWithParentFixture(block3.Header) 411 block4.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal2))) 412 413 block5 := suite.BlockWithParentFixture(block4.Header) 414 415 suite.AddBlocks(block1, block2, block3, block4, block5) 416 417 _, err := suite.builder.SealingSegment() 418 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 419 } 420 421 // TestBuild_ChangingProtocolStateID_Blocks tests constructing a sealing segment where 422 // the primary segment (`Blocks`) contains blocks with different protocol state entries. 423 // In this test, B1 commits to the default protocol state ID, and blocks B2 and B3 424 // commit to a different protocol state ID (PS2). 425 // B1(R*,S*) <- B2(R1,PS2) <- B3(S1,PS2) 426 func (suite *SealingSegmentSuite) TestBuild_ChangingProtocolStateID_Blocks() { 427 block1 := suite.BlockFixture() 428 block1.SetPayload(suite.PayloadFixture(unittest.WithReceipts(suite.priorReceipt), unittest.WithSeals(suite.priorSeal))) 429 430 protocolStateID2 := unittest.IdentifierFixture() 431 suite.addProtocolStateEntry(protocolStateID2, suite.ProtocolStateEntryWrapperFixture()) 432 433 block2 := suite.BlockWithParentFixture(block1.Header) 434 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 435 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1), unittest.WithProtocolStateID(protocolStateID2))) 436 437 block3 := suite.BlockWithParentFixture(block2.Header) 438 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1), unittest.WithProtocolStateID(protocolStateID2))) 439 440 suite.AddBlocks(&block1, block2, block3) 441 442 segment, err := suite.builder.SealingSegment() 443 require.NoError(suite.T(), err) 444 require.NoError(suite.T(), segment.Validate()) 445 446 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 447 // resulting segment must contain both protocol state IDs 448 assert.Len(suite.T(), segment.ProtocolStateEntries, 2) 449 _, ok := segment.ProtocolStateEntries[suite.defaultProtocolStateID] 450 assert.True(suite.T(), ok) 451 _, ok = segment.ProtocolStateEntries[protocolStateID2] 452 assert.True(suite.T(), ok) 453 } 454 455 // TestBuild_ChangingProtocolStateID_ExtraBlocks tests constructing a sealing segment where 456 // `ExtraBlocks` contains blocks with different protocol state entries. 457 // In this test, blocks B1, B2, and B3 commits to the default protocol state ID. 458 // Extra blocks EB1 and EB2 commit to a different protocol state ID (PS2). 459 // EB2(PS2) <- EB1 <- B1(R*,S*) <- B2(R1) <- B3(S1) 460 func (suite *SealingSegmentSuite) TestBuild_ChangingProtocolStateID_ExtraBlocks() { 461 block1 := suite.BlockFixture() 462 block1.SetPayload(suite.PayloadFixture(unittest.WithReceipts(suite.priorReceipt), unittest.WithSeals(suite.priorSeal))) 463 464 block2 := suite.BlockWithParentFixture(block1.Header) 465 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 466 block2.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt1))) 467 468 block3 := suite.BlockWithParentFixture(block2.Header) 469 block3.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal1))) 470 471 suite.AddBlocks(&block1, block2, block3) 472 473 // construct two extra blocks that connect to the lowest block and add them to builder 474 protocolStateID2 := unittest.IdentifierFixture() 475 suite.addProtocolStateEntry(protocolStateID2, suite.ProtocolStateEntryWrapperFixture()) 476 477 extraBlock1 := suite.BlockFixture() 478 extraBlock1.Header.Height = block1.Header.Height - 1 479 extraBlock1.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(protocolStateID2))) 480 err := suite.builder.AddExtraBlock(&extraBlock1) 481 require.NoError(suite.T(), err) 482 483 extraBlock2 := suite.BlockFixture() 484 extraBlock2.Header.Height = extraBlock1.Header.Height - 1 485 err = suite.builder.AddExtraBlock(&extraBlock2) 486 require.NoError(suite.T(), err) 487 488 segment, err := suite.builder.SealingSegment() 489 require.NoError(suite.T(), err) 490 err = segment.Validate() 491 require.NoError(suite.T(), err) 492 493 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 494 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&extraBlock2, &extraBlock1}, segment.ExtraBlocks) 495 // resulting segment must contain both protocol state IDs 496 assert.Len(suite.T(), segment.ProtocolStateEntries, 2) 497 _, ok := segment.ProtocolStateEntries[suite.defaultProtocolStateID] 498 assert.True(suite.T(), ok) 499 _, ok = segment.ProtocolStateEntries[protocolStateID2] 500 assert.True(suite.T(), ok) 501 } 502 503 // Test that we should return InvalidSealingSegmentError if sealing segment is 504 // built with no blocks. 505 func (suite *SealingSegmentSuite) TestBuild_NoBlocks() { 506 builder := flow.NewSealingSegmentBuilder(nil, nil, nil) 507 _, err := builder.SealingSegment() 508 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 509 } 510 511 // should return InvalidSealingSegmentError if block has invalid height 512 func (suite *SealingSegmentSuite) TestAddBlock_InvalidHeight() { 513 514 block1 := suite.FirstBlock() 515 // block 2 has an invalid height 516 block2 := suite.BlockFixture() 517 block2.Header.Height = block1.Header.Height + 2 518 519 err := suite.builder.AddBlock(block1) 520 require.NoError(suite.T(), err) 521 522 err = suite.builder.AddBlock(&block2) 523 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 524 } 525 526 // TestAddBlock_StorageError tests that errors in the resource getters bubble up. 527 func TestAddBlock_StorageError(t *testing.T) { 528 529 t.Run("missing result", func(t *testing.T) { 530 // create a receipt to include in the first block, whose result is not in storage 531 missingReceipt := unittest.ExecutionReceiptFixture() 532 block1 := unittest.BlockFixture() 533 exception := fmt.Errorf("") 534 sealLookup := func(flow.Identifier) (*flow.Seal, error) { return unittest.Seal.Fixture(), nil } 535 resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return nil, exception } 536 protocolStateEntryLookup := func(flow.Identifier) (*flow.ProtocolStateEntryWrapper, error) { 537 return &flow.ProtocolStateEntryWrapper{}, nil 538 } 539 builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup, protocolStateEntryLookup) 540 541 block1.SetPayload(unittest.PayloadFixture( 542 unittest.WithReceiptsAndNoResults(missingReceipt), 543 unittest.WithSeals(unittest.Seal.Fixture(unittest.Seal.WithResult(&missingReceipt.ExecutionResult))), 544 )) 545 546 err := builder.AddBlock(&block1) 547 require.ErrorIs(t, err, exception) 548 }) 549 550 // create a first block which contains no seal, and the seal isn't in storage 551 t.Run("missing seal", func(t *testing.T) { 552 exception := fmt.Errorf("") 553 resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return unittest.ExecutionResultFixture(), nil } 554 sealLookup := func(flow.Identifier) (*flow.Seal, error) { return nil, exception } 555 protocolStateEntryLookup := func(flow.Identifier) (*flow.ProtocolStateEntryWrapper, error) { 556 return &flow.ProtocolStateEntryWrapper{}, nil 557 } 558 block1 := unittest.BlockFixture() 559 block1.SetPayload(flow.EmptyPayload()) 560 builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup, protocolStateEntryLookup) 561 562 err := builder.AddBlock(&block1) 563 require.ErrorIs(t, err, exception) 564 }) 565 566 t.Run("missing protocol state entry", func(t *testing.T) { 567 exception := fmt.Errorf("") 568 resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return unittest.ExecutionResultFixture(), nil } 569 sealLookup := func(flow.Identifier) (*flow.Seal, error) { return unittest.Seal.Fixture(), nil } 570 protocolStateEntryLookup := func(flow.Identifier) (*flow.ProtocolStateEntryWrapper, error) { return nil, exception } 571 block1 := unittest.BlockFixture() 572 block1.SetPayload(flow.EmptyPayload()) 573 builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup, protocolStateEntryLookup) 574 575 err := builder.AddBlock(&block1) 576 require.ErrorIs(t, err, exception) 577 }) 578 } 579 580 // TestAddExtraBlock tests different scenarios for adding extra blocks, covers happy and unhappy path scenarios. 581 func (suite *SealingSegmentSuite) TestAddExtraBlock() { 582 // populate sealing segment with one block 583 firstBlock := suite.FirstBlock() 584 firstBlock.Header.Height += 100 585 suite.AddBlocks(firstBlock) 586 587 suite.T().Run("empty-segment", func(t *testing.T) { 588 builder := flow.NewSealingSegmentBuilder(nil, nil, nil) 589 block := suite.BlockFixture() 590 err := builder.AddExtraBlock(&block) 591 require.Error(t, err) 592 }) 593 suite.T().Run("extra-block-does-not-connect", func(t *testing.T) { 594 // adding extra block that doesn't connect to the lowest is an error 595 extraBlock := suite.BlockFixture() 596 extraBlock.Header.Height = firstBlock.Header.Height + 10 // make sure it doesn't connect by height 597 err := suite.builder.AddExtraBlock(&extraBlock) 598 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 599 }) 600 suite.T().Run("extra-block-not-continuous", func(t *testing.T) { 601 builder := flow.NewSealingSegmentBuilder(suite.GetResult, suite.GetSealByBlockID, suite.GetProtocolStateEntry) 602 err := builder.AddBlock(firstBlock) 603 require.NoError(t, err) 604 extraBlock := suite.BlockFixture() 605 extraBlock.Header.Height = firstBlock.Header.Height - 1 // make it connect 606 err = builder.AddExtraBlock(&extraBlock) 607 require.NoError(t, err) 608 extraBlockWithSkip := suite.BlockFixture() 609 extraBlockWithSkip.Header.Height = extraBlock.Header.Height - 2 // skip one height 610 err = builder.AddExtraBlock(&extraBlockWithSkip) 611 require.True(suite.T(), flow.IsInvalidSealingSegmentError(err)) 612 }) 613 suite.T().Run("root-segment-extra-blocks", func(t *testing.T) { 614 builder := flow.NewSealingSegmentBuilder(suite.GetResult, suite.GetSealByBlockID, suite.GetProtocolStateEntry) 615 err := builder.AddBlock(firstBlock) 616 require.NoError(t, err) 617 618 extraBlock := suite.BlockFixture() 619 extraBlock.Header.Height = firstBlock.Header.Height - 1 620 err = builder.AddExtraBlock(&extraBlock) 621 require.NoError(t, err) 622 _, err = builder.SealingSegment() 623 // root segment cannot have extra blocks 624 require.Error(t, err) 625 }) 626 suite.T().Run("happy-path", func(t *testing.T) { 627 // add a few blocks with results and seals to form a valid sealing segment 628 // B1(S*) <- B2(R1) <- B3(S1) 629 630 receipt, seal := unittest.ReceiptAndSealForBlock(firstBlock) 631 blockWithER := suite.BlockWithParentFixture(firstBlock.Header) 632 blockWithER.SetPayload(suite.PayloadFixture(unittest.WithReceipts(receipt))) 633 634 // add one more block, with seal to the ER 635 highestBlock := suite.BlockWithParentFixture(blockWithER.Header) 636 highestBlock.SetPayload(suite.PayloadFixture(unittest.WithSeals(seal))) 637 638 suite.AddBlocks(blockWithER, highestBlock) 639 640 // construct two extra blocks that connect to the lowest block and add them to builder 641 // EB2 <- EB1 <- B1(S*) <- B2(R1) <- B3(S1) 642 extraBlock := suite.BlockFixture() 643 extraBlock.Header.Height = firstBlock.Header.Height - 1 644 err := suite.builder.AddExtraBlock(&extraBlock) 645 require.NoError(t, err) 646 secondExtraBlock := suite.BlockFixture() 647 secondExtraBlock.Header.Height = extraBlock.Header.Height - 1 648 err = suite.builder.AddExtraBlock(&secondExtraBlock) 649 require.NoError(t, err) 650 segment, err := suite.builder.SealingSegment() 651 require.NoError(t, err) 652 err = segment.Validate() 653 require.NoError(t, err) 654 }) 655 }