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