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  }