github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/model/flow/sealing_segment.go (about)

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