github.com/koko1123/flow-go-1@v0.29.6/model/flow/sealing_segment_test.go (about)

     1  package flow_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	"github.com/stretchr/testify/suite"
     9  
    10  	"github.com/koko1123/flow-go-1/model/flow"
    11  	"github.com/koko1123/flow-go-1/utils/unittest"
    12  )
    13  
    14  // SealingSegmentSuite is the test suite for sealing segment construction and validation.
    15  // Notation:
    16  // B1 - block 1
    17  // R1 - execution result+receipt for block 1
    18  // S1 - seal for block 1
    19  // R* - execution result+receipt for some block before the segment
    20  // S* - seal for some block before the segment
    21  type SealingSegmentSuite struct {
    22  	suite.Suite
    23  
    24  	results        map[flow.Identifier]*flow.ExecutionResult
    25  	sealsByBlockID map[flow.Identifier]*flow.Seal
    26  	// bootstrap each test case with a block which is before, and receipt+seal for the block
    27  	priorBlock   *flow.Block
    28  	priorReceipt *flow.ExecutionReceipt
    29  	priorSeal    *flow.Seal
    30  
    31  	builder *flow.SealingSegmentBuilder
    32  }
    33  
    34  func TestSealingSegmentSuite(t *testing.T) {
    35  	suite.Run(t, new(SealingSegmentSuite))
    36  }
    37  
    38  // addResult adds the result to the suite mapping.
    39  func (suite *SealingSegmentSuite) addResult(result *flow.ExecutionResult) {
    40  	suite.results[result.ID()] = result
    41  }
    42  
    43  // addSeal adds the seal as being the latest w.r.t. the block ID.
    44  func (suite *SealingSegmentSuite) addSeal(blockID flow.Identifier, seal *flow.Seal) {
    45  	suite.sealsByBlockID[blockID] = seal
    46  }
    47  
    48  // GetResult gets a result by ID from the map in the suite.
    49  func (suite *SealingSegmentSuite) GetResult(resultID flow.Identifier) (*flow.ExecutionResult, error) {
    50  	result, ok := suite.results[resultID]
    51  	if !ok {
    52  		return nil, fmt.Errorf("not found")
    53  	}
    54  	return result, nil
    55  }
    56  
    57  // GetSealByBlockID gets a seal by block ID from the map in the suite.
    58  func (suite *SealingSegmentSuite) GetSealByBlockID(blockID flow.Identifier) (*flow.Seal, error) {
    59  	seal, ok := suite.sealsByBlockID[blockID]
    60  	if !ok {
    61  		return nil, fmt.Errorf("not found")
    62  	}
    63  	return seal, nil
    64  }
    65  
    66  // SetupTest resets maps and creates a new builder for a new test case.
    67  func (suite *SealingSegmentSuite) SetupTest() {
    68  	suite.results = make(map[flow.Identifier]*flow.ExecutionResult)
    69  	suite.sealsByBlockID = make(map[flow.Identifier]*flow.Seal)
    70  	suite.builder = flow.NewSealingSegmentBuilder(suite.GetResult, suite.GetSealByBlockID)
    71  
    72  	priorBlock := unittest.BlockFixture()
    73  	priorReceipt, priorSeal := unittest.ReceiptAndSealForBlock(&priorBlock)
    74  	suite.results[priorReceipt.ExecutionResult.ID()] = &priorReceipt.ExecutionResult
    75  	suite.priorBlock = &priorBlock
    76  	suite.priorReceipt = priorReceipt
    77  	suite.priorSeal = priorSeal
    78  }
    79  
    80  // FirstBlock returns a first block which contains a seal and receipt referencing
    81  // priorBlock (this is the simplest case for a sealing segment).
    82  func (suite *SealingSegmentSuite) FirstBlock() *flow.Block {
    83  	block := unittest.BlockFixture()
    84  	block.SetPayload(unittest.PayloadFixture(
    85  		unittest.WithSeals(suite.priorSeal),
    86  		unittest.WithReceipts(suite.priorReceipt),
    87  	))
    88  	suite.addSeal(block.ID(), suite.priorSeal)
    89  	return &block
    90  }
    91  
    92  // AddBlocks is a short-hand for adding a sequence of blocks, in order.
    93  // No errors are expected.
    94  func (suite *SealingSegmentSuite) AddBlocks(blocks ...*flow.Block) {
    95  	latestSeal := suite.priorSeal
    96  	for _, block := range blocks {
    97  		// before adding block, ensure its latest seal is indexed in suite
    98  		// convention for this test: seals are ordered by height of the sealed block
    99  		for _, seal := range block.Payload.Seals {
   100  			latestSeal = seal
   101  		}
   102  		suite.addSeal(block.ID(), latestSeal)
   103  		err := suite.builder.AddBlock(block)
   104  		require.NoError(suite.T(), err)
   105  	}
   106  }
   107  
   108  // Tests the case where a receipt in the segment references a result outside it.
   109  // The result should still be included in the sealing segment.
   110  //
   111  // B1(R*,S*) <- B2(R1) <- B4(S1)
   112  func (suite *SealingSegmentSuite) TestBuild_MissingResultFromReceipt() {
   113  
   114  	// B1 contains a receipt (but no result) and seal for a prior block
   115  	block1 := unittest.BlockFixture()
   116  	block1.SetPayload(unittest.PayloadFixture(unittest.WithReceiptsAndNoResults(suite.priorReceipt), unittest.WithSeals(suite.priorSeal)))
   117  
   118  	block2 := unittest.BlockWithParentFixture(block1.Header)
   119  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1)
   120  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   121  
   122  	block3 := unittest.BlockWithParentFixture(block2.Header)
   123  	block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1)))
   124  
   125  	suite.AddBlocks(&block1, block2, block3)
   126  
   127  	segment, err := suite.builder.SealingSegment()
   128  	require.NoError(suite.T(), err)
   129  	require.NoError(suite.T(), segment.Validate())
   130  
   131  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks)
   132  	require.Equal(suite.T(), 1, segment.ExecutionResults.Size())
   133  	require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID())
   134  }
   135  
   136  // Tests the case where the first block contains no seal.
   137  // The latest seal as of the first block should still be included in the segment.
   138  //
   139  // B1 <- B2(R1) <- B3(S1)
   140  func (suite *SealingSegmentSuite) TestBuild_MissingFirstBlockSeal() {
   141  
   142  	// B1 contains an empty payload
   143  	block1 := unittest.BlockFixture()
   144  	// latest seal as of B1 is priorSeal
   145  	suite.sealsByBlockID[block1.ID()] = suite.priorSeal
   146  
   147  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(&block1)
   148  	block2 := unittest.BlockWithParentFixture(block1.Header)
   149  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   150  
   151  	block3 := unittest.BlockWithParentFixture(block2.Header)
   152  	block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1)))
   153  
   154  	suite.AddBlocks(&block1, block2, block3)
   155  
   156  	segment, err := suite.builder.SealingSegment()
   157  	require.NoError(suite.T(), err)
   158  	require.NoError(suite.T(), segment.Validate())
   159  
   160  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{&block1, block2, block3}, segment.Blocks)
   161  	// should contain priorSeal as first seal
   162  	require.Equal(suite.T(), suite.priorSeal, segment.FirstSeal)
   163  	// should contain result referenced by first seal
   164  	require.Equal(suite.T(), 1, segment.ExecutionResults.Size())
   165  	require.Equal(suite.T(), suite.priorReceipt.ExecutionResult.ID(), segment.ExecutionResults[0].ID())
   166  }
   167  
   168  // Tests the case where a seal contained in a segment block payloads references
   169  // a missing result. The result should still be included in the segment.
   170  //
   171  // B1(S*,R*) <- B2(R1,S**) <- B3(S1)
   172  func (suite *SealingSegmentSuite) TestBuild_MissingResultFromPayloadSeal() {
   173  
   174  	block1 := suite.FirstBlock()
   175  
   176  	// create a seal referencing some past receipt/block
   177  	pastResult := unittest.ExecutionResultFixture()
   178  	suite.addResult(pastResult)
   179  	pastSeal := unittest.Seal.Fixture()
   180  	pastSeal.ResultID = pastResult.ID()
   181  
   182  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   183  	block2 := unittest.BlockWithParentFixture(block1.Header)
   184  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1), unittest.WithSeals(pastSeal)))
   185  
   186  	block3 := unittest.BlockWithParentFixture(block2.Header)
   187  	block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1)))
   188  
   189  	suite.AddBlocks(block1, block2, block3)
   190  
   191  	segment, err := suite.builder.SealingSegment()
   192  	require.NoError(suite.T(), err)
   193  	require.NoError(suite.T(), segment.Validate())
   194  
   195  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks)
   196  	require.Equal(suite.T(), 1, segment.ExecutionResults.Size())
   197  	require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID())
   198  }
   199  
   200  // Tests the case where the final block in the segment contains both a seal
   201  // for lowest, and a seal for a block above lowest. This should be considered
   202  // an invalid segment.
   203  //
   204  // B1(S*,R*) <- B2 <- B3(R1,R2) <- B4(S1,S2)
   205  func (suite *SealingSegmentSuite) TestBuild_WrongLatestSeal() {
   206  
   207  	block1 := suite.FirstBlock()
   208  	block2 := unittest.BlockWithParentFixture(block1.Header)
   209  
   210  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   211  	receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2)
   212  
   213  	block3 := unittest.BlockWithParentFixture(block2.Header)
   214  	block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1, receipt2)))
   215  
   216  	block4 := unittest.BlockWithParentFixture(block3.Header)
   217  	block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1, seal2)))
   218  
   219  	suite.AddBlocks(block1, block2, block3, block4)
   220  
   221  	_, err := suite.builder.SealingSegment()
   222  	require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal)
   223  }
   224  
   225  // Tests the case where the final block in the segment seals multiple
   226  // blocks, but the latest sealed is still lowest, hence it is a valid
   227  // sealing segment.
   228  //
   229  // B1(S*,R*) <- B2(R1) <- B3(S**,S1)
   230  func (suite *SealingSegmentSuite) TestBuild_MultipleFinalBlockSeals() {
   231  
   232  	block1 := suite.FirstBlock()
   233  
   234  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   235  	// create a seal referencing some past receipt/block
   236  	pastResult := unittest.ExecutionResultFixture()
   237  	suite.addResult(pastResult)
   238  	pastSeal := unittest.Seal.Fixture()
   239  	pastSeal.ResultID = pastResult.ID()
   240  
   241  	block2 := unittest.BlockWithParentFixture(block1.Header)
   242  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   243  
   244  	block3 := unittest.BlockWithParentFixture(block2.Header)
   245  	block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(pastSeal, seal1)))
   246  
   247  	suite.AddBlocks(block1, block2, block3)
   248  
   249  	segment, err := suite.builder.SealingSegment()
   250  	require.NoError(suite.T(), err)
   251  
   252  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3}, segment.Blocks)
   253  	require.Equal(suite.T(), 1, segment.ExecutionResults.Size())
   254  	require.Equal(suite.T(), pastResult.ID(), segment.ExecutionResults[0].ID())
   255  	require.NoError(suite.T(), segment.Validate())
   256  }
   257  
   258  // TestBuild_RootSegment tests we can build a valid root sealing segment.
   259  func (suite *SealingSegmentSuite) TestBuild_RootSegment() {
   260  
   261  	root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles()))
   262  	suite.sealsByBlockID[root.ID()] = seal
   263  	suite.addResult(result)
   264  	err := suite.builder.AddBlock(root)
   265  	require.NoError(suite.T(), err)
   266  
   267  	segment, err := suite.builder.SealingSegment()
   268  	require.NoError(suite.T(), err)
   269  	require.NoError(suite.T(), segment.Validate())
   270  
   271  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{root}, segment.Blocks)
   272  	require.Equal(suite.T(), segment.Highest().ID(), root.ID())
   273  	require.Equal(suite.T(), segment.Lowest().ID(), root.ID())
   274  }
   275  
   276  // TestBuild_RootSegmentWrongView tests that we return ErrSegmentInvalidRootView for
   277  // a single-block sealing segment with a block view not equal to 0.
   278  func (suite *SealingSegmentSuite) TestBuild_RootSegmentWrongView() {
   279  
   280  	root, result, seal := unittest.BootstrapFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles()))
   281  	root.Header.View = 10 // invalid root block view
   282  	suite.sealsByBlockID[root.ID()] = seal
   283  	suite.addResult(result)
   284  	err := suite.builder.AddBlock(root)
   285  	require.NoError(suite.T(), err)
   286  
   287  	_, err = suite.builder.SealingSegment()
   288  	require.Error(suite.T(), err)
   289  }
   290  
   291  // Test the case when the highest block in the segment does not contain seals but
   292  // the first ancestor of the highest block does contain a seal for lowest,
   293  // we return a valid sealing segment.
   294  //
   295  // B1(S*) <- B2(R1) <- B3(S1) <- B4
   296  func (suite *SealingSegmentSuite) TestBuild_HighestContainsNoSeals() {
   297  	block1 := suite.FirstBlock()
   298  
   299  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   300  	block2 := unittest.BlockWithParentFixture(block1.Header)
   301  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   302  
   303  	block3 := unittest.BlockWithParentFixture(block2.Header)
   304  	block3.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1)))
   305  
   306  	block4 := unittest.BlockWithParentFixture(block3.Header)
   307  
   308  	suite.AddBlocks(block1, block2, block3, block4)
   309  
   310  	segment, err := suite.builder.SealingSegment()
   311  	require.NoError(suite.T(), err)
   312  	require.NoError(suite.T(), segment.Validate())
   313  
   314  	unittest.AssertEqualBlocksLenAndOrder(suite.T(), []*flow.Block{block1, block2, block3, block4}, segment.Blocks)
   315  }
   316  
   317  // Test that we should return ErrSegmentMissingSeal if highest block contains
   318  // seals but does not contain seal for lowest, when sealing segment is built.
   319  //
   320  // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2)
   321  func (suite *SealingSegmentSuite) TestBuild_HighestContainsWrongSeal() {
   322  	block1 := suite.FirstBlock()
   323  
   324  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   325  	block2 := unittest.BlockWithParentFixture(block1.Header)
   326  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   327  
   328  	receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2)
   329  	block3 := unittest.BlockWithParentFixture(block2.Header)
   330  	block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1)))
   331  
   332  	// highest block contains wrong seal - invalid
   333  	block4 := unittest.BlockWithParentFixture(block3.Header)
   334  	block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal2)))
   335  
   336  	suite.AddBlocks(block1, block2, block3, block4)
   337  
   338  	_, err := suite.builder.SealingSegment()
   339  	require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal)
   340  }
   341  
   342  // Test that we should return ErrSegmentMissingSeal if highest block contains
   343  // no seals and first ancestor with seals does not seal lowest, when sealing
   344  // segment is built
   345  //
   346  // B1(S*) <- B2(R1) <- B3(S1,R2) <- B4(S2) <- B5
   347  func (suite *SealingSegmentSuite) TestBuild_HighestAncestorContainsWrongSeal() {
   348  	block1 := suite.FirstBlock()
   349  
   350  	receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   351  	block2 := unittest.BlockWithParentFixture(block1.Header)
   352  	block2.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt1)))
   353  
   354  	receipt2, seal2 := unittest.ReceiptAndSealForBlock(block2)
   355  	block3 := unittest.BlockWithParentFixture(block2.Header)
   356  	block3.SetPayload(unittest.PayloadFixture(unittest.WithReceipts(receipt2), unittest.WithSeals(seal1)))
   357  
   358  	// ancestor of highest block contains wrong seal - invalid
   359  	block4 := unittest.BlockWithParentFixture(block3.Header)
   360  	block4.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal2)))
   361  
   362  	block5 := unittest.BlockWithParentFixture(block4.Header)
   363  
   364  	suite.AddBlocks(block1, block2, block3, block4, block5)
   365  
   366  	_, err := suite.builder.SealingSegment()
   367  	require.ErrorIs(suite.T(), err, flow.ErrSegmentMissingSeal)
   368  }
   369  
   370  // Test that we should return ErrSegmentBlocksWrongLen if sealing segment is
   371  // built with no blocks.
   372  func (suite *SealingSegmentSuite) TestBuild_NoBlocks() {
   373  	builder := flow.NewSealingSegmentBuilder(nil, nil)
   374  	_, err := builder.SealingSegment()
   375  	require.ErrorIs(suite.T(), err, flow.ErrSegmentBlocksWrongLen)
   376  }
   377  
   378  // should return ErrSegmentInvalidBlockHeight if block has invalid height
   379  func (suite *SealingSegmentSuite) TestAddBlock_InvalidHeight() {
   380  
   381  	block1 := suite.FirstBlock()
   382  	// block 2 has an invalid height
   383  	block2 := unittest.BlockFixture()
   384  	block2.Header.Height = block1.Header.Height + 2
   385  
   386  	err := suite.builder.AddBlock(block1)
   387  	require.NoError(suite.T(), err)
   388  
   389  	err = suite.builder.AddBlock(&block2)
   390  	require.ErrorIs(suite.T(), err, flow.ErrSegmentInvalidBlockHeight)
   391  }
   392  
   393  // TestAddBlock_StorageError tests that errors in the resource getters bubble up.
   394  func TestAddBlock_StorageError(t *testing.T) {
   395  
   396  	t.Run("missing result", func(t *testing.T) {
   397  		// create a receipt to include in the first block, whose result is not in storage
   398  		missingReceipt := unittest.ExecutionReceiptFixture()
   399  		block1 := unittest.BlockFixture()
   400  		sealLookup := func(flow.Identifier) (*flow.Seal, error) { return unittest.Seal.Fixture(), nil }
   401  		resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return nil, fmt.Errorf("not found") }
   402  		builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup)
   403  
   404  		block1.SetPayload(unittest.PayloadFixture(
   405  			unittest.WithReceiptsAndNoResults(missingReceipt),
   406  			unittest.WithSeals(unittest.Seal.Fixture(unittest.Seal.WithResult(&missingReceipt.ExecutionResult))),
   407  		))
   408  
   409  		err := builder.AddBlock(&block1)
   410  		require.ErrorIs(t, err, flow.ErrSegmentResultLookup)
   411  	})
   412  
   413  	// create a first block which contains no seal, and the seal isn't in storage
   414  	t.Run("missing seal", func(t *testing.T) {
   415  		resultLookup := func(flow.Identifier) (*flow.ExecutionResult, error) { return unittest.ExecutionResultFixture(), nil }
   416  		sealLookup := func(flow.Identifier) (*flow.Seal, error) { return nil, fmt.Errorf("not found") }
   417  		block1 := unittest.BlockFixture()
   418  		block1.SetPayload(flow.EmptyPayload())
   419  		builder := flow.NewSealingSegmentBuilder(resultLookup, sealLookup)
   420  
   421  		err := builder.AddBlock(&block1)
   422  		require.ErrorIs(t, err, flow.ErrSegmentSealLookup)
   423  	})
   424  }