github.com/onflow/flow-go@v0.33.17/model/flow/sealing_segment.go (about)

     1  package flow
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"golang.org/x/exp/slices"
     7  )
     8  
     9  // SealingSegment is a continuous segment of recently finalized blocks that contains enough history
    10  // for a new node to execute its business logic normally. It is part of the data need to initialize
    11  // a new node to join the network.
    12  // DETAILED SPECIFICATION: ./sealing_segment.md
    13  //
    14  //	├═══════════┤  ├───────────────────────┤
    15  //	 ExtraBlocks   ^          Blocks       ^
    16  //	               B                      head
    17  //
    18  // Lets denote the highest block in the sealing segment as `head`. Per convention, `head` must be a
    19  // finalized block. Consider the chain of blocks leading up to `head` (included). The highest block
    20  // in chain leading up to `head` that is sealed, we denote as B.
    21  // In other words, head is the last finalized block, and B is the last sealed block,
    22  // block at height (B.Height + 1) is not sealed.
    23  type SealingSegment struct {
    24  	// Blocks contain the chain `B <- ... <- Head` in ascending height order.
    25  	// Formally, Blocks contains exactly (not more!) the history to satisfy condition
    26  	// (see sealing_segment.md for details):
    27  	//   (i) The highest sealed block as of `head` needs to be included in the sealing segment.
    28  	//       This is relevant if `head` does not contain any seals.
    29  	Blocks []*Block
    30  
    31  	// ExtraBlocks [optional] holds ancestors of `Blocks` in ascending height order.
    32  	// Formally, ExtraBlocks contains at least the additional history to satisfy conditions
    33  	// (see sealing_segment.md for details):
    34  	//  (ii) All blocks that are sealed by `head`. This is relevant if `head` contains _multiple_ seals.
    35  	// (iii) The sealing segment holds the history of all non-expired collection guarantees, i.e.
    36  	//       limitHeight := max(blockSealedAtHead.Height - flow.ExtraBlocksInRootSealingSegment, SporkRootBlockHeight)
    37  	//       where blockSealedAtHead is the block sealed by `head` block.
    38  	// (Potentially longer history is permitted)
    39  	ExtraBlocks []*Block
    40  
    41  	// ExecutionResults contain any results which are referenced by receipts
    42  	// or seals in the sealing segment, but not included in any segment block
    43  	// payloads.
    44  	//
    45  	// Due to decoupling of execution receipts from execution results,
    46  	// it's possible that blocks from the sealing segment will be referring to
    47  	// execution results incorporated in blocks that aren't part of the segment.
    48  	ExecutionResults ExecutionResultList
    49  
    50  	// LatestSeals is a mapping from block ID to the ID of the latest seal
    51  	// incorporated as of that block. Note: we store the seals' IDs here
    52  	// (instead of the full seals), because the seals for all blocks, except for
    53  	// the lowest one, are contained in the blocks of the sealing segment.
    54  	LatestSeals map[Identifier]Identifier
    55  
    56  	// FirstSeal contains the latest seal as of the first block in the segment.
    57  	// Per convention, this field holds a seal that was included _prior_ to the
    58  	// first block of the sealing segment. If the first block in the segment
    59  	// contains a seal, then this field is `nil`.
    60  	// This information is needed for the `Commit` method of protocol snapshot
    61  	// to return the sealed state, when the first block contains no seal.
    62  	FirstSeal *Seal
    63  }
    64  
    65  // Highest is the highest block in the sealing segment and the reference block from snapshot that was
    66  // used to produce this sealing segment.
    67  func (segment *SealingSegment) Highest() *Block {
    68  	return segment.Blocks[len(segment.Blocks)-1]
    69  }
    70  
    71  // Finalized returns the last finalized block, which is an alias of Highest
    72  func (segment *SealingSegment) Finalized() *Block {
    73  	return segment.Highest()
    74  }
    75  
    76  // Sealed returns the most recently sealed block based on head of sealing segment(highest block).
    77  func (segment *SealingSegment) Sealed() *Block {
    78  	return segment.Blocks[0]
    79  }
    80  
    81  // AllBlocks returns all blocks within the sealing segment, including extra blocks, in ascending height order.
    82  func (segment *SealingSegment) AllBlocks() []*Block {
    83  	return append(segment.ExtraBlocks, segment.Blocks...)
    84  }
    85  
    86  // FinalizedSeal returns the seal that seals the lowest block.
    87  // Per specification, this seal must be included in a SealingSegment.
    88  // The SealingSegment must be validated.
    89  // No errors are expected during normal operation.
    90  func (segment *SealingSegment) FinalizedSeal() (*Seal, error) {
    91  	if isRootSegment(segment.LatestSeals) {
    92  		return segment.FirstSeal, nil
    93  	}
    94  
    95  	seal, err := findLatestSealForLowestBlock(segment.Blocks, segment.LatestSeals)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	// sanity check
   101  	if seal.BlockID != segment.Sealed().ID() {
   102  		return nil, fmt.Errorf("finalized seal should seal the lowest block %v, but actually is to seal %v",
   103  			segment.Sealed().ID(), seal.BlockID)
   104  	}
   105  	return seal, nil
   106  }
   107  
   108  // Validate validates the sealing segment structure and returns an error if
   109  // the segment isn't valid. This is done by re-building the segment from scratch,
   110  // re-using the validation logic already present in the SealingSegmentBuilder.
   111  // The node logic requires a valid sealing segment to bootstrap.
   112  // No errors are expected during normal operation.
   113  func (segment *SealingSegment) Validate() error {
   114  
   115  	// populate lookup of seals and results in the segment to satisfy builder
   116  	seals := make(map[Identifier]*Seal)
   117  	results := segment.ExecutionResults.Lookup()
   118  
   119  	if segment.FirstSeal != nil {
   120  		seals[segment.FirstSeal.ID()] = segment.FirstSeal
   121  	}
   122  	for _, block := range segment.Blocks {
   123  		for _, result := range block.Payload.Results {
   124  			results[result.ID()] = result
   125  		}
   126  		for _, seal := range block.Payload.Seals {
   127  			seals[seal.ID()] = seal
   128  		}
   129  	}
   130  
   131  	getResult := func(resultID Identifier) (*ExecutionResult, error) {
   132  		result, ok := results[resultID]
   133  		if !ok {
   134  			return nil, fmt.Errorf("result (id=%x) not found in segment", resultID)
   135  		}
   136  		return result, nil
   137  	}
   138  	getSeal := func(blockID Identifier) (*Seal, error) {
   139  		sealID, ok := segment.LatestSeals[blockID]
   140  		if !ok {
   141  			return nil, fmt.Errorf("seal for block (id=%x) not found in segment", blockID)
   142  		}
   143  		seal, ok := seals[sealID]
   144  		if !ok {
   145  			return nil, fmt.Errorf("seal (id=%x) not found in segment at block %x", sealID, blockID)
   146  		}
   147  		return seal, nil
   148  	}
   149  
   150  	builder := NewSealingSegmentBuilder(getResult, getSeal)
   151  	for _, block := range segment.Blocks {
   152  		err := builder.AddBlock(block)
   153  		if err != nil {
   154  			return fmt.Errorf("invalid segment: %w", err)
   155  		}
   156  	}
   157  	// extra blocks should be added in reverse order, starting from the highest one since they are sorted
   158  	// in ascending order.
   159  	for i := len(segment.ExtraBlocks) - 1; i >= 0; i-- {
   160  		block := segment.ExtraBlocks[i]
   161  		err := builder.AddExtraBlock(block)
   162  		if err != nil {
   163  			return fmt.Errorf("invalid segment: %w", err)
   164  		}
   165  	}
   166  	_, err := builder.SealingSegment()
   167  	if err != nil {
   168  		return fmt.Errorf("invalid segment: %w", err)
   169  	}
   170  	return nil
   171  }
   172  
   173  var (
   174  	ErrSegmentMissingSeal        = fmt.Errorf("sealing segment failed sanity check: missing seal referenced by segment")
   175  	ErrSegmentBlocksWrongLen     = fmt.Errorf("sealing segment failed sanity check: non-root sealing segment must have at least 2 blocks")
   176  	ErrSegmentInvalidBlockHeight = fmt.Errorf("sealing segment failed sanity check: blocks must be in ascending order")
   177  	ErrSegmentResultLookup       = fmt.Errorf("failed to lookup execution result")
   178  	ErrSegmentSealLookup         = fmt.Errorf("failed to lookup seal")
   179  )
   180  
   181  // GetResultFunc is a getter function for results by ID.
   182  // No errors are expected during normal operation.
   183  type GetResultFunc func(resultID Identifier) (*ExecutionResult, error)
   184  
   185  // GetSealByBlockIDFunc is a getter function for seals by block ID, returning
   186  // the latest seals incorporated as of the given block.
   187  // No errors are expected during normal operation.
   188  type GetSealByBlockIDFunc func(blockID Identifier) (*Seal, error)
   189  
   190  // SealingSegmentBuilder is a utility for incrementally building a sealing segment.
   191  type SealingSegmentBuilder struct {
   192  	// access to storage to read referenced by not included resources
   193  	resultLookup        GetResultFunc
   194  	sealByBlockIDLookup GetSealByBlockIDFunc
   195  	// keep track of resources included in payloads
   196  	includedResults map[Identifier]struct{}
   197  	// resources to include in the sealing segment
   198  	blocks      []*Block
   199  	results     []*ExecutionResult
   200  	latestSeals map[Identifier]Identifier
   201  	firstSeal   *Seal
   202  	// extraBlocks included in sealing segment, must connect to the lowest block of segment
   203  	// stored in descending order for simpler population logic
   204  	extraBlocks []*Block
   205  }
   206  
   207  // AddBlock appends a block to the sealing segment under construction.
   208  // No errors are expected during normal operation.
   209  func (builder *SealingSegmentBuilder) AddBlock(block *Block) error {
   210  	// sanity check: all blocks have to be added before adding extra blocks
   211  	if len(builder.extraBlocks) > 0 {
   212  		return fmt.Errorf("cannot add sealing segment block after extra block is added")
   213  	}
   214  
   215  	// sanity check: block should be 1 height higher than current highest
   216  	if !builder.isValidHeight(block) {
   217  		return fmt.Errorf("invalid block height (%d): %w", block.Header.Height, ErrSegmentInvalidBlockHeight)
   218  	}
   219  	blockID := block.ID()
   220  
   221  	// a block might contain receipts or seals that refer to results that are included in blocks
   222  	// whose height is below the first block of the segment.
   223  	// In order to include those missing results into the segment, we construct a list of those
   224  	// missing result IDs referenced by this block
   225  	missingResultIDs := make(map[Identifier]struct{})
   226  
   227  	// for the first (lowest) block, if it contains no seal, store the latest
   228  	// seal incorporated prior to the first block
   229  	if len(builder.blocks) == 0 {
   230  		if len(block.Payload.Seals) == 0 {
   231  			seal, err := builder.sealByBlockIDLookup(blockID)
   232  			if err != nil {
   233  				return fmt.Errorf("%w: %v", ErrSegmentSealLookup, err)
   234  			}
   235  			builder.firstSeal = seal
   236  			// add first seal result ID here, since it isn't in payload
   237  			missingResultIDs[seal.ResultID] = struct{}{}
   238  		}
   239  	}
   240  
   241  	// index the latest seal for this block
   242  	latestSeal, err := builder.sealByBlockIDLookup(blockID)
   243  	if err != nil {
   244  		return fmt.Errorf("%w: %v", ErrSegmentSealLookup, err)
   245  	}
   246  	builder.latestSeals[blockID] = latestSeal.ID()
   247  
   248  	// cache included results and seals
   249  	// they could be referenced in a future block in the segment
   250  	for _, result := range block.Payload.Results {
   251  		builder.includedResults[result.ID()] = struct{}{}
   252  	}
   253  
   254  	for _, receipt := range block.Payload.Receipts {
   255  		if _, ok := builder.includedResults[receipt.ResultID]; !ok {
   256  			missingResultIDs[receipt.ResultID] = struct{}{}
   257  		}
   258  	}
   259  	for _, seal := range block.Payload.Seals {
   260  		if _, ok := builder.includedResults[seal.ResultID]; !ok {
   261  			missingResultIDs[seal.ResultID] = struct{}{}
   262  		}
   263  	}
   264  
   265  	// add the missing results
   266  	for resultID := range missingResultIDs {
   267  		result, err := builder.resultLookup(resultID)
   268  
   269  		if err != nil {
   270  			return fmt.Errorf("%w: (%x) %v", ErrSegmentResultLookup, resultID, err)
   271  		}
   272  		builder.addExecutionResult(result)
   273  		builder.includedResults[resultID] = struct{}{}
   274  	}
   275  
   276  	builder.blocks = append(builder.blocks, block)
   277  	return nil
   278  }
   279  
   280  // AddExtraBlock appends an extra block to sealing segment under construction.
   281  // Extra blocks needs to be added in descending order and the first block must connect to the lowest block
   282  // of sealing segment, this way they form a continuous chain.
   283  // No errors are expected during normal operation.
   284  func (builder *SealingSegmentBuilder) AddExtraBlock(block *Block) error {
   285  	if len(builder.extraBlocks) == 0 {
   286  		if len(builder.blocks) == 0 {
   287  			return fmt.Errorf("cannot add extra blocks before adding lowest sealing segment block")
   288  		}
   289  		// first extra block has to match the lowest block of sealing segment
   290  		if (block.Header.Height + 1) != builder.lowest().Header.Height {
   291  			return fmt.Errorf("invalid extra block height (%d), doesn't connect to sealing segment: %w", block.Header.Height, ErrSegmentInvalidBlockHeight)
   292  		}
   293  	} else if (block.Header.Height + 1) != builder.extraBlocks[len(builder.extraBlocks)-1].Header.Height {
   294  		return fmt.Errorf("invalid extra block height (%d), doesn't connect to last extra block: %w", block.Header.Height, ErrSegmentInvalidBlockHeight)
   295  	}
   296  
   297  	builder.extraBlocks = append(builder.extraBlocks, block)
   298  	return nil
   299  }
   300  
   301  // AddExecutionResult adds result to executionResults
   302  func (builder *SealingSegmentBuilder) addExecutionResult(result *ExecutionResult) {
   303  	builder.results = append(builder.results, result)
   304  }
   305  
   306  // SealingSegment completes building the sealing segment, validating the segment
   307  // constructed so far, and returning it as a SealingSegment if it is valid.
   308  //
   309  // All errors indicate the SealingSegmentBuilder internal state does not represent
   310  // a valid sealing segment.
   311  // No errors are expected during normal operation.
   312  func (builder *SealingSegmentBuilder) SealingSegment() (*SealingSegment, error) {
   313  	if err := builder.validateSegment(); err != nil {
   314  		return nil, fmt.Errorf("failed to validate sealing segment: %w", err)
   315  	}
   316  
   317  	// SealingSegment must store extra blocks in ascending order, builder stores them in descending.
   318  	// Apply a sort to reverse the slice and use correct ordering.
   319  	slices.SortFunc(builder.extraBlocks, func(lhs, rhs *Block) int {
   320  		return int(lhs.Header.Height) - int(rhs.Header.Height)
   321  	})
   322  
   323  	return &SealingSegment{
   324  		Blocks:           builder.blocks,
   325  		ExtraBlocks:      builder.extraBlocks,
   326  		ExecutionResults: builder.results,
   327  		LatestSeals:      builder.latestSeals,
   328  		FirstSeal:        builder.firstSeal,
   329  	}, nil
   330  }
   331  
   332  // isValidHeight returns true iff block is exactly 1 height higher than the current highest block in the segment.
   333  func (builder *SealingSegmentBuilder) isValidHeight(block *Block) bool {
   334  	if builder.highest() == nil {
   335  		return true
   336  	}
   337  
   338  	return block.Header.Height == builder.highest().Header.Height+1
   339  }
   340  
   341  // validateRootSegment will check that the current builder state represents a valid
   342  // root sealing segment. In particular:
   343  // * the root block must be the first block (least height) in the segment
   344  // * no blocks in the segment may contain any seals (by the minimality requirement)
   345  //
   346  // All errors indicate an invalid root segment, and either a bug in SealingSegmentBuilder
   347  // or a corrupted underlying protocol state.
   348  // No errors are expected during normal operation.
   349  func (builder *SealingSegmentBuilder) validateRootSegment() error {
   350  	if len(builder.blocks) == 0 {
   351  		return fmt.Errorf("root segment must have at least 1 block")
   352  	}
   353  	if len(builder.extraBlocks) > 0 {
   354  		return fmt.Errorf("root segment cannot have extra blocks")
   355  	}
   356  	if builder.lowest().Header.View != 0 {
   357  		return fmt.Errorf("root block has unexpected view (%d != 0)", builder.lowest().Header.View)
   358  	}
   359  	if len(builder.results) != 1 {
   360  		return fmt.Errorf("expected %d results, got %d", 1, len(builder.results))
   361  	}
   362  	if builder.firstSeal == nil {
   363  		return fmt.Errorf("firstSeal must not be nil for root segment")
   364  	}
   365  	if builder.results[0].BlockID != builder.lowest().ID() {
   366  		return fmt.Errorf("result (block_id=%x) is not for root block (id=%x)", builder.results[0].BlockID, builder.lowest().ID())
   367  	}
   368  	if builder.results[0].ID() != builder.firstSeal.ResultID {
   369  		return fmt.Errorf("firstSeal (result_id=%x) is not for root result (id=%x)", builder.firstSeal.ResultID, builder.results[0].ID())
   370  	}
   371  	if builder.results[0].BlockID != builder.firstSeal.BlockID {
   372  		return fmt.Errorf("root seal (block_id=%x) references different block than root result (block_id=%x)", builder.firstSeal.BlockID, builder.results[0].BlockID)
   373  	}
   374  	for _, block := range builder.blocks {
   375  		if len(block.Payload.Seals) > 0 {
   376  			return fmt.Errorf("root segment cannot contain blocks with seals (minimality requirement) - block (height=%d,id=%x) has %d seals",
   377  				block.Header.Height, block.ID(), len(block.Payload.Seals))
   378  		}
   379  	}
   380  	return nil
   381  }
   382  
   383  // validateSegment will validate if builder satisfies conditions for a valid sealing segment.
   384  // No errors are expected during normal operation.
   385  func (builder *SealingSegmentBuilder) validateSegment() error {
   386  	// sealing cannot be empty
   387  	if len(builder.blocks) == 0 {
   388  		return fmt.Errorf("expect at least 2 blocks in a sealing segment or 1 block in the case of root segments, but got an empty sealing segment: %w", ErrSegmentBlocksWrongLen)
   389  	}
   390  
   391  	if len(builder.extraBlocks) > 0 {
   392  		if builder.extraBlocks[0].Header.Height+1 != builder.lowest().Header.Height {
   393  			return fmt.Errorf("extra blocks don't connect to lowest block in segment")
   394  		}
   395  	}
   396  
   397  	// if root sealing segment, use different validation
   398  	if isRootSegment(builder.latestSeals) {
   399  		err := builder.validateRootSegment()
   400  		if err != nil {
   401  			return fmt.Errorf("invalid root segment: %w", err)
   402  		}
   403  		return nil
   404  	}
   405  
   406  	// validate the latest seal is for the lowest block
   407  	_, err := findLatestSealForLowestBlock(builder.blocks, builder.latestSeals)
   408  	if err != nil {
   409  		return fmt.Errorf("sealing segment missing seal (lowest block id: %x) (highest block id: %x) %v: %w", builder.lowest().ID(), builder.highest().ID(), err, ErrSegmentMissingSeal)
   410  	}
   411  
   412  	return nil
   413  }
   414  
   415  // highest returns the highest block in segment.
   416  func (builder *SealingSegmentBuilder) highest() *Block {
   417  	if len(builder.blocks) == 0 {
   418  		return nil
   419  	}
   420  
   421  	return builder.blocks[len(builder.blocks)-1]
   422  }
   423  
   424  // lowest returns the lowest block in segment.
   425  func (builder *SealingSegmentBuilder) lowest() *Block {
   426  	return builder.blocks[0]
   427  }
   428  
   429  // NewSealingSegmentBuilder returns *SealingSegmentBuilder
   430  func NewSealingSegmentBuilder(resultLookup GetResultFunc, sealLookup GetSealByBlockIDFunc) *SealingSegmentBuilder {
   431  	return &SealingSegmentBuilder{
   432  		resultLookup:        resultLookup,
   433  		sealByBlockIDLookup: sealLookup,
   434  		includedResults:     make(map[Identifier]struct{}),
   435  		latestSeals:         make(map[Identifier]Identifier),
   436  		blocks:              make([]*Block, 0),
   437  		extraBlocks:         make([]*Block, 0),
   438  		results:             make(ExecutionResultList, 0),
   439  	}
   440  }
   441  
   442  // findLatestSealForLowestBlock finds the seal for the lowest block.
   443  // As a sanity check, the method confirms that this seal is the latest seal as of the highest block.
   444  // In other words, this function checks that the sealing segment's history is minimal.
   445  // Inputs:
   446  //   - `blocks` is the continuous sequence of blocks that form the sealing segment
   447  //   - `latestSeals` holds for each block the identifier of the latest seal included in the fork as of this block
   448  //
   449  // CAUTION: this method is only applicable for non-root sealing segments, where at least one block
   450  // was sealed after the root block.
   451  // Examples:
   452  //
   453  //	A <- B <- C <- D(seal_A)               ==> valid
   454  //	A <- B <- C <- D(seal_A) <- E()        ==> valid
   455  //	A <- B <- C <- D(seal_A,seal_B)        ==> invalid, because latest seal is B, but lowest block is A
   456  //	A <- B <- C <- D(seal_X,seal_A)        ==> valid, because it's OK for block X to be unknown
   457  //	A <- B <- C <- D(seal_A) <- E(seal_B)  ==> invalid, because latest seal is B, but lowest block is A
   458  //	A(seal_A)                              ==> invalid, because this is impossible for non-root sealing segments
   459  //
   460  // The node logic requires a valid sealing segment to bootstrap. There are no
   461  // errors expected during normal operations.
   462  func findLatestSealForLowestBlock(blocks []*Block, latestSeals map[Identifier]Identifier) (*Seal, error) {
   463  	lowestBlockID := blocks[0].ID()
   464  	highestBlockID := blocks[len(blocks)-1].ID()
   465  
   466  	// get the ID of the latest seal for highest block
   467  	latestSealID := latestSeals[highestBlockID]
   468  
   469  	// find the seal within the block payloads
   470  	for i := len(blocks) - 1; i >= 0; i-- {
   471  		block := blocks[i]
   472  		// look for latestSealID in the payload
   473  		for _, seal := range block.Payload.Seals {
   474  			// if we found the latest seal, confirm it seals lowest
   475  			if seal.ID() == latestSealID {
   476  				if seal.BlockID == lowestBlockID {
   477  					return seal, nil
   478  				}
   479  				return nil, fmt.Errorf("invalid segment: segment contain seal for block %v, but doesn't match lowest block %v",
   480  					seal.BlockID, lowestBlockID)
   481  			}
   482  		}
   483  
   484  		// the latest seal must be found in a block that has a seal when traversing blocks
   485  		// backwards from higher height to lower height.
   486  		// otherwise, the sealing segment is invalid
   487  		if len(block.Payload.Seals) > 0 {
   488  			return nil, fmt.Errorf("invalid segment: segment's last block contain seal %v, but doesn't match latestSealID: %v",
   489  				block.Payload.Seals[0].ID(), latestSealID)
   490  		}
   491  	}
   492  
   493  	return nil, fmt.Errorf("invalid segment: seal %v not found", latestSealID)
   494  }
   495  
   496  // isRootSegment returns true if the input latestSeals map represents a root segment.
   497  // The implementation makes use of the fact that root sealing segments uniquely
   498  // have the same latest seal, for all blocks in the segment.
   499  func isRootSegment(latestSeals map[Identifier]Identifier) bool {
   500  	var rootSealID Identifier
   501  	// set root seal ID to the latest seal value for any block in the segment
   502  	for _, sealID := range latestSeals {
   503  		rootSealID = sealID
   504  		break
   505  	}
   506  	// then, verify all other blocks have the same latest seal
   507  	for _, sealID := range latestSeals {
   508  		if sealID != rootSealID {
   509  			return false
   510  		}
   511  	}
   512  	return true
   513  }