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