github.com/koko1123/flow-go-1@v0.29.6/model/flow/sealing_segment_test.go (about) 1 package flow_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 "github.com/stretchr/testify/suite" 9 10 "github.com/koko1123/flow-go-1/model/flow" 11 "github.com/koko1123/flow-go-1/utils/unittest" 12 ) 13 14 // SealingSegmentSuite is the test suite for sealing segment construction and validation. 15 // Notation: 16 // B1 - block 1 17 // R1 - execution result+receipt for block 1 18 // S1 - seal for block 1 19 // R* - execution result+receipt for some block before the segment 20 // S* - seal for some block before the segment 21 type SealingSegmentSuite struct { 22 suite.Suite 23 24 results map[flow.Identifier]*flow.ExecutionResult 25 sealsByBlockID map[flow.Identifier]*flow.Seal 26 // bootstrap each test case with a block which is before, and receipt+seal for the block 27 priorBlock *flow.Block 28 priorReceipt *flow.ExecutionReceipt 29 priorSeal *flow.Seal 30 31 builder *flow.SealingSegmentBuilder 32 } 33 34 func TestSealingSegmentSuite(t *testing.T) { 35 suite.Run(t, new(SealingSegmentSuite)) 36 } 37 38 // addResult adds the result to the suite mapping. 39 func (suite *SealingSegmentSuite) addResult(result *flow.ExecutionResult) { 40 suite.results[result.ID()] = result 41 } 42 43 // addSeal adds the seal as being the latest w.r.t. the block ID. 44 func (suite *SealingSegmentSuite) addSeal(blockID flow.Identifier, seal *flow.Seal) { 45 suite.sealsByBlockID[blockID] = seal 46 } 47 48 // GetResult gets a result by ID from the map in the suite. 49 func (suite *SealingSegmentSuite) GetResult(resultID flow.Identifier) (*flow.ExecutionResult, error) { 50 result, ok := suite.results[resultID] 51 if !ok { 52 return nil, fmt.Errorf("not found") 53 } 54 return result, nil 55 } 56 57 // GetSealByBlockID gets a seal by block ID from the map in the suite. 58 func (suite *SealingSegmentSuite) GetSealByBlockID(blockID flow.Identifier) (*flow.Seal, error) { 59 seal, ok := suite.sealsByBlockID[blockID] 60 if !ok { 61 return nil, fmt.Errorf("not found") 62 } 63 return seal, nil 64 } 65 66 // SetupTest resets maps and creates a new builder for a new test case. 67 func (suite *SealingSegmentSuite) SetupTest() { 68 suite.results = make(map[flow.Identifier]*flow.ExecutionResult) 69 suite.sealsByBlockID = make(map[flow.Identifier]*flow.Seal) 70 suite.builder = flow.NewSealingSegmentBuilder(suite.GetResult, suite.GetSealByBlockID) 71 72 priorBlock := unittest.BlockFixture() 73 priorReceipt, priorSeal := unittest.ReceiptAndSealForBlock(&priorBlock) 74 suite.results[priorReceipt.ExecutionResult.ID()] = &priorReceipt.ExecutionResult 75 suite.priorBlock = &priorBlock 76 suite.priorReceipt = priorReceipt 77 suite.priorSeal = priorSeal 78 } 79 80 // FirstBlock returns a first block which contains a seal and receipt referencing 81 // priorBlock (this is the simplest case for a sealing segment). 82 func (suite *SealingSegmentSuite) FirstBlock() *flow.Block { 83 block := unittest.BlockFixture() 84 block.SetPayload(unittest.PayloadFixture( 85 unittest.WithSeals(suite.priorSeal), 86 unittest.WithReceipts(suite.priorReceipt), 87 )) 88 suite.addSeal(block.ID(), suite.priorSeal) 89 return &block 90 } 91 92 // AddBlocks is a short-hand for adding a sequence of blocks, in order. 93 // No errors are expected. 94 func (suite *SealingSegmentSuite) AddBlocks(blocks ...*flow.Block) { 95 latestSeal := suite.priorSeal 96 for _, block := range blocks { 97 // before adding block, ensure its latest seal is indexed in suite 98 // convention for this test: seals are ordered by height of the sealed block 99 for _, seal := range block.Payload.Seals { 100 latestSeal = seal 101 } 102 suite.addSeal(block.ID(), latestSeal) 103 err := suite.builder.AddBlock(block) 104 require.NoError(suite.T(), err) 105 } 106 } 107 108 // Tests the case where a receipt in the segment references a result outside it. 109 // The result should still be included in the sealing segment. 110 // 111 // B1(R*,S*) <- B2(R1) <- B4(S1) 112 func (suite *SealingSegmentSuite) TestBuild_MissingResultFromReceipt() { 113 114 // B1 contains a receipt (but no result) and seal for a prior block 115 block1 := unittest.BlockFixture() 116 block1.SetPayload(unittest.PayloadFixture(unittest.WithReceiptsAndNoResults(suite.priorReceipt), unittest.WithSeals(suite.priorSeal))) 117 118 block2 := unittest.BlockWithParentFixture(block1.Header) 119 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 120 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 121 122 block3 := unittest.BlockWithParentFixture(block2.Header) 123 block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1))) 124 125 suite.AddBlocks(&block1, block2, block3) 126 127 segment, err := suite.builder.SealingSegment() 128 require.NoError(suite.T(), err) 129 require.NoError(suite.T(), segment.Validate()) 130 131 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 132 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 133 require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID()) 134 } 135 136 // Tests the case where the first block contains no seal. 137 // The latest seal as of the first block should still be included in the segment. 138 // 139 // B1 <- B2(R1) <- B3(S1) 140 func (suite *SealingSegmentSuite) TestBuild_MissingFirstBlockSeal() { 141 142 // B1 contains an empty payload 143 block1 := unittest.BlockFixture() 144 // latest seal as of B1 is priorSeal 145 suite.sealsByBlockID[block1.ID()] = suite.priorSeal 146 147 receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1) 148 block2 := unittest.BlockWithParentFixture(block1.Header) 149 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 150 151 block3 := unittest.BlockWithParentFixture(block2.Header) 152 block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1))) 153 154 suite.AddBlocks(&block1, block2, block3) 155 156 segment, err := suite.builder.SealingSegment() 157 require.NoError(suite.T(), err) 158 require.NoError(suite.T(), segment.Validate()) 159 160 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks) 161 // should contain priorSeal as first seal 162 require.Equal(suite.T(), suite.priorSeal, segment.FirstSeal) 163 // should contain result referenced by first seal 164 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 165 require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID()) 166 } 167 168 // Tests the case where a seal contained in a segment block payloads references 169 // a missing result. The result should still be included in the segment. 170 // 171 // B1(S*,R*) <- B2(R1,S**) <- B3(S1) 172 func (suite *SealingSegmentSuite) TestBuild_MissingResultFromPayloadSeal() { 173 174 block1 := suite.FirstBlock() 175 176 // create a seal referencing some past receipt/block 177 pastResult := unittest.ExecutionResultFixture() 178 suite.addResult(pastResult) 179 pastSeal := unittest.Seal.Fixture() 180 pastSeal.ResultID = pastResult.ID() 181 182 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 183 block2 := unittest.BlockWithParentFixture(block1.Header) 184 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1), unittest.WithSeals(pastSeal))) 185 186 block3 := unittest.BlockWithParentFixture(block2.Header) 187 block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1))) 188 189 suite.AddBlocks(block1, block2, block3) 190 191 segment, err := suite.builder.SealingSegment() 192 require.NoError(suite.T(), err) 193 require.NoError(suite.T(), segment.Validate()) 194 195 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks) 196 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 197 require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID()) 198 } 199 200 // Tests the case where the final block in the segment contains both a seal 201 // for lowest, and a seal for a block above lowest. This should be considered 202 // an invalid segment. 203 // 204 // B1(S*,R*) <- B2 <- B3(R1,R2) <- B4(S1,S2) 205 func (suite *SealingSegmentSuite) TestBuild_WrongLatestSeal() { 206 207 block1 := suite.FirstBlock() 208 block2 := unittest.BlockWithParentFixture(block1.Header) 209 210 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 211 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 212 213 block3 := unittest.BlockWithParentFixture(block2.Header) 214 block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1, receipt2))) 215 216 block4 := unittest.BlockWithParentFixture(block3.Header) 217 block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1, seal2))) 218 219 suite.AddBlocks(block1, block2, block3, block4) 220 221 _, err := suite.builder.SealingSegment() 222 require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal) 223 } 224 225 // Tests the case where the final block in the segment seals multiple 226 // blocks, but the latest sealed is still lowest, hence it is a valid 227 // sealing segment. 228 // 229 // B1(S*,R*) <- B2(R1) <- B3(S**,S1) 230 func (suite *SealingSegmentSuite) TestBuild_MultipleFinalBlockSeals() { 231 232 block1 := suite.FirstBlock() 233 234 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 235 // create a seal referencing some past receipt/block 236 pastResult := unittest.ExecutionResultFixture() 237 suite.addResult(pastResult) 238 pastSeal := unittest.Seal.Fixture() 239 pastSeal.ResultID = pastResult.ID() 240 241 block2 := unittest.BlockWithParentFixture(block1.Header) 242 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 243 244 block3 := unittest.BlockWithParentFixture(block2.Header) 245 block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(pastSeal, seal1))) 246 247 suite.AddBlocks(block1, block2, block3) 248 249 segment, err := suite.builder.SealingSegment() 250 require.NoError(suite.T(), err) 251 252 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks) 253 require.Equal(suite.T(), 1, segment.ExecutionResults.Size()) 254 require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID()) 255 require.NoError(suite.T(), segment.Validate()) 256 } 257 258 // TestBuild_RootSegment tests we can build a valid root sealing segment. 259 func (suite *SealingSegmentSuite) TestBuild_RootSegment() { 260 261 root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles())) 262 suite.sealsByBlockID[root.ID()] = seal 263 suite.addResult(result) 264 err := suite.builder.AddBlock(root) 265 require.NoError(suite.T(), err) 266 267 segment, err := suite.builder.SealingSegment() 268 require.NoError(suite.T(), err) 269 require.NoError(suite.T(), segment.Validate()) 270 271 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{root}, segment.Blocks) 272 require.Equal(suite.T(), segment.Highest().ID(), root.ID()) 273 require.Equal(suite.T(), segment.Lowest().ID(), root.ID()) 274 } 275 276 // TestBuild_RootSegmentWrongView tests that we return ErrSegmentInvalidRootView for 277 // a single-block sealing segment with a block view not equal to 0. 278 func (suite *SealingSegmentSuite) TestBuild_RootSegmentWrongView() { 279 280 root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles())) 281 root.Header.View = 10 // invalid root block view 282 suite.sealsByBlockID[root.ID()] = seal 283 suite.addResult(result) 284 err := suite.builder.AddBlock(root) 285 require.NoError(suite.T(), err) 286 287 _, err = suite.builder.SealingSegment() 288 require.Error(suite.T(), err) 289 } 290 291 // Test the case when the highest block in the segment does not contain seals but 292 // the first ancestor of the highest block does contain a seal for lowest, 293 // we return a valid sealing segment. 294 // 295 // B1(S*) <- B2(R1) <- B3(S1) <- B4 296 func (suite *SealingSegmentSuite) TestBuild_HighestContainsNoSeals() { 297 block1 := suite.FirstBlock() 298 299 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 300 block2 := unittest.BlockWithParentFixture(block1.Header) 301 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 302 303 block3 := unittest.BlockWithParentFixture(block2.Header) 304 block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1))) 305 306 block4 := unittest.BlockWithParentFixture(block3.Header) 307 308 suite.AddBlocks(block1, block2, block3, block4) 309 310 segment, err := suite.builder.SealingSegment() 311 require.NoError(suite.T(), err) 312 require.NoError(suite.T(), segment.Validate()) 313 314 unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3, block4}, segment.Blocks) 315 } 316 317 // Test that we should return ErrSegmentMissingSeal if highest block contains 318 // seals but does not contain seal for lowest, when sealing segment is built. 319 // 320 // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2) 321 func (suite *SealingSegmentSuite) TestBuild_HighestContainsWrongSeal() { 322 block1 := suite.FirstBlock() 323 324 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 325 block2 := unittest.BlockWithParentFixture(block1.Header) 326 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 327 328 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 329 block3 := unittest.BlockWithParentFixture(block2.Header) 330 block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1))) 331 332 // highest block contains wrong seal - invalid 333 block4 := unittest.BlockWithParentFixture(block3.Header) 334 block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal2))) 335 336 suite.AddBlocks(block1, block2, block3, block4) 337 338 _, err := suite.builder.SealingSegment() 339 require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal) 340 } 341 342 // Test that we should return ErrSegmentMissingSeal if highest block contains 343 // no seals and first ancestor with seals does not seal lowest, when sealing 344 // segment is built 345 // 346 // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2) <- B5 347 func (suite *SealingSegmentSuite) TestBuild_HighestAncestorContainsWrongSeal() { 348 block1 := suite.FirstBlock() 349 350 receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1) 351 block2 := unittest.BlockWithParentFixture(block1.Header) 352 block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1))) 353 354 receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2) 355 block3 := unittest.BlockWithParentFixture(block2.Header) 356 block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1))) 357 358 // ancestor of highest block contains wrong seal - invalid 359 block4 := unittest.BlockWithParentFixture(block3.Header) 360 block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal2))) 361 362 block5 := unittest.BlockWithParentFixture(block4.Header) 363 364 suite.AddBlocks(block1, block2, block3, block4, block5) 365 366 _, err := suite.builder.SealingSegment() 367 require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal) 368 } 369 370 // Test that we should return ErrSegmentBlocksWrongLen if sealing segment is 371 // built with no blocks. 372 func (suite *SealingSegmentSuite) TestBuild_NoBlocks() { 373 builder := flow.NewSealingSegmentBuilder(nil, nil) 374 _, err := builder.SealingSegment() 375 require.ErrorIs(suite.T(), err, flow.ErrSegmentBlocksWrongLen) 376 } 377 378 // should return ErrSegmentInvalidBlockHeight if block has invalid height 379 func (suite *SealingSegmentSuite) TestAddBlock_InvalidHeight() { 380 381 block1 := suite.FirstBlock() 382 // block 2 has an invalid height 383 block2 := unittest.BlockFixture() 384 block2.Header.Height = block1.Header.Height + 2 385 386 err := suite.builder.AddBlock(block1) 387 require.NoError(suite.T(), err) 388 389 err = suite.builder.AddBlock(&block2) 390 require.ErrorIs(suite.T(), err, flow.ErrSegmentInvalidBlockHeight) 391 } 392 393 // TestAddBlock_StorageError tests that errors in the resource getters bubble up. 394 func TestAddBlock_StorageError(t *testing.T) { 395 396 t.Run("missing result", func(t *testing.T) { 397 // create a receipt to include in the first block, whose result is not in storage 398 missingReceipt := unittest.ExecutionReceiptFixture() 399 block1 := unittest.BlockFixture() 400 sealLookup := func(flow.Identifier) (*flow.Seal, error) { return unittest.Seal.Fixture(), nil } 401 resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return nil, fmt.Errorf("not found") } 402 builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup) 403 404 block1.SetPayload(unittest.PayloadFixture( 405 unittest.WithReceiptsAndNoResults(missingReceipt), 406 unittest.WithSeals(unittest.Seal.Fixture(unittest.Seal.WithResult(&missingReceipt.ExecutionResult))), 407 )) 408 409 err := builder.AddBlock(&block1) 410 require.ErrorIs(t, err, flow.ErrSegmentResultLookup) 411 }) 412 413 // create a first block which contains no seal, and the seal isn't in storage 414 t.Run("missing seal", func(t *testing.T) { 415 resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return unittest.ExecutionResultFixture(), nil } 416 sealLookup := func(flow.Identifier) (*flow.Seal, error) { return nil, fmt.Errorf("not found") } 417 block1 := unittest.BlockFixture() 418 block1.SetPayload(flow.EmptyPayload()) 419 builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup) 420 421 err := builder.AddBlock(&block1) 422 require.ErrorIs(t, err, flow.ErrSegmentSealLookup) 423 }) 424 }