github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/validation/seal_validator_test.go (about)

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