github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/common/checkpoint.go (about) 1 package common 2 3 import ( 4 "fmt" 5 "path/filepath" 6 7 "github.com/rs/zerolog" 8 "github.com/rs/zerolog/log" 9 10 "github.com/onflow/flow-go/ledger" 11 "github.com/onflow/flow-go/ledger/complete/wal" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/state/protocol" 14 "github.com/onflow/flow-go/storage" 15 ) 16 17 // FindHeightsByCheckpoints finds the sealed height that produces the state commitment included in the checkpoint file. 18 func FindHeightsByCheckpoints( 19 logger zerolog.Logger, 20 headers storage.Headers, 21 seals storage.Seals, 22 checkpointFilePath string, 23 blocksToSkip uint, 24 startHeight uint64, 25 endHeight uint64, 26 ) ( 27 uint64, // sealed height that produces the state commitment included in the checkpoint file 28 flow.StateCommitment, // the state commitment that matches the sealed height 29 uint64, // the finalized height that seals the sealed height 30 error, 31 ) { 32 33 // find all trie root hashes in the checkpoint file 34 dir, fileName := filepath.Split(checkpointFilePath) 35 hashes, err := wal.ReadTriesRootHash(logger, dir, fileName) 36 if err != nil { 37 return 0, flow.DummyStateCommitment, 0, 38 fmt.Errorf("could not read trie root hashes from checkpoint file %v: %w", 39 checkpointFilePath, err) 40 } 41 42 // convert all trie root hashes to state commitments 43 commitments := hashesToCommits(hashes) 44 45 commitMap := make(map[flow.StateCommitment]struct{}, len(commitments)) 46 for _, commit := range commitments { 47 commitMap[commit] = struct{}{} 48 } 49 50 // iterate backwards from the end height to the start height 51 // to find the block that produces a state commitment in the given list 52 // It is safe to skip blocks in this linear search because we expect `stateCommitments` to hold commits 53 // for a contiguous range of blocks (for correct operation we assume `blocksToSkip` is smaller than this range). 54 // end height must be a sealed block 55 step := blocksToSkip + 1 56 for height := endHeight; height >= startHeight; height -= uint64(step) { 57 finalizedID, err := headers.BlockIDByHeight(height) 58 if err != nil { 59 return 0, flow.DummyStateCommitment, 0, 60 fmt.Errorf("could not find block by height %v: %w", height, err) 61 } 62 63 // since height is a sealed block height, then we must be able to find the seal for this block 64 finalizedSeal, err := seals.HighestInFork(finalizedID) 65 if err != nil { 66 return 0, flow.DummyStateCommitment, 0, 67 fmt.Errorf("could not find seal for block %v at height %v: %w", finalizedID, height, err) 68 } 69 70 commit := finalizedSeal.FinalState 71 72 _, ok := commitMap[commit] 73 if ok { 74 sealedBlock, err := headers.ByBlockID(finalizedSeal.BlockID) 75 if err != nil { 76 return 0, flow.DummyStateCommitment, 0, 77 fmt.Errorf("could not find block by ID %v: %w", finalizedSeal.BlockID, err) 78 } 79 80 log.Info().Msgf("successfully found block %v (%v) that seals block %v (%v) for commit %x in checkpoint file %v", 81 height, finalizedID, 82 sealedBlock.Height, finalizedSeal.BlockID, 83 commit, checkpointFilePath) 84 85 return sealedBlock.Height, commit, height, nil 86 } 87 88 if height < uint64(step) { 89 break 90 } 91 } 92 93 return 0, flow.DummyStateCommitment, 0, 94 fmt.Errorf("could not find commit within height range [%v,%v]", startHeight, endHeight) 95 } 96 97 // GenerateProtocolSnapshotForCheckpoint finds a sealed block that produces the state commitment contained in the latest 98 // checkpoint file, and return a protocol snapshot for the finalized block that seals the sealed block. 99 // The returned protocol snapshot can be used for dynamic bootstrapping an execution node along with the latest checkpoint file. 100 // 101 // When finding a sealed block it iterates backwards through each sealed height from the last sealed height, and see 102 // if the state commitment matches with one of the state commitments contained in the checkpoint file. 103 // However, the iteration could be slow, in order to speed up the iteration, we can skip some blocks each time. 104 // Since a checkpoint file usually contains 500 tries, which might cover around 250 blocks (assuming 2 tries per block), 105 // then skipping 10 blocks each time will still allow us to find the sealed block while not missing the height contained 106 // by the checkpoint file. 107 // So the blocksToSkip parameter is used to skip some blocks each time when iterating the sealed heights. 108 func GenerateProtocolSnapshotForCheckpoint( 109 logger zerolog.Logger, 110 state protocol.State, 111 headers storage.Headers, 112 seals storage.Seals, 113 checkpointDir string, 114 blocksToSkip uint, 115 ) (protocol.Snapshot, uint64, flow.StateCommitment, string, error) { 116 // skip X blocks (i.e. 10) each time to find the block that produces the state commitment in the checkpoint file 117 // since a checkpoint file contains 500 tries, this allows us to find the block more efficiently 118 sealed, err := state.Sealed().Head() 119 if err != nil { 120 return nil, 0, flow.DummyStateCommitment, "", err 121 } 122 endHeight := sealed.Height 123 124 return GenerateProtocolSnapshotForCheckpointWithHeights(logger, state, headers, seals, 125 checkpointDir, 126 blocksToSkip, 127 endHeight, 128 ) 129 } 130 131 // findLatestCheckpointFilePath finds the latest checkpoint file in the given directory 132 // it returns the header file name of the latest checkpoint file 133 func findLatestCheckpointFilePath(checkpointDir string) (string, error) { 134 _, last, err := wal.ListCheckpoints(checkpointDir) 135 if err != nil { 136 return "", fmt.Errorf("could not list checkpoints in directory %v: %w", checkpointDir, err) 137 } 138 139 fileName := wal.NumberToFilename(last) 140 if last < 0 { 141 fileName = "root.checkpoint" 142 } 143 144 checkpointFilePath := filepath.Join(checkpointDir, fileName) 145 return checkpointFilePath, nil 146 } 147 148 // GenerateProtocolSnapshotForCheckpointWithHeights does the same thing as GenerateProtocolSnapshotForCheckpoint 149 // except that it allows the caller to specify the end height of the sealed block that we iterate backwards from. 150 func GenerateProtocolSnapshotForCheckpointWithHeights( 151 logger zerolog.Logger, 152 state protocol.State, 153 headers storage.Headers, 154 seals storage.Seals, 155 checkpointDir string, 156 blocksToSkip uint, 157 endHeight uint64, 158 ) (protocol.Snapshot, uint64, flow.StateCommitment, string, error) { 159 // Stop searching after 10,000 iterations or upon reaching the minimum height, whichever comes first. 160 startHeight := uint64(0) 161 // preventing startHeight from being negative 162 length := uint64(blocksToSkip+1) * 10000 163 if endHeight > length { 164 startHeight = endHeight - length 165 } 166 167 checkpointFilePath, err := findLatestCheckpointFilePath(checkpointDir) 168 if err != nil { 169 return nil, 0, flow.DummyStateCommitment, "", fmt.Errorf("could not find latest checkpoint file in directory %v: %w", checkpointDir, err) 170 } 171 172 log.Info(). 173 Uint64("start_height", startHeight). 174 Uint64("end_height", endHeight). 175 Uint("blocksToSkip", blocksToSkip). 176 Msgf("generating protocol snapshot for checkpoint file %v", checkpointFilePath) 177 // find the height of the finalized block that produces the state commitment contained in the checkpoint file 178 sealedHeight, commit, finalizedHeight, err := FindHeightsByCheckpoints(logger, headers, seals, checkpointFilePath, blocksToSkip, startHeight, endHeight) 179 if err != nil { 180 return nil, 0, flow.DummyStateCommitment, "", fmt.Errorf("could not find sealed height in range [%v:%v] (blocksToSkip: %v) by checkpoints: %w", 181 startHeight, endHeight, blocksToSkip, 182 err) 183 } 184 185 snapshot := state.AtHeight(finalizedHeight) 186 return snapshot, sealedHeight, commit, checkpointFilePath, nil 187 } 188 189 // hashesToCommits converts a list of ledger.RootHash to a list of flow.StateCommitment 190 func hashesToCommits(hashes []ledger.RootHash) []flow.StateCommitment { 191 commits := make([]flow.StateCommitment, len(hashes)) 192 for i, h := range hashes { 193 commits[i] = flow.StateCommitment(h) 194 } 195 return commits 196 }