github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/validation/seal_validator_test.go (about) 1 package validation 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/mock" 7 "github.com/stretchr/testify/require" 8 "github.com/stretchr/testify/suite" 9 10 "github.com/onflow/flow-go/engine" 11 "github.com/onflow/flow-go/model/flow" 12 module "github.com/onflow/flow-go/module/mock" 13 "github.com/onflow/flow-go/module/updatable_configs" 14 "github.com/onflow/flow-go/utils/unittest" 15 ) 16 17 func TestSealValidator(t *testing.T) { 18 suite.Run(t, new(SealValidationSuite)) 19 } 20 21 type SealValidationSuite struct { 22 unittest.BaseChainSuite 23 24 sealValidator *sealValidator 25 metrics *module.ConsensusMetrics 26 publicKey *module.PublicKey 27 } 28 29 func (s *SealValidationSuite) SetupTest() { 30 s.SetupChain() 31 s.publicKey = &module.PublicKey{} 32 s.metrics = &module.ConsensusMetrics{} 33 34 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 35 s.Assigner, unittest.NewSealingConfigs(2), s.metrics) 36 } 37 38 // TestSealValid tests that a candidate block with a valid seal passes validation. 39 // We test with the following fork: 40 // 41 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0]}░ 42 // 43 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 44 // (see test `TestSeal_EnforceGap` for more details) 45 func (s *SealValidationSuite) TestSealValid() { 46 _, _, newBlock, _, _ := s.generateBasicTestFork() 47 _, err := s.sealValidator.Validate(newBlock) 48 s.Require().NoError(err) 49 } 50 51 // TestSeal_EnforceGap checks the seal-validation does _not_ allow to seal a result that was 52 // incorporated in the direct parent. In other words, there must be at least a 1-block gap 53 // between the block incorporating the result and the block sealing the result. Enforcing 54 // such gap is important for the following reason: 55 // - We need the Source of Randomness for the block that _incorporates_ the sealed result, 56 // to compute the verifier assignment. Therefore, we require that the block _incorporating_ 57 // the result has at least one child in the fork, _before_ we include the seal. 58 // - Thereby, we guarantee that a verifier assignment can be computed without needing 59 // unverified information (the unverified qc) from the block that we are just constructing. 60 // 61 // We test with the following fork: 62 // 63 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 64 // └-- ░newBlock{ Seal[B0] }░ 65 func (s *SealValidationSuite) TestSeal_EnforceGap() { 66 b1, _, _, _, sealB0 := s.generateBasicTestFork() 67 68 // block includes seal for direct parent: 69 newBlock := unittest.BlockWithParentFixture(b1.Header) 70 newBlock.SetPayload(flow.Payload{ 71 Seals: []*flow.Seal{sealB0}, 72 }) 73 74 _, err := s.sealValidator.Validate(newBlock) 75 s.Require().Error(err) 76 s.Require().True(engine.IsInvalidInputError(err)) 77 } 78 79 // TestSealInvalidBlockID tests that we reject seal with invalid blockID for 80 // submitted seal. We test with the following fork: 81 // 82 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0]}░ 83 // 84 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 85 // (see test `TestSeal_EnforceGap` for more details) 86 func (s *SealValidationSuite) TestSealInvalidBlockID() { 87 _, _, newBlock, _, seal := s.generateBasicTestFork() 88 seal.BlockID = unittest.IdentifierFixture() // modify seal (seal pointer already included in newBlock's payload) 89 90 _, err := s.sealValidator.Validate(newBlock) 91 s.Require().Error(err) 92 s.Require().True(engine.IsInvalidInputError(err)) 93 } 94 95 // TestSealInvalidAggregatedSigCount tests that we reject seal with invalid number of 96 // approval signatures for submitted seal. We test with the following fork: 97 // 98 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0]}░ 99 // 100 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 101 // (see test `TestSeal_EnforceGap` for more details) 102 func (s *SealValidationSuite) TestSealInvalidAggregatedSigCount() { 103 _, _, newBlock, _, seal := s.generateBasicTestFork() 104 seal.AggregatedApprovalSigs = seal.AggregatedApprovalSigs[1:] // modify seal (seal pointer already included in newBlock's payload) 105 106 // we want to make sure that the emergency-seal metric is not called because 107 // requiredApprovalsForSealVerification is > 0. 108 s.metrics.On("EmergencySeal").Run(func(args mock.Arguments) { 109 s.T().Errorf("should not count as emmergency sealed, because seal has fewer approvals than required for Seal Verification") 110 }).Return() 111 _, err := s.sealValidator.Validate(newBlock) 112 s.Require().Error(err) 113 s.Require().True(engine.IsInvalidInputError(err)) 114 } 115 116 // TestSealEmergencySeal checks that, when requiredApprovalsForSealVerification 117 // is 0, a seal which has 0 signatures for at least one chunk will be accepted, 118 // and that the emergency-seal metric will be incremented. 119 // We test with the following fork: 120 // 121 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0]}░ 122 // 123 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 124 // (see test `TestSeal_EnforceGap` for more details) 125 func (s *SealValidationSuite) TestSealEmergencySeal_VerificationRequire0ApprovalEmergencyNotTriggered() { 126 instance, err := updatable_configs.NewSealingConfigs(2, 0, 3, false) 127 require.NoError(s.T(), err) 128 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 129 s.Assigner, instance, s.metrics) 130 131 _, b2, _, receipt, _ := s.generateBasicTestFork() 132 133 // requiredApprovalsForSealConstruction = 2 134 // receive seal with 2 approvals => _not_ emergency sealed 135 newBlock := s.makeBlockSealingResult(b2.Header, &receipt.ExecutionResult, 2) 136 metrics := &module.ConsensusMetrics{} 137 metrics.On("EmergencySeal").Run(func(args mock.Arguments) { 138 s.T().Errorf("happy path sealing should not be counted as emmergency sealed") 139 }).Return() 140 s.sealValidator.metrics = metrics 141 142 _, err = s.sealValidator.Validate(newBlock) 143 s.Require().NoError(err) 144 } 145 146 func (s *SealValidationSuite) TestSealEmergencySeal_VerificationRequire1ApprovalReceive1Approval() { 147 instance, err := updatable_configs.NewSealingConfigs(2, 1, 3, false) 148 require.NoError(s.T(), err) 149 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 150 s.Assigner, instance, s.metrics) 151 152 _, b2, _, receipt, _ := s.generateBasicTestFork() 153 154 // requiredApprovalsForSealConstruction = 2 155 // requiredApprovalsForSealVerification = 1 156 // receive seal with 1 approval => emergency sealed 157 158 newBlock := s.makeBlockSealingResult(b2.Header, &receipt.ExecutionResult, 1) 159 metrics := &module.ConsensusMetrics{} 160 metrics.On("EmergencySeal").Once() 161 s.sealValidator.metrics = metrics 162 // 163 _, err = s.sealValidator.Validate(newBlock) 164 s.Require().NoError(err) 165 metrics.AssertExpectations(s.T()) 166 } 167 168 func (s *SealValidationSuite) TestSealEmergencySeal_VerificationRequire1ApprovalReceiveEmergencyNotTriggered() { 169 instance, err := updatable_configs.NewSealingConfigs(1, 1, 3, false) 170 require.NoError(s.T(), err) 171 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 172 s.Assigner, instance, s.metrics) 173 174 _, b2, _, receipt, _ := s.generateBasicTestFork() 175 176 // requiredApprovalsForSealConstruction = 2 177 // requiredApprovalsForSealVerification = 1 178 // receive seal with 0 approval => invalid 179 newBlock := s.makeBlockSealingResult(b2.Header, &receipt.ExecutionResult, 0) 180 metrics := &module.ConsensusMetrics{} 181 metrics.On("EmergencySeal").Run(func(args mock.Arguments) { 182 s.T().Errorf("invaid seal should not be counted as emmergency sealed") 183 }).Return() 184 s.sealValidator.metrics = metrics 185 // 186 _, err = s.sealValidator.Validate(newBlock) 187 s.Require().Error(err) 188 s.Require().True(engine.IsInvalidInputError(err)) 189 } 190 191 func (s *SealValidationSuite) TestSealEmergencySeal_VerificationRequire0ApprovalReceiveEmergencyTriggered() { 192 instance, err := updatable_configs.NewSealingConfigs(2, 0, 3, false) 193 require.NoError(s.T(), err) 194 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 195 s.Assigner, instance, s.metrics) 196 197 _, b2, _, receipt, _ := s.generateBasicTestFork() 198 199 // requiredApprovalsForSealConstruction = 2 200 // requiredApprovalsForSealVerification = 0 201 // receive seal with 1 approval => emergency sealed 202 203 newBlock := s.makeBlockSealingResult(b2.Header, &receipt.ExecutionResult, 1) 204 metrics := &module.ConsensusMetrics{} 205 metrics.On("EmergencySeal").Once() 206 s.sealValidator.metrics = metrics 207 // 208 _, err = s.sealValidator.Validate(newBlock) 209 s.Require().NoError(err) 210 metrics.AssertExpectations(s.T()) 211 } 212 213 func (s *SealValidationSuite) TestSealEmergencySeal_VerificationRequire0ApprovalReceive0ApprovalEmergencyTriggered() { 214 instance, err := updatable_configs.NewSealingConfigs(2, 0, 3, false) 215 require.NoError(s.T(), err) 216 s.sealValidator = NewSealValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB, 217 s.Assigner, instance, s.metrics) 218 219 _, b2, _, receipt, _ := s.generateBasicTestFork() 220 221 // requiredApprovalsForSealConstruction = 2 222 // requiredApprovalsForSealVerification = 0 223 // receive seal with 0 approval => emergency sealed 224 newBlock := s.makeBlockSealingResult(b2.Header, &receipt.ExecutionResult, 0) 225 metrics := &module.ConsensusMetrics{} 226 metrics.On("EmergencySeal").Once() 227 s.sealValidator.metrics = metrics 228 // 229 _, err = s.sealValidator.Validate(newBlock) 230 s.Require().NoError(err) 231 metrics.AssertExpectations(s.T()) 232 } 233 234 // TestSealInvalidChunkSignersCount tests that we reject seal with invalid approval signatures for 235 // submitted seal 236 func (s *SealValidationSuite) TestSealInvalidChunkSignersCount() { 237 _, _, newBlock, _, seal := s.generateBasicTestFork() 238 seal.AggregatedApprovalSigs[0].SignerIDs = seal.AggregatedApprovalSigs[0].SignerIDs[1:] // modify seal (seal pointer already included in newBlock's payload) 239 240 _, err := s.sealValidator.Validate(newBlock) 241 s.Require().Error(err) 242 s.Require().True(engine.IsInvalidInputError(err)) 243 } 244 245 // TestSealInvalidChunkSignaturesCount tests that we reject seal with invalid approval signatures for 246 // submitted seal 247 func (s *SealValidationSuite) TestSealInvalidChunkSignaturesCount() { 248 _, _, newBlock, _, seal := s.generateBasicTestFork() 249 250 // modify seal (seal pointer already included in newBlock's payload) 251 seal.AggregatedApprovalSigs[0].VerifierSignatures = seal.AggregatedApprovalSigs[0].VerifierSignatures[1:] 252 253 _, err := s.sealValidator.Validate(newBlock) 254 s.Require().Error(err) 255 s.Require().True(engine.IsInvalidInputError(err)) 256 } 257 258 // TestSealDuplicatedApproval verifies that the seal validator rejects an invalid 259 // seal where approvals are repeated. Otherwise, this would open up an attack vector, 260 // where the seal contains insufficient approvals when duplicating. 261 func (s *SealValidationSuite) TestSealDuplicatedApproval() { 262 _, _, newBlock, _, seal := s.generateBasicTestFork() 263 264 // modify seal (seal pointer already included in newBlock's payload) 265 seal.AggregatedApprovalSigs[0].VerifierSignatures[1] = seal.AggregatedApprovalSigs[0].VerifierSignatures[0] 266 seal.AggregatedApprovalSigs[0].SignerIDs[1] = seal.AggregatedApprovalSigs[0].SignerIDs[0] 267 268 _, err := s.sealValidator.Validate(newBlock) 269 s.Require().Error(err) 270 s.Require().True(engine.IsInvalidInputError(err)) 271 } 272 273 // TestSealInvalidChunkAssignment tests that we reject seal with invalid signerID of approval signature for 274 // submitted seal. We test with the following fork: 275 // 276 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0]}░ 277 // 278 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 279 // (see test `TestSeal_EnforceGap` for more details) 280 func (s *SealValidationSuite) TestSealInvalidChunkAssignment() { 281 _, _, newBlock, _, seal := s.generateBasicTestFork() 282 seal.AggregatedApprovalSigs[0].SignerIDs[0] = unittest.IdentifierFixture() // modify seal (seal pointer already included in newBlock's payload) 283 284 _, err := s.sealValidator.Validate(newBlock) 285 s.Require().Error(err) 286 s.Require().True(engine.IsInvalidInputError(err)) 287 } 288 289 // TestHighestSeal tests that Validate will pick the seal corresponding to the 290 // highest block when the payload contains multiple seals that are not ordered. 291 // We test with the following known fork: 292 // 293 // ... <- B1 <- B2 <- B3{Receipt(B2), Result(B2)} <- B4{Receipt(B3), Result(B3)} <- B5 294 // 295 // with 296 // - B1 is the latest sealed block: we use s.LatestSealedBlock, 297 // which has the result s.LatestExecutionResult 298 // - B2 is the latest finalized block: we use s.LatestFinalizedBlock 299 // 300 // Now we consider the new candidate block B6: 301 // 302 // ... <- B5 <-B6{ SealResult(B3), SealResult(B2) } 303 // 304 // Note that the order of the seals is specifically reversed. We expect that 305 // the validator handles this without error. 306 func (s *SealValidationSuite) TestHighestSeal() { 307 // take finalized block and build a receipt for it 308 block3 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 309 block2Receipt := unittest.ExecutionReceiptFixture( 310 unittest.WithExecutorID(s.ExeID), 311 unittest.WithResult(unittest.ExecutionResultFixture( 312 unittest.WithBlock(s.LatestFinalizedBlock), 313 unittest.WithPreviousResult(*s.LatestExecutionResult), 314 )), 315 ) 316 block3.SetPayload(flow.Payload{ 317 Receipts: []*flow.ExecutionReceiptMeta{block2Receipt.Meta()}, 318 Results: []*flow.ExecutionResult{&block2Receipt.ExecutionResult}, 319 }) 320 s.Extend(block3) 321 322 // create and insert block4 containing a receipt for block3 323 block3Receipt := unittest.ExecutionReceiptFixture( 324 unittest.WithExecutorID(s.ExeID), 325 unittest.WithResult(unittest.ExecutionResultFixture( 326 unittest.WithBlock(block3), 327 unittest.WithPreviousResult(block2Receipt.ExecutionResult), 328 )), 329 ) 330 block4 := unittest.BlockWithParentFixture(block3.Header) 331 block4.SetPayload(flow.Payload{ 332 Receipts: []*flow.ExecutionReceiptMeta{block3Receipt.Meta()}, 333 Results: []*flow.ExecutionResult{&block3Receipt.ExecutionResult}, 334 }) 335 s.Extend(block4) 336 337 // block B5 338 block5 := unittest.BlockWithParentFixture(block4.Header) 339 s.Extend(block5) 340 341 // construct block B5 and its payload components, i.e. seals for block B2 and B3 342 seal2 := s.validSealForResult(&block2Receipt.ExecutionResult) 343 seal3 := s.validSealForResult(&block3Receipt.ExecutionResult) 344 block6 := unittest.BlockWithParentFixture(block5.Header) 345 block6.SetPayload(flow.Payload{ 346 // placing seals in the reversed order to test 347 // Extend will pick the highest sealed block 348 Seals: []*flow.Seal{seal3, seal2}, 349 }) 350 351 last, err := s.sealValidator.Validate(block6) 352 require.NoError(s.T(), err) 353 require.Equal(s.T(), last.FinalState, seal3.FinalState) 354 } 355 356 // TestValidatePayload_SealsSkipBlock verifies that proposed seals 357 // are rejected if the chain of proposed seals skips a block. 358 // We test with the following known fork: 359 // 360 // S <- B0 <- B1 <- B2 <- B3{R(B0), R(B1), R(B2)} <- B4 361 // 362 // where S is the latest sealed block. 363 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 364 // (see test `TestSeal_EnforceGap` for more details) 365 // 366 // Now we consider the new candidate block X: 367 // 368 // S <- B0 <- B1 <- B2 <- B3{R(B0), R(B1), R(B2)} <- B4 <-X 369 // 370 // It would be valid for X to seal the chain of execution results R(B0), R(B1), R(B2) 371 // We test the two distinct failure cases: 372 // (i) X has no seal for the immediately next unsealed block B0 373 // (ii) X has a seal for the immediately next unsealed block B0 but skips 374 // 375 // the seal for one of the following blocks (here B1) 376 // 377 // In addition, we also run a valid test case to confirm the proper construction of the test 378 func (s *SealValidationSuite) TestValidatePayload_SealsSkipBlock() { 379 380 blocks := unittest.ChainFixtureFrom(4, s.LatestSealedBlock.Header) 381 382 // B3's payload contains results and receipts for B0, B1, B2 383 resultB0 := unittest.ExecutionResultFixture(unittest.WithBlock(blocks[0]), unittest.WithPreviousResult(*s.LatestExecutionResult)) 384 receipts := unittest.ReceiptChainFor(blocks, resultB0) 385 blocks[3].SetPayload(flow.Payload{ 386 Receipts: []*flow.ExecutionReceiptMeta{receipts[0].Meta(), receipts[1].Meta(), receipts[2].Meta()}, 387 Results: []*flow.ExecutionResult{&receipts[0].ExecutionResult, &receipts[1].ExecutionResult, &receipts[2].ExecutionResult}, 388 }) 389 b4 := unittest.BlockWithParentFixture(blocks[3].Header) 390 blocks = append(blocks, b4) 391 392 for _, b := range blocks { 393 s.Extend(b) 394 } 395 396 block0Seal := s.validSealForResult(&receipts[0].ExecutionResult) 397 block1Seal := s.validSealForResult(&receipts[1].ExecutionResult) 398 block2Seal := s.validSealForResult(&receipts[2].ExecutionResult) 399 400 // S <- B0 <- B1 <- B2 <- B3{R(B0), R(B1), R(B2)} <- B4 <- X{Seal(R(B1))} 401 s.T().Run("no seal for the immediately next unsealed block", func(t *testing.T) { 402 X := unittest.BlockWithParentFixture(b4.Header) 403 X.SetPayload(flow.Payload{ 404 Seals: []*flow.Seal{block1Seal}, 405 }) 406 407 _, err := s.sealValidator.Validate(X) 408 require.Error(s.T(), err) 409 require.True(s.T(), engine.IsInvalidInputError(err), err) 410 }) 411 412 // S <- B0 <- B1 <- B2 <- B3{R(B0), R(B1), R(B2)} <- B4 <- X{Seal(R(B0)), Seal(R(B2))} 413 s.T().Run("seals skip one of the following blocks", func(t *testing.T) { 414 X := unittest.BlockWithParentFixture(b4.Header) 415 X.SetPayload(flow.Payload{ 416 Seals: []*flow.Seal{block0Seal, block2Seal}, 417 }) 418 419 _, err := s.sealValidator.Validate(X) 420 require.Error(s.T(), err) 421 require.True(s.T(), engine.IsInvalidInputError(err), err) 422 }) 423 424 // S <- B0 <- B1 <- B2 <- B3{R(B0), R(B1), R(B2)} <- B4 <- X{Seal(R(B0)), Seal(R(B1)), Seal(R(B2))} 425 s.T().Run("valid test case", func(t *testing.T) { 426 X := unittest.BlockWithParentFixture(b4.Header) 427 X.SetPayload(flow.Payload{ 428 Seals: []*flow.Seal{block0Seal, block1Seal, block2Seal}, 429 }) 430 431 _, err := s.sealValidator.Validate(X) 432 require.NoError(s.T(), err) 433 }) 434 } 435 436 // TestExtendSeal_ExecutionDisconnected verifies that the Seal Validator only 437 // accepts a seals, if their execution results connect. 438 // Specifically, we test that the counter-example is rejected: 439 // - we have the chain in storage: 440 // . S <- A{Result[S]_1, Result[S]_2, ReceiptMeta[S]_1, ReceiptMeta[S]_2} 441 // . <- B{Result[A]_1, Result[A]_2, ReceiptMeta[A]_1, ReceiptMeta[A]_2} 442 // . <- C{Result[B]_1, Result[B]_2, ReceiptMeta[B]_1, ReceiptMeta[B]_2} 443 // . <- D{Seal for Result[S]_1} 444 // - Note that we are explicitly testing the handling of an execution fork that 445 // was incorporated _before_ the seal 446 // . Blocks: S <----------- A <----------- B 447 // . Results: Result[S]_1 <- Result[A]_1 <- Result[B]_1 :: the root of this execution tree is sealed 448 // . Result[S]_2 <- Result[A]_2 <- Result[B]_2 :: the root of this execution tree conflicts with sealed result 449 // - Now we consider the new candidate block X: 450 // S <- A{..} <- B{..} <- C{..} <- D{..} <- X 451 // 452 // We test the two distinct failure cases: 453 // - (i) illegal to seal Result[A]_2, because it is _not_ derived from the sealed result 454 // (we verify checking of the payload seals with respect to the existing seals) 455 // - (ii) illegal to seal Result[A]_1 followed by Result[B]_2, as Result[B]_2 not _not_ derived 456 // from the sealed result (we verify checking of the payload seals with respect to each other) 457 // 458 // In addition, we also run a valid test case to confirm the proper construction of the test 459 func (s *SealValidationSuite) TestValidatePayload_ExecutionDisconnected() { 460 461 blocks := []*flow.Block{&s.LatestSealedBlock} // slice with elements [S, A, B, C, D] 462 blocks = append(blocks, unittest.ChainFixtureFrom(4, s.LatestSealedBlock.Header)...) 463 receiptChain1 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[S]_1, Result[A]_1, Result[B]_1, ...] 464 receiptChain2 := unittest.ReceiptChainFor(blocks, unittest.ExecutionResultFixture()) // elements [Result[S]_2, Result[A]_2, Result[B]_2, ...] 465 466 for i := 1; i <= 3; i++ { // set payload for blocks A, B, C 467 blocks[i].SetPayload(flow.Payload{ 468 Results: []*flow.ExecutionResult{&receiptChain1[i-1].ExecutionResult, &receiptChain2[i-1].ExecutionResult}, 469 Receipts: []*flow.ExecutionReceiptMeta{receiptChain1[i-1].Meta(), receiptChain2[i-1].Meta()}, 470 }) 471 } 472 blocks[4].SetPayload(flow.Payload{ 473 Seals: []*flow.Seal{unittest.Seal.Fixture(unittest.Seal.WithResult(&receiptChain1[0].ExecutionResult))}, 474 }) 475 for i := 0; i <= 4; i++ { 476 // we need to run this several times, as in each iteration as we have _multiple_ execution chains. 477 // In each iteration, we only mange to reconnect one additional height 478 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain1) 479 unittest.ReconnectBlocksAndReceipts(blocks, receiptChain2) 480 } 481 482 for _, b := range blocks { 483 s.Extend(b) 484 } 485 486 // seals for inclusion in X 487 sealA1 := s.validSealForResult(&receiptChain1[1].ExecutionResult) 488 sealB1 := s.validSealForResult(&receiptChain1[2].ExecutionResult) 489 sealA2 := s.validSealForResult(&receiptChain2[1].ExecutionResult) 490 sealB2 := s.validSealForResult(&receiptChain2[2].ExecutionResult) 491 492 // S <- A{..} <- B{..} <- C{..} <- D{..} <- X{Seal for Result[A]_2} 493 s.T().Run("seals in candidate block does connect to latest sealed result of parent", func(t *testing.T) { 494 X := unittest.BlockWithParentFixture(blocks[4].Header) 495 X.SetPayload(flow.Payload{ 496 Seals: []*flow.Seal{sealA2}, 497 }) 498 499 _, err := s.sealValidator.Validate(X) 500 require.Error(s.T(), err) 501 require.True(s.T(), engine.IsInvalidInputError(err), err) 502 }) 503 504 // S <- A{..} <- B{..} <- C{..} <- D{..} <- X{Seal for Result[A]_1; Seal for Result[B]_2} 505 s.T().Run("sealed execution results within candidate block do not form a chain", func(t *testing.T) { 506 X := unittest.BlockWithParentFixture(blocks[4].Header) 507 X.SetPayload(flow.Payload{ 508 Seals: []*flow.Seal{sealA1, sealB2}, 509 }) 510 511 _, err := s.sealValidator.Validate(X) 512 require.Error(s.T(), err) 513 require.True(s.T(), engine.IsInvalidInputError(err), err) 514 }) 515 516 // S <- A{..} <- B{..} <- C{..} <- D{..} <- X{Seal for Result[A]_1; Seal for Result[B]_1} 517 s.T().Run("valid test case", func(t *testing.T) { 518 X := unittest.BlockWithParentFixture(blocks[4].Header) 519 X.SetPayload(flow.Payload{ 520 Seals: []*flow.Seal{sealA1, sealB1}, 521 }) 522 523 _, err := s.sealValidator.Validate(X) 524 require.NoError(s.T(), err) 525 }) 526 } 527 528 // TestSealValid tests that a candidate block with a valid seal passes validation. 529 // We test with the following fork: 530 // 531 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ... 532 // 533 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 534 // (see test `TestSeal_EnforceGap` for more details) 535 func (s *SealValidationSuite) TestExtendSealDuplicate() { 536 // <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- B3{S(R(B1))} <- B4{S(R(B1))} 537 s.T().Run("Duplicate seal in separate block", func(t *testing.T) { 538 _, _, b3, _, sealB1 := s.generateBasicTestFork() 539 // by itself, b3 should be a valid extension 540 _, err := s.sealValidator.Validate(b3) 541 require.NoError(t, err) 542 s.Extend(b3) 543 544 // insert B4 with a duplicate seal 545 b4 := unittest.BlockWithParentFixture(b3.Header) 546 b4.SetPayload(flow.Payload{ 547 Seals: []*flow.Seal{sealB1}, 548 }) 549 550 // we expect an error because block 4 contains a seal that is 551 // already contained in another block on the fork 552 _, err = s.sealValidator.Validate(b4) 553 require.Error(t, err) 554 require.True(t, engine.IsInvalidInputError(err), err) 555 }) 556 557 // <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- B3{S(R(B1)), S(R(B1))} 558 s.T().Run("Duplicate seal in same payload", func(t *testing.T) { 559 _, _, b3, _, sealB1 := s.generateBasicTestFork() 560 b3.SetPayload(flow.Payload{ 561 Seals: []*flow.Seal{sealB1, sealB1}, 562 }) 563 564 // we expect an error because block 3 contains duplicate seals within its payload 565 _, err := s.sealValidator.Validate(b3) 566 require.Error(t, err) 567 require.True(t, engine.IsInvalidInputError(err), err) 568 }) 569 } 570 571 // TestExtendSeal_NoIncorporatedResult tests that seals are rejected if _no_ for the sealed block 572 // was incorporated in blocks. We test with the following fork: 573 // 574 // ... <- LatestSealedBlock <- B0 <- B1 <- B2 <- ░newBlock{ Seal[B0]}░ 575 // 576 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 577 // (see test `TestSeal_EnforceGap` for more details) 578 func (s *SealValidationSuite) TestExtendSeal_NoIncorporatedResult() { 579 // Notes: 580 // * the result for `LatestSealedBlock` is `LatestExecutionResult` (already initialized in test setup) 581 // * as block B0, we use `LatestFinalizedBlock` (already initialized in test setup) 582 // construct block B1 and B2 583 b1 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 584 b2 := unittest.BlockWithParentFixture(b1.Header) 585 s.Extend(b1) 586 s.Extend(b2) 587 588 // construct `newBlock` 589 receipt := unittest.ExecutionReceiptFixture( 590 unittest.WithExecutorID(s.ExeID), 591 unittest.WithResult(unittest.ExecutionResultFixture( 592 unittest.WithBlock(s.LatestFinalizedBlock), 593 unittest.WithPreviousResult(*s.LatestExecutionResult), 594 )), 595 ) 596 seal := unittest.Seal.Fixture(unittest.Seal.WithResult(&receipt.ExecutionResult)) 597 newBlock := unittest.BlockWithParentFixture(b2.Header) 598 newBlock.SetPayload(flow.Payload{ 599 Seals: []*flow.Seal{seal}, 600 }) 601 602 // we expect an error because there is no block on the fork that 603 // contains a receipt committing to block1 604 _, err := s.sealValidator.Validate(newBlock) 605 s.Require().Error(err) 606 s.Require().True(engine.IsInvalidInputError(err), err) 607 } 608 609 // TestExtendSeal_DifferentIncorporatedResult tests that seals are rejected if a result is sealed that is 610 // different than the one incorporated in this fork. We test with the following fork: 611 // 612 // ... <- LatestSealedBlock <- B0 <- B1{ ER0a } <- B2 <- ░newBlock{ Seal[ER0b] }░ 613 // 614 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 615 // (see test `TestSeal_EnforceGap` for more details) 616 func (s *SealValidationSuite) TestExtendSeal_DifferentIncorporatedResult() { 617 _, _, newBlock, _, _ := s.generateBasicTestFork() 618 619 differentResult := unittest.ExecutionResultFixture( 620 unittest.WithBlock(s.LatestFinalizedBlock), 621 unittest.WithPreviousResult(*s.LatestExecutionResult), 622 ) 623 seal := unittest.Seal.Fixture(unittest.Seal.WithResult(differentResult)) 624 newBlock.SetPayload(flow.Payload{ 625 Seals: []*flow.Seal{seal}, 626 }) 627 628 // Should fail because ER0a is different than ER0b, although they 629 // reference the same block. Technically the fork does not contain an 630 // IncorporatedResult for the result referenced by the proposed seal. 631 _, err := s.sealValidator.Validate(newBlock) 632 s.Require().Error(err) 633 s.Require().True(engine.IsInvalidInputError(err), err) 634 } 635 636 // TestExtendSeal_ResultIncorporatedOnDifferentFork tests that seals are rejected if they correspond 637 // to ExecutionResults that are incorporated in a different fork 638 // We test with the following fork: 639 // 640 // ... <- LatestSealedBlock <- B0 <- B1 <- B2 <- ░newBlock{ Seal[ER0]}░ 641 // └-- A1{ ER0 } 642 // 643 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 644 // (see test `TestSeal_EnforceGap` for more details) 645 func (s *SealValidationSuite) TestExtendSeal_ResultIncorporatedOnDifferentFork() { 646 // Notes: 647 // * the result for `LatestSealedBlock` is `LatestExecutionResult` (already initialized in test setup) 648 // * as block B0, we use `LatestFinalizedBlock` (already initialized in test setup) 649 receipt := unittest.ExecutionReceiptFixture( 650 unittest.WithExecutorID(s.ExeID), 651 unittest.WithResult(unittest.ExecutionResultFixture( 652 unittest.WithBlock(s.LatestFinalizedBlock), 653 unittest.WithPreviousResult(*s.LatestExecutionResult), 654 )), 655 ) 656 657 // construct block A1 658 a1 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 659 a1.SetPayload(flow.Payload{ 660 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 661 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 662 }) 663 s.Extend(a1) 664 665 // construct block B1 and B2 666 b1 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 667 b2 := unittest.BlockWithParentFixture(b1.Header) 668 s.Extend(b1) 669 s.Extend(b2) 670 671 // construct `newBlock` 672 seal := s.validSealForResult(&receipt.ExecutionResult) 673 newBlock := unittest.BlockWithParentFixture(b2.Header) 674 newBlock.SetPayload(flow.Payload{ 675 Seals: []*flow.Seal{seal}, 676 }) 677 678 // we expect an error because there is no block on the fork that 679 // contains a receipt committing to the seal's result 680 _, err := s.sealValidator.Validate(newBlock) 681 s.Require().Error(err) 682 s.Require().True(engine.IsInvalidInputError(err), err) 683 } 684 685 // validSealForResult generates a valid seal based on ExecutionResult. As part of seal generation it 686 // configures mocked seal verifier to match approvals based on chunk assignments. 687 func (s *SealValidationSuite) validSealForResult(result *flow.ExecutionResult) *flow.Seal { 688 seal := unittest.Seal.Fixture(unittest.Seal.WithResult(result)) 689 690 assignment := s.Assignments[result.ID()] 691 for _, chunk := range result.Chunks { 692 aggregatedSigs := &seal.AggregatedApprovalSigs[chunk.Index] 693 assignedVerifiers := assignment.Verifiers(chunk) 694 aggregatedSigs.SignerIDs = assignedVerifiers[:] 695 aggregatedSigs.VerifierSignatures = unittest.SignaturesFixture(len(assignedVerifiers)) 696 697 for i, aggregatedSig := range aggregatedSigs.VerifierSignatures { 698 payload := flow.Attestation{ 699 BlockID: result.BlockID, 700 ExecutionResultID: result.ID(), 701 ChunkIndex: chunk.Index, 702 }.ID() 703 // assuming all signatures are valid 704 s.Identities[aggregatedSigs.SignerIDs[i]].StakingPubKey = s.publicKey 705 s.publicKey.On("Verify", 706 aggregatedSig, 707 payload[:], 708 mock.Anything, 709 ).Return(true, nil).Maybe() 710 } 711 } 712 return seal 713 } 714 715 // makeBlockSealingResult constructs a child block of `parentBlock`, whose payload includes 716 // a seal for `sealedResult`. For each chunk, the seal has aggregated approval signatures from 717 // `numberApprovals` assigned verification Nodes. 718 // Note: numberApprovals cannot be larger than the number of assigned verification nodes. 719 func (s *SealValidationSuite) makeBlockSealingResult(parentBlock *flow.Header, sealedResult *flow.ExecutionResult, numberApprovals int) *flow.Block { 720 seal := s.validSealForResult(sealedResult) 721 for chunkIndex := 0; chunkIndex < len(seal.AggregatedApprovalSigs); chunkIndex++ { 722 seal.AggregatedApprovalSigs[chunkIndex].SignerIDs = seal.AggregatedApprovalSigs[chunkIndex].SignerIDs[:numberApprovals] 723 seal.AggregatedApprovalSigs[chunkIndex].VerifierSignatures = seal.AggregatedApprovalSigs[chunkIndex].VerifierSignatures[:numberApprovals] 724 } 725 726 sealingBlock := unittest.BlockWithParentFixture(parentBlock) 727 sealingBlock.SetPayload(flow.Payload{ 728 Seals: []*flow.Seal{seal}, 729 }) 730 return sealingBlock 731 } 732 733 // generateBasicTestFork initializes the following fork (basis for many tests): 734 // 735 // ... <- LatestSealedBlock <- B0 <- B1{ Result[B0], Receipt[B0] } <- B2 <- ░newBlock{ Seal[B0] }░ 736 // 737 // The gap of 1 block, i.e. B2, is required to avoid a sealing edge-case 738 // (see test `TestSeal_EnforceGap` for more details). 739 // Notes: 740 // - the result for `LatestSealedBlock` is `LatestExecutionResult` (already initialized in test setup) 741 // - as block B0, we use `LatestFinalizedBlock` (already initialized in test setup) 742 // 743 // Returns: (B1, B2, newBlock, Receipt[B0], Seal[B0]) 744 func (s *SealValidationSuite) generateBasicTestFork() (*flow.Block, *flow.Block, *flow.Block, *flow.ExecutionReceipt, *flow.Seal) { 745 receipt := unittest.ExecutionReceiptFixture( 746 unittest.WithExecutorID(s.ExeID), 747 unittest.WithResult(unittest.ExecutionResultFixture( 748 unittest.WithBlock(s.LatestFinalizedBlock), 749 unittest.WithPreviousResult(*s.LatestExecutionResult), 750 )), 751 ) 752 753 // construct block B1 and B2 754 b1 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 755 b1.SetPayload(flow.Payload{ 756 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 757 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 758 }) 759 b2 := unittest.BlockWithParentFixture(b1.Header) 760 s.Extend(b1) 761 s.Extend(b2) 762 763 seal := s.validSealForResult(&receipt.ExecutionResult) 764 newBlock := unittest.BlockWithParentFixture(b2.Header) 765 newBlock.SetPayload(flow.Payload{ 766 Seals: []*flow.Seal{seal}, 767 }) 768 769 return b1, b2, newBlock, receipt, seal 770 }