github.com/koko1123/flow-go-1@v0.29.6/module/validation/receipt_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/koko1123/flow-go-1/engine" 11 "github.com/koko1123/flow-go-1/model/flow" 12 "github.com/koko1123/flow-go-1/module" 13 fmock "github.com/koko1123/flow-go-1/module/mock" 14 "github.com/koko1123/flow-go-1/utils/unittest" 15 ) 16 17 func TestReceiptValidator(t *testing.T) { 18 suite.Run(t, new(ReceiptValidationSuite)) 19 } 20 21 type ReceiptValidationSuite struct { 22 unittest.BaseChainSuite 23 24 receiptValidator module.ReceiptValidator 25 publicKey *fmock.PublicKey 26 } 27 28 func (s *ReceiptValidationSuite) SetupTest() { 29 s.SetupChain() 30 s.publicKey = &fmock.PublicKey{} 31 s.Identities[s.ExeID].StakingPubKey = s.publicKey 32 s.receiptValidator = NewReceiptValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB) 33 } 34 35 // TestReceiptValid try submitting valid receipt 36 func (s *ReceiptValidationSuite) TestReceiptValid() { 37 valSubgrph := s.ValidSubgraphFixture() 38 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 39 unittest.WithResult(valSubgrph.Result)) 40 s.AddSubgraphFixtureToMempools(valSubgrph) 41 42 receiptID := receipt.ID() 43 s.publicKey.On("Verify", 44 receipt.ExecutorSignature, 45 receiptID[:], 46 mock.Anything, 47 ).Return(true, nil).Once() 48 49 err := s.receiptValidator.Validate(receipt) 50 s.Require().NoError(err, "should successfully validate receipt") 51 s.publicKey.AssertExpectations(s.T()) 52 } 53 54 // TestReceiptNoIdentity tests that we reject receipt with invalid `ExecutionResult.ExecutorID` 55 func (s *ReceiptValidationSuite) TestReceiptNoIdentity() { 56 valSubgrph := s.ValidSubgraphFixture() 57 node := unittest.IdentityFixture() 58 mockPk := &fmock.PublicKey{} 59 node.StakingPubKey = mockPk 60 61 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(node.NodeID), 62 unittest.WithResult(valSubgrph.Result)) 63 s.AddSubgraphFixtureToMempools(valSubgrph) 64 receiptID := receipt.ID() 65 66 mockPk.On("Verify", 67 receiptID[:], 68 receipt.ExecutorSignature, 69 mock.Anything, 70 ).Return(true, nil).Once() 71 err := s.receiptValidator.Validate(receipt) 72 s.Require().Error(err, "should reject invalid identity") 73 s.Assert().True(engine.IsInvalidInputError(err)) 74 } 75 76 // TestReceiptFromZeroWeightNode tests that we reject receipt from node with zero weight 77 func (s *ReceiptValidationSuite) TestReceiptFromZeroWeightNode() { 78 valSubgrph := s.ValidSubgraphFixture() 79 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 80 unittest.WithResult(valSubgrph.Result)) 81 s.AddSubgraphFixtureToMempools(valSubgrph) 82 83 s.publicKey.On("Verify", 84 mock.Anything, 85 mock.Anything, 86 mock.Anything).Return(true, nil).Maybe() // call optional, as validator might check weight first 87 88 // replace weight with invalid one 89 s.Identities[s.ExeID].Weight = 0 90 91 err := s.receiptValidator.Validate(receipt) 92 s.Require().Error(err, "should reject invalid weight") 93 s.Assert().True(engine.IsInvalidInputError(err)) 94 } 95 96 // TestReceiptInvalidRole tests that we reject receipt with invalid execution node role 97 func (s *ReceiptValidationSuite) TestReceiptInvalidRole() { 98 valSubgrph := s.ValidSubgraphFixture() 99 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 100 unittest.WithResult(valSubgrph.Result)) 101 s.AddSubgraphFixtureToMempools(valSubgrph) 102 103 s.publicKey.On("Verify", 104 mock.Anything, 105 mock.Anything, 106 mock.Anything).Return(true, nil).Maybe() // call optional, as validator might check weight first 107 108 // replace identity with invalid one 109 s.Identities[s.ExeID] = unittest.IdentityFixture(unittest.WithRole(flow.RoleConsensus)) 110 111 err := s.receiptValidator.Validate(receipt) 112 s.Require().Error(err, "should reject invalid identity") 113 s.Assert().True(engine.IsInvalidInputError(err)) 114 } 115 116 // TestReceiptInvalidSignature tests that we reject receipt with invalid signature 117 func (s *ReceiptValidationSuite) TestReceiptInvalidSignature() { 118 119 valSubgrph := s.ValidSubgraphFixture() 120 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 121 unittest.WithResult(valSubgrph.Result)) 122 s.AddSubgraphFixtureToMempools(valSubgrph) 123 124 s.publicKey.On("Verify", 125 mock.Anything, 126 mock.Anything, 127 mock.Anything, 128 ).Return(false, nil).Once() 129 130 err := s.receiptValidator.Validate(receipt) 131 s.Require().Error(err, "should reject invalid signature") 132 s.Assert().True(engine.IsInvalidInputError(err)) 133 s.publicKey.AssertExpectations(s.T()) 134 } 135 136 // TestReceiptTooFewChunks tests that we reject receipt with invalid chunk count 137 func (s *ReceiptValidationSuite) TestReceiptTooFewChunks() { 138 valSubgrph := s.ValidSubgraphFixture() 139 chunks := valSubgrph.Result.Chunks 140 valSubgrph.Result.Chunks = chunks[0 : len(chunks)-2] // drop the last chunk 141 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 142 unittest.WithResult(valSubgrph.Result)) 143 s.AddSubgraphFixtureToMempools(valSubgrph) 144 145 s.publicKey.On("Verify", 146 mock.Anything, 147 mock.Anything, 148 mock.Anything).Return(true, nil).Maybe() 149 150 err := s.receiptValidator.Validate(receipt) 151 s.Require().Error(err, "should reject with invalid chunks") 152 s.Assert().True(engine.IsInvalidInputError(err)) 153 } 154 155 // TestReceiptTooManyChunks tests that we reject receipt with more chunks than expected 156 func (s *ReceiptValidationSuite) TestReceiptTooManyChunks() { 157 valSubgrph := s.ValidSubgraphFixture() 158 chunks := valSubgrph.Result.Chunks 159 valSubgrph.Result.Chunks = append(chunks, chunks[len(chunks)-1]) // duplicate the last chunk 160 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 161 unittest.WithResult(valSubgrph.Result)) 162 s.AddSubgraphFixtureToMempools(valSubgrph) 163 164 s.publicKey.On("Verify", 165 mock.Anything, 166 mock.Anything, 167 mock.Anything).Return(true, nil).Maybe() 168 169 err := s.receiptValidator.Validate(receipt) 170 s.Require().Error(err, "should reject with invalid chunks") 171 s.Assert().True(engine.IsInvalidInputError(err)) 172 } 173 174 // TestReceiptChunkInvalidBlockID tests that we reject receipt with invalid chunk blockID 175 func (s *ReceiptValidationSuite) TestReceiptChunkInvalidBlockID() { 176 valSubgrph := s.ValidSubgraphFixture() 177 valSubgrph.Result.Chunks[0].BlockID = unittest.IdentifierFixture() 178 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 179 unittest.WithResult(valSubgrph.Result)) 180 s.AddSubgraphFixtureToMempools(valSubgrph) 181 182 s.publicKey.On("Verify", 183 mock.Anything, 184 mock.Anything, 185 mock.Anything).Return(true, nil).Maybe() 186 187 err := s.receiptValidator.Validate(receipt) 188 s.Require().Error(err, "should reject with invalid chunks") 189 s.Assert().True(engine.IsInvalidInputError(err)) 190 } 191 192 // TestReceiptInvalidCollectionIndex tests that we reject receipt with invalid chunk collection index 193 func (s *ReceiptValidationSuite) TestReceiptInvalidCollectionIndex() { 194 valSubgrph := s.ValidSubgraphFixture() 195 valSubgrph.Result.Chunks[0].CollectionIndex = 42 196 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 197 unittest.WithResult(valSubgrph.Result)) 198 s.AddSubgraphFixtureToMempools(valSubgrph) 199 200 s.publicKey.On("Verify", 201 mock.Anything, 202 mock.Anything, 203 mock.Anything).Return(true, nil).Maybe() 204 205 err := s.receiptValidator.Validate(receipt) 206 s.Require().Error(err, "should reject invalid collection index") 207 s.Assert().True(engine.IsInvalidInputError(err)) 208 } 209 210 // TestReceiptNoPreviousResult tests that we reject receipt with missing previous result 211 func (s *ReceiptValidationSuite) TestReceiptNoPreviousResult() { 212 valSubgrph := s.ValidSubgraphFixture() 213 // invalidate prev execution result, it will result in failing to lookup 214 // prev result during sub-graph check 215 valSubgrph.PreviousResult = unittest.ExecutionResultFixture() 216 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 217 unittest.WithResult(valSubgrph.Result)) 218 s.AddSubgraphFixtureToMempools(valSubgrph) 219 220 s.publicKey.On("Verify", 221 mock.Anything, 222 mock.Anything, 223 mock.Anything).Return(true, nil).Maybe() 224 225 err := s.receiptValidator.Validate(receipt) 226 s.Require().Error(err, "should reject invalid receipt") 227 s.Assert().True(engine.IsUnverifiableInputError(err), err) 228 } 229 230 // TestReceiptInvalidPreviousResult tests that we reject receipt with invalid previous result 231 func (s *ReceiptValidationSuite) TestReceiptInvalidPreviousResult() { 232 valSubgrph := s.ValidSubgraphFixture() 233 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 234 unittest.WithResult(valSubgrph.Result)) 235 s.AddSubgraphFixtureToMempools(valSubgrph) 236 237 // invalidate prev execution result blockID, this should fail because 238 // prev result points to wrong block 239 valSubgrph.PreviousResult.BlockID = unittest.IdentifierFixture() 240 241 s.publicKey.On("Verify", 242 mock.Anything, 243 mock.Anything, 244 mock.Anything).Return(true, nil).Maybe() 245 246 err := s.receiptValidator.Validate(receipt) 247 s.Require().Error(err, "should reject invalid previous result") 248 s.Assert().True(engine.IsInvalidInputError(err), err) 249 } 250 251 // TestReceiptInvalidResultChain tests that we reject receipts, 252 // where the start state does not match the parent result's end state 253 func (s *ReceiptValidationSuite) TestReceiptInvalidResultChain() { 254 valSubgrph := s.ValidSubgraphFixture() 255 receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), 256 unittest.WithResult(valSubgrph.Result)) 257 s.AddSubgraphFixtureToMempools(valSubgrph) 258 259 // invalidate prev execution result blockID, this should fail because 260 // prev result points to wrong block 261 valSubgrph.PreviousResult.Chunks[len(valSubgrph.Result.Chunks)-1].EndState = unittest.StateCommitmentFixture() 262 263 s.publicKey.On("Verify", 264 mock.Anything, 265 mock.Anything, 266 mock.Anything).Return(true, nil).Maybe() 267 268 err := s.receiptValidator.Validate(receipt) 269 s.Require().Error(err, "should reject invalid previous result") 270 s.Assert().True(engine.IsInvalidInputError(err), err) 271 } 272 273 // TestMultiReceiptValidResultChain tests that multiple receipts and results 274 // within one block payload are accepted, where the receipts are building on 275 // top of each other (i.e. their results form a chain). 276 // Say B(A) means block B has receipt for A: 277 // - we have such chain in storage: G <- A <- B(A) <- C 278 // - if a child block of C payload contains receipts and results for (B,C) 279 // it should be accepted as valid 280 func (s *ReceiptValidationSuite) TestMultiReceiptValidResultChain() { 281 // assuming signatures are all good 282 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 283 284 // G <- A <- B <- C 285 blocks, result0, seal := unittest.ChainFixture(4) 286 s.SealsIndex[blocks[0].ID()] = seal 287 288 receipts := unittest.ReceiptChainFor(blocks, result0) 289 blockA, blockB, blockC := blocks[1], blocks[2], blocks[3] 290 receiptA, receiptB, receiptC := receipts[1], receipts[2], receipts[3] 291 292 blockA.Payload.Receipts = []*flow.ExecutionReceiptMeta{} 293 blockB.Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptA.Meta()} 294 blockB.Payload.Results = []*flow.ExecutionResult{&receiptA.ExecutionResult} 295 blockC.Payload.Receipts = []*flow.ExecutionReceiptMeta{} 296 // update block header so that blocks are chained together 297 unittest.ReconnectBlocksAndReceipts(blocks, receipts) 298 // assuming all receipts are executed by the correct executor 299 for _, r := range receipts { 300 r.ExecutorID = s.ExeID 301 } 302 303 for _, b := range blocks { 304 s.Extend(b) 305 } 306 s.PersistedResults[result0.ID()] = result0 307 308 candidate := unittest.BlockWithParentFixture(blockC.Header) 309 candidate.Payload = &flow.Payload{ 310 Receipts: []*flow.ExecutionReceiptMeta{receiptB.Meta(), receiptC.Meta()}, 311 Results: []*flow.ExecutionResult{&receiptB.ExecutionResult, &receiptC.ExecutionResult}, 312 } 313 314 err := s.receiptValidator.ValidatePayload(candidate) 315 s.Require().NoError(err) 316 } 317 318 // we have such chain in storage: G <- A <- B(A) <- C 319 // if a block payload contains (C,B_bad), they should be invalid 320 func (s *ReceiptValidationSuite) TestMultiReceiptInvalidParent() { 321 // assuming signatures are all good 322 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 323 324 // G <- A <- B <- C 325 blocks, result0, seal := unittest.ChainFixture(4) 326 s.SealsIndex[blocks[0].ID()] = seal 327 328 receipts := unittest.ReceiptChainFor(blocks, result0) 329 blockA, blockB, blockC := blocks[1], blocks[2], blocks[3] 330 receiptA := receipts[1] 331 receiptBInvalid := receipts[2] 332 receiptC := receipts[3] 333 blockA.Payload.Receipts = []*flow.ExecutionReceiptMeta{} 334 blockB.Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptA.Meta()} 335 blockB.Payload.Results = []*flow.ExecutionResult{&receiptA.ExecutionResult} 336 blockC.Payload.Receipts = []*flow.ExecutionReceiptMeta{} 337 // update block header so that blocks are chained together 338 unittest.ReconnectBlocksAndReceipts(blocks, receipts) 339 // assuming all receipts are executed by the correct executor 340 for _, r := range receipts { 341 r.ExecutorID = s.ExeID 342 } 343 344 for _, b := range blocks { 345 s.Extend(b) 346 } 347 s.PersistedResults[result0.ID()] = result0 348 349 // make receipt B as bad 350 receiptBInvalid.ExecutorID = unittest.IdentifierFixture() 351 352 candidate := unittest.BlockWithParentFixture(blockC.Header) 353 candidate.Payload = &flow.Payload{ 354 Receipts: []*flow.ExecutionReceiptMeta{receiptBInvalid.Meta(), receiptC.Meta()}, 355 Results: []*flow.ExecutionResult{&receiptBInvalid.ExecutionResult, &receiptC.ExecutionResult}, 356 } 357 358 // receiptB and receiptC 359 err := s.receiptValidator.ValidatePayload(candidate) 360 s.Require().Error(err) 361 require.True(s.T(), engine.IsInvalidInputError(err), err) 362 } 363 364 // Test that `ValidatePayload` will refuse payloads that contain receipts for blocks that 365 // are already sealed on the fork, but will accept receipts for blocks that are 366 // sealed on another fork. 367 func (s *ReceiptValidationSuite) TestValidationReceiptsForSealedBlock() { 368 // assuming signatures are all good 369 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 370 371 // create block2 372 block2 := unittest.BlockWithParentFixture(s.LatestSealedBlock.Header) 373 block2.SetPayload(flow.Payload{}) 374 s.Extend(block2) 375 376 block2Receipt := unittest.ExecutionReceiptFixture(unittest.WithResult( 377 unittest.ExecutionResultFixture(unittest.WithBlock(block2), 378 unittest.WithPreviousResult(*s.LatestExecutionResult)))) 379 380 // B1<--B2<--B3{R{B2)}<--B4{S(R(B2))}<--B5{R'(B2)} 381 382 // create block3 with a receipt for block2 383 block3 := unittest.BlockWithParentFixture(block2.Header) 384 block3.SetPayload(flow.Payload{ 385 Receipts: []*flow.ExecutionReceiptMeta{block2Receipt.Meta()}, 386 Results: []*flow.ExecutionResult{&block2Receipt.ExecutionResult}, 387 }) 388 s.Extend(block3) 389 390 // create a seal for block2 391 seal2 := unittest.Seal.Fixture(unittest.Seal.WithResult(&block2Receipt.ExecutionResult)) 392 393 // create block4 containing a seal for block2 394 block4 := unittest.BlockWithParentFixture(block3.Header) 395 block4.SetPayload(flow.Payload{ 396 Seals: []*flow.Seal{seal2}, 397 }) 398 s.Extend(block4) 399 400 // insert another receipt for block 2, which is now the highest sealed 401 // block, and ensure that the receipt is rejected 402 receipt := unittest.ExecutionReceiptFixture(unittest.WithResult( 403 unittest.ExecutionResultFixture(unittest.WithBlock(block2), 404 unittest.WithPreviousResult(*s.LatestExecutionResult))), 405 unittest.WithExecutorID(s.ExeID)) 406 block5 := unittest.BlockWithParentFixture(block4.Header) 407 block5.SetPayload(flow.Payload{ 408 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 409 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 410 }) 411 412 err := s.receiptValidator.ValidatePayload(block5) 413 require.Error(s.T(), err) 414 require.True(s.T(), engine.IsInvalidInputError(err), err) 415 416 // B1<--B2<--B3{R{B2)}<--B4{S(R(B2))}<--B5{R'(B2)} 417 // | 418 // +---B6{R''(B2)} 419 420 // insert another receipt for B2 but in a separate fork. The fact that 421 // B2 is sealed on a separate fork should not cause the receipt to be 422 // rejected 423 block6 := unittest.BlockWithParentFixture(block2.Header) 424 block6.SetPayload(flow.Payload{ 425 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 426 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 427 }) 428 err = s.receiptValidator.ValidatePayload(block6) 429 require.NoError(s.T(), err) 430 } 431 432 // Test that validator will accept payloads with receipts that are referring execution results 433 // which were incorporated in previous blocks of fork. 434 func (s *ReceiptValidationSuite) TestValidationReceiptForIncorporatedResult() { 435 // assuming signatures are all good 436 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 437 438 // create block2 439 block2 := unittest.BlockWithParentFixture(s.LatestSealedBlock.Header) 440 block2.SetPayload(flow.Payload{}) 441 s.Extend(block2) 442 443 executionResult := unittest.ExecutionResultFixture(unittest.WithBlock(block2), 444 unittest.WithPreviousResult(*s.LatestExecutionResult)) 445 firstReceipt := unittest.ExecutionReceiptFixture( 446 unittest.WithResult(executionResult), 447 unittest.WithExecutorID(s.ExeID)) 448 449 // B1<--B2<--B3{R{B2)}<--B4{(R'(B2))} 450 451 // create block3 with a receipt for block2 452 block3 := unittest.BlockWithParentFixture(block2.Header) 453 block3.SetPayload(flow.Payload{ 454 Receipts: []*flow.ExecutionReceiptMeta{firstReceipt.Meta()}, 455 Results: []*flow.ExecutionResult{&firstReceipt.ExecutionResult}, 456 }) 457 s.Extend(block3) 458 459 exe := unittest.IdentityFixture(unittest.WithRole(flow.RoleExecution)) 460 s.Identities[exe.NodeID] = exe 461 exe.StakingPubKey = s.publicKey // make sure the other exection node's signatures are valid 462 463 // insert another receipt for block 2, it's a receipt from another execution node 464 // for the same result 465 secondReceipt := unittest.ExecutionReceiptFixture( 466 unittest.WithResult(executionResult), 467 unittest.WithExecutorID(exe.NodeID)) 468 block5 := unittest.BlockWithParentFixture(block3.Header) 469 block5.SetPayload(flow.Payload{ 470 // no results, only receipt 471 Receipts: []*flow.ExecutionReceiptMeta{secondReceipt.Meta()}, 472 }) 473 474 err := s.receiptValidator.ValidatePayload(block5) 475 require.NoError(s.T(), err) 476 } 477 478 // TestValidationReceiptWithoutIncorporatedResult verifies that receipts must commit 479 // to results that are included in the respective fork. Specifically, we test that 480 // the counter-example is rejected: 481 // - we have the chain in storage: 482 // . G <- A <- B 483 // . ^- C(Result[A], ReceiptMeta[A]) 484 // here, block C contains the result _and_ the receipt Meta-data for block A 485 // - now receive the new block X: G <- A <- B <- X(ReceiptMeta[A]) 486 // Note that X only contains the receipt for A, but _not_ the result. 487 // 488 // Block X must be considered invalid, because confirming validity of 489 // ReceiptMeta[A] requires information _not_ included in the fork. 490 func (s *ReceiptValidationSuite) TestValidationReceiptWithoutIncorporatedResult() { 491 // assuming signatures are all good 492 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 493 494 // create block A 495 blockA := unittest.BlockWithParentFixture(s.LatestSealedBlock.Header) // for block G, we use the LatestSealedBlock 496 s.Extend(blockA) 497 498 // result for A; and receipt for A 499 resultA := unittest.ExecutionResultFixture(unittest.WithBlock(blockA), unittest.WithPreviousResult(*s.LatestExecutionResult)) 500 receiptA := unittest.ExecutionReceiptFixture(unittest.WithResult(resultA), unittest.WithExecutorID(s.ExeID)) 501 502 // create block B and block C 503 blockB := unittest.BlockWithParentFixture(blockA.Header) 504 blockC := unittest.BlockWithParentFixture(blockA.Header) 505 blockC.SetPayload(flow.Payload{ 506 Receipts: []*flow.ExecutionReceiptMeta{receiptA.Meta()}, 507 Results: []*flow.ExecutionResult{resultA}, 508 }) 509 s.Extend(blockB) 510 s.Extend(blockC) 511 512 // create block X: 513 blockX := unittest.BlockWithParentFixture(blockB.Header) 514 blockX.SetPayload(flow.Payload{ 515 Receipts: []*flow.ExecutionReceiptMeta{receiptA.Meta()}, 516 }) 517 518 err := s.receiptValidator.ValidatePayload(blockX) 519 require.Error(s.T(), err) 520 require.True(s.T(), engine.IsInvalidInputError(err), err) 521 } 522 523 // TestPayloadWithExecutionFork checks that the Receipt Validator only 524 // accepts results that decent from the sealed result. Specifically, we test that 525 // the counter-example is rejected: 526 // 527 // - we have the chain in storage: 528 // 529 // . S <- A(Result[S]_1, Result[S]_2, ReceiptMeta[S]_1, ReceiptMeta[S]_2) 530 // . <- B(Seal for Result[S]_2) 531 // . <- X(Result[A]_1, Result[A]_2, Result[A]_3, 532 // . ReceiptMeta[A]_1, ReceiptMeta[A]_2, ReceiptMeta[A]_3) 533 // 534 // - Note that we are explicitly testing the handling of an execution fork _before_ 535 // and _after_ the sealed result 536 // 537 // . Blocks: S <----------- A 538 // . Results: Result[S]_1 <- Result[A]_1 :: the root of this execution tree conflicts with sealed result 539 // . Result[S]_2 <- Result[A]_2 :: the root of this execution tree is sealed 540 // . ^- Result[A]_3 541 // 542 // Expected Behaviour: 543 // In the fork which X extends, Result[S]_2 has been sealed. Hence, it should be: 544 // (i) illegal to include Result[A]_1, because it is _not_ derived from the sealed result. 545 // (ii) legal to include only results Result[A]_2 and Result[A]_3, as they are derived from the sealed result. 546 func (s *ReceiptValidationSuite) TestPayloadWithExecutionFork() { 547 // assuming signatures are all good 548 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 549 550 // block S: we use s.LatestSealedBlock; its result is s.LatestExecutionResult 551 blockS := s.LatestSealedBlock 552 resultS1 := s.LatestExecutionResult 553 receiptS1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultS1), unittest.WithExecutorID(s.ExeID)) 554 resultS2 := unittest.ExecutionResultFixture(unittest.WithBlock(&blockS)) 555 receiptS2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultS2), unittest.WithExecutorID(s.ExeID)) 556 557 // create block A, including results and receipts for it 558 blockA := unittest.BlockWithParentFixture(blockS.Header) 559 blockA.SetPayload(flow.Payload{ 560 Results: []*flow.ExecutionResult{resultS1, resultS2}, 561 Receipts: []*flow.ExecutionReceiptMeta{receiptS1.Meta(), receiptS2.Meta()}, 562 }) 563 s.Extend(blockA) 564 565 // create block B 566 blockB := unittest.BlockWithParentFixture(blockA.Header) 567 sealResultS2 := unittest.Seal.Fixture(unittest.Seal.WithBlock(blockS.Header), unittest.Seal.WithResult(resultS2)) 568 blockB.SetPayload(flow.Payload{ 569 Seals: []*flow.Seal{sealResultS2}, 570 }) 571 s.Extend(blockB) 572 573 // create Result[A]_1, Result[A]_2, Result[A]_3 and their receipts 574 resultA1 := unittest.ExecutionResultFixture(unittest.WithBlock(blockA), unittest.WithPreviousResult(*resultS1)) 575 receiptA1 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultA1), unittest.WithExecutorID(s.ExeID)) 576 resultA2 := unittest.ExecutionResultFixture(unittest.WithBlock(blockA), unittest.WithPreviousResult(*resultS2)) 577 receiptA2 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultA2), unittest.WithExecutorID(s.ExeID)) 578 resultA3 := unittest.ExecutionResultFixture(unittest.WithBlock(blockA), unittest.WithPreviousResult(*resultS2)) 579 receiptA3 := unittest.ExecutionReceiptFixture(unittest.WithResult(resultA3), unittest.WithExecutorID(s.ExeID)) 580 581 // SCENARIO (i): a block containing Result[A]_1 should fail validation 582 blockX := unittest.BlockWithParentFixture(blockB.Header) 583 blockX.SetPayload(flow.Payload{ 584 Results: []*flow.ExecutionResult{resultA1, resultA2, resultA3}, 585 Receipts: []*flow.ExecutionReceiptMeta{receiptA1.Meta(), receiptA2.Meta(), receiptA3.Meta()}, 586 }) 587 err := s.receiptValidator.ValidatePayload(blockX) 588 require.Error(s.T(), err) 589 require.True(s.T(), engine.IsInvalidInputError(err), err) 590 591 // SCENARIO (ii): a block containing only results Result[A]_2 and Result[A]_3 should pass validation 592 blockX = unittest.BlockWithParentFixture(blockB.Header) 593 blockX.SetPayload(flow.Payload{ 594 Results: []*flow.ExecutionResult{resultA2, resultA3}, 595 Receipts: []*flow.ExecutionReceiptMeta{receiptA2.Meta(), receiptA3.Meta()}, 596 }) 597 err = s.receiptValidator.ValidatePayload(blockX) 598 require.NoError(s.T(), err) 599 } 600 601 // TestMultiLevelExecutionTree verifies that a result is accepted that 602 // extends a multi-level execution tree : 603 // - Let S be the latest sealed block 604 // - we have the chain in storage: 605 // S <- A <- B(Result[A], ReceiptMeta[A]) <- C(Result[B], ReceiptMeta[B]) 606 // - now receive the new block X: 607 // S <- A <- B(Result[A], ReceiptMeta[A]) <- C(Result[B], ReceiptMeta[B]) <- X(Result[C], ReceiptMeta[C]) 608 // 609 // Block X should be considered valid, as it extends the 610 // Execution Tree with root latest sealed Result (i.e. result sealed for S) 611 func (s *ReceiptValidationSuite) TestMultiLevelExecutionTree() { 612 // assuming signatures are all good 613 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 614 615 // create block A, including result and receipt for it 616 blockA := unittest.BlockWithParentFixture(s.LatestSealedBlock.Header) 617 resultA := unittest.ExecutionResultFixture(unittest.WithBlock(blockA), unittest.WithPreviousResult(*s.LatestExecutionResult)) 618 receiptA := unittest.ExecutionReceiptFixture(unittest.WithResult(resultA), unittest.WithExecutorID(s.ExeID)) 619 s.Extend(blockA) 620 621 // create block B, including result and receipt for it 622 blockB := unittest.BlockWithParentFixture(blockA.Header) 623 blockB.SetPayload(flow.Payload{ 624 Receipts: []*flow.ExecutionReceiptMeta{receiptA.Meta()}, 625 Results: []*flow.ExecutionResult{resultA}, 626 }) 627 resultB := unittest.ExecutionResultFixture(unittest.WithBlock(blockB), unittest.WithPreviousResult(*resultA)) 628 receiptB := unittest.ExecutionReceiptFixture(unittest.WithResult(resultB), unittest.WithExecutorID(s.ExeID)) 629 s.Extend(blockB) 630 631 // create block C, including result and receipt for it 632 blockC := unittest.BlockWithParentFixture(blockB.Header) 633 blockC.SetPayload(flow.Payload{ 634 Receipts: []*flow.ExecutionReceiptMeta{receiptB.Meta()}, 635 Results: []*flow.ExecutionResult{resultB}, 636 }) 637 resultC := unittest.ExecutionResultFixture(unittest.WithBlock(blockC), unittest.WithPreviousResult(*resultB)) 638 receiptC := unittest.ExecutionReceiptFixture(unittest.WithResult(resultC), unittest.WithExecutorID(s.ExeID)) 639 s.Extend(blockC) 640 641 // create block X: 642 blockX := unittest.BlockWithParentFixture(blockC.Header) 643 blockX.SetPayload(flow.Payload{ 644 Receipts: []*flow.ExecutionReceiptMeta{receiptC.Meta()}, 645 Results: []*flow.ExecutionResult{resultC}, 646 }) 647 648 err := s.receiptValidator.ValidatePayload(blockX) 649 require.NoError(s.T(), err) 650 } 651 652 // Test that validator will reject payloads that contain receipts for blocks that 653 // are not on the fork 654 // 655 // B1<--B2<--B3 656 // | 657 // +----B4{R(B3)} 658 func (s *ReceiptValidationSuite) TestValidationReceiptsBlockNotOnFork() { 659 // create block2 660 block2 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 661 block2.Payload.Guarantees = nil 662 block2.Header.PayloadHash = block2.Payload.Hash() 663 s.Extend(block2) 664 665 // create block3 666 block3 := unittest.BlockWithParentFixture(block2.Header) 667 block3.SetPayload(flow.Payload{}) 668 s.Extend(block3) 669 670 block3Receipt := unittest.ReceiptForBlockFixture(block3) 671 672 block4 := unittest.BlockWithParentFixture(block2.Header) 673 block4.SetPayload(flow.Payload{ 674 Receipts: []*flow.ExecutionReceiptMeta{block3Receipt.Meta()}, 675 Results: []*flow.ExecutionResult{&block3Receipt.ExecutionResult}, 676 }) 677 err := s.receiptValidator.ValidatePayload(block4) 678 require.Error(s.T(), err) 679 require.True(s.T(), engine.IsInvalidInputError(err), err) 680 } 681 682 // Test that Extend will refuse payloads that contain duplicate receipts, where 683 // duplicates can be in another block on the fork, or within the payload. 684 func (s *ReceiptValidationSuite) TestExtendReceiptsDuplicate() { 685 686 block2 := unittest.BlockWithParentFixture(s.LatestFinalizedBlock.Header) 687 block2.SetPayload(flow.Payload{}) 688 s.Extend(block2) 689 690 receipt := unittest.ReceiptForBlockFixture(block2) 691 692 // B1 <- B2 <- B3{R(B2)} <- B4{R(B2)} 693 s.T().Run("duplicate receipt in different block", func(t *testing.T) { 694 block3 := unittest.BlockWithParentFixture(block2.Header) 695 block3.SetPayload(flow.Payload{ 696 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 697 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 698 }) 699 s.Extend(block3) 700 701 block4 := unittest.BlockWithParentFixture(block3.Header) 702 block4.SetPayload(flow.Payload{ 703 Receipts: []*flow.ExecutionReceiptMeta{receipt.Meta()}, 704 Results: []*flow.ExecutionResult{&receipt.ExecutionResult}, 705 }) 706 err := s.receiptValidator.ValidatePayload(block4) 707 require.Error(t, err) 708 require.True(t, engine.IsInvalidInputError(err), err) 709 }) 710 711 // B1 <- B2 <- B3{R(B2), R(B2)} 712 s.T().Run("duplicate receipt in same block", func(t *testing.T) { 713 block3 := unittest.BlockWithParentFixture(block2.Header) 714 block3.SetPayload(flow.Payload{ 715 Receipts: []*flow.ExecutionReceiptMeta{ 716 receipt.Meta(), 717 receipt.Meta(), 718 }, 719 Results: []*flow.ExecutionResult{ 720 &receipt.ExecutionResult, 721 }, 722 }) 723 err := s.receiptValidator.ValidatePayload(block3) 724 require.Error(t, err) 725 require.True(t, engine.IsInvalidInputError(err), err) 726 }) 727 } 728 729 // `TestValidateReceiptAfterBootstrap` tests a special case when we try to produce a new block 730 // after genesis with empty payload. 731 func (s *ReceiptValidationSuite) TestValidateReceiptAfterBootstrap() { 732 // assuming signatures are all good 733 s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) 734 735 // G 736 blocks, result0, seal := unittest.ChainFixture(0) 737 s.SealsIndex[blocks[0].ID()] = seal 738 739 for _, b := range blocks { 740 s.Extend(b) 741 } 742 s.PersistedResults[result0.ID()] = result0 743 744 candidate := unittest.BlockWithParentFixture(blocks[0].Header) 745 err := s.receiptValidator.ValidatePayload(candidate) 746 s.Require().NoError(err) 747 }