github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/snapshots/dynamic_bootstrap.go (about)

     1  package snapshots
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/onflow/flow-go/model/flow"
     8  	"github.com/onflow/flow-go/state/protocol"
     9  )
    10  
    11  var ErrSnapshotPhaseMismatch = errors.New("snapshot does not contain a valid sealing segment")
    12  var ErrSnapshotHistoryLimit = errors.New("reached the snapshot history limit")
    13  
    14  // GetDynamicBootstrapSnapshot returns `refSnapshot` if it is valid for use in dynamic bootstrapping.
    15  // Otherwise returns an error. (Effectively this validates that the input snapshot can be used in dynamic bootstrapping.)
    16  // Expected error returns during normal operations:
    17  // * ErrSnapshotPhaseMismatch - snapshot does not contain a valid sealing segment
    18  // All other errors should be treated as exceptions.
    19  func GetDynamicBootstrapSnapshot(state protocol.State, refSnapshot protocol.Snapshot) (protocol.Snapshot, error) {
    20  	return getValidSnapshot(state, refSnapshot, 0, false, 0)
    21  }
    22  
    23  // GetClosestDynamicBootstrapSnapshot will return a valid snapshot for dynamic bootstrapping
    24  // Expected error returns during normal operations:
    25  // If a snapshot does contain an invalid sealing segment query the state
    26  // by height of each block in the segment and return a snapshot at the point
    27  // where the transition happens.
    28  // * ErrSnapshotPhaseMismatch - snapshot does not contain a valid sealing segment
    29  // * ErrSnapshotHistoryLimit - reached the snapshot history limit
    30  // All other errors should be treated as exceptions.
    31  func GetClosestDynamicBootstrapSnapshot(state protocol.State, refSnapshot protocol.Snapshot, snapshotHistoryLimit int) (protocol.Snapshot, error) {
    32  	return getValidSnapshot(state, refSnapshot, 0, true, snapshotHistoryLimit)
    33  }
    34  
    35  // GetCounterAndPhase returns the current epoch counter and phase, at `height`.
    36  // No errors are expected during normal operation.
    37  func GetCounterAndPhase(state protocol.State, height uint64) (uint64, flow.EpochPhase, error) {
    38  	snapshot := state.AtHeight(height)
    39  
    40  	counter, err := snapshot.Epochs().Current().Counter()
    41  	if err != nil {
    42  		return 0, 0, fmt.Errorf("failed to get counter for block (height=%d): %w", height, err)
    43  	}
    44  
    45  	phase, err := snapshot.Phase()
    46  	if err != nil {
    47  		return 0, 0, fmt.Errorf("failed to get phase for block (height=%d): %w", height, err)
    48  	}
    49  
    50  	return counter, phase, nil
    51  }
    52  
    53  func IsEpochOrPhaseDifferent(counter1, counter2 uint64, phase1, phase2 flow.EpochPhase) bool {
    54  	return counter1 != counter2 || phase1 != phase2
    55  }
    56  
    57  // getValidSnapshot will return a valid snapshot that has a sealing segment which
    58  // 1. does not contain any blocks that span an epoch transition
    59  // 2. does not contain any blocks that span an epoch phase transition
    60  // If a snapshot does contain an invalid sealing segment query the state
    61  // by height of each block in the segment and return a snapshot at the point
    62  // where the transition happens.
    63  // Expected error returns during normal operations:
    64  // * ErrSnapshotPhaseMismatch - snapshot does not contain a valid sealing segment
    65  // * ErrSnapshotHistoryLimit - failed to find a valid snapshot after checking `snapshotHistoryLimit` blocks
    66  // All other errors should be treated as exceptions.
    67  func getValidSnapshot(
    68  	state protocol.State,
    69  	snapshot protocol.Snapshot,
    70  	blocksVisited int,
    71  	findNextValidSnapshot bool,
    72  	snapshotHistoryLimit int,
    73  ) (protocol.Snapshot, error) {
    74  	segment, err := snapshot.SealingSegment()
    75  	if err != nil {
    76  		return nil, fmt.Errorf("failed to get sealing segment: %w", err)
    77  	}
    78  
    79  	counterAtHighest, phaseAtHighest, err := GetCounterAndPhase(state, segment.Highest().Header.Height)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("failed to get counter and phase at highest block in the segment: %w", err)
    82  	}
    83  
    84  	counterAtLowest, phaseAtLowest, err := GetCounterAndPhase(state, segment.Sealed().Header.Height)
    85  	if err != nil {
    86  		return nil, fmt.Errorf("failed to get counter and phase at lowest block in the segment: %w", err)
    87  	}
    88  
    89  	// Check if the counters and phase are different this indicates that the sealing segment
    90  	// of the snapshot requested spans either an epoch transition or phase transition.
    91  	if IsEpochOrPhaseDifferent(counterAtHighest, counterAtLowest, phaseAtHighest, phaseAtLowest) {
    92  		if !findNextValidSnapshot {
    93  			return nil, ErrSnapshotPhaseMismatch
    94  		}
    95  
    96  		// Visit each node in strict order of decreasing height starting at head
    97  		// to find the block that straddles the transition boundary.
    98  		for i := len(segment.Blocks) - 1; i >= 0; i-- {
    99  			blocksVisited++
   100  
   101  			// NOTE: Check if we have reached our history limit, in edge cases
   102  			// where the sealing segment is abnormally long we want to short circuit
   103  			// the recursive calls and return an error. The API caller can retry.
   104  			if blocksVisited > snapshotHistoryLimit {
   105  				return nil, fmt.Errorf("%w: (%d)", ErrSnapshotHistoryLimit, snapshotHistoryLimit)
   106  			}
   107  
   108  			counterAtBlock, phaseAtBlock, err := GetCounterAndPhase(state, segment.Blocks[i].Header.Height)
   109  			if err != nil {
   110  				return nil, fmt.Errorf("failed to get epoch counter and phase for snapshot at block %s: %w", segment.Blocks[i].ID(), err)
   111  			}
   112  
   113  			// Check if this block straddles the transition boundary, if it does return the snapshot
   114  			// at that block height.
   115  			if IsEpochOrPhaseDifferent(counterAtHighest, counterAtBlock, phaseAtHighest, phaseAtBlock) {
   116  				return getValidSnapshot(state, state.AtHeight(segment.Blocks[i].Header.Height), blocksVisited, true, snapshotHistoryLimit)
   117  			}
   118  		}
   119  	}
   120  
   121  	return snapshot, nil
   122  }