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 }