github.com/Finschia/finschia-sdk@v0.48.1/snapshots/store.go (about) 1 package snapshots 2 3 import ( 4 "crypto/sha256" 5 "encoding/binary" 6 "io" 7 "math" 8 "os" 9 "path/filepath" 10 "strconv" 11 "sync" 12 13 "github.com/gogo/protobuf/proto" 14 dbm "github.com/tendermint/tm-db" 15 16 "github.com/Finschia/finschia-sdk/snapshots/types" 17 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 18 ) 19 20 const ( 21 // keyPrefixSnapshot is the prefix for snapshot database keys 22 keyPrefixSnapshot byte = 0x01 23 ) 24 25 // Store is a snapshot store, containing snapshot metadata and binary chunks. 26 type Store struct { 27 db dbm.DB 28 dir string 29 30 mtx sync.Mutex 31 saving map[uint64]bool // heights currently being saved 32 } 33 34 // NewStore creates a new snapshot store. 35 func NewStore(db dbm.DB, dir string) (*Store, error) { 36 if dir == "" { 37 return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "snapshot directory not given") 38 } 39 err := os.MkdirAll(dir, 0o755) 40 if err != nil { 41 return nil, sdkerrors.Wrapf(err, "failed to create snapshot directory %q", dir) 42 } 43 44 return &Store{ 45 db: db, 46 dir: dir, 47 saving: make(map[uint64]bool), 48 }, nil 49 } 50 51 // Delete deletes a snapshot. 52 func (s *Store) Delete(height uint64, format uint32) error { 53 s.mtx.Lock() 54 saving := s.saving[height] 55 s.mtx.Unlock() 56 if saving { 57 return sdkerrors.Wrapf(sdkerrors.ErrConflict, 58 "snapshot for height %v format %v is currently being saved", height, format) 59 } 60 err := s.db.DeleteSync(encodeKey(height, format)) 61 if err != nil { 62 return sdkerrors.Wrapf(err, "failed to delete snapshot for height %v format %v", 63 height, format) 64 } 65 err = os.RemoveAll(s.pathSnapshot(height, format)) 66 return sdkerrors.Wrapf(err, "failed to delete snapshot chunks for height %v format %v", 67 height, format) 68 } 69 70 // Get fetches snapshot info from the database. 71 func (s *Store) Get(height uint64, format uint32) (*types.Snapshot, error) { 72 bytes, err := s.db.Get(encodeKey(height, format)) 73 if err != nil { 74 return nil, sdkerrors.Wrapf(err, "failed to fetch snapshot metadata for height %v format %v", 75 height, format) 76 } 77 if bytes == nil { 78 return nil, nil 79 } 80 snapshot := &types.Snapshot{} 81 err = proto.Unmarshal(bytes, snapshot) 82 if err != nil { 83 return nil, sdkerrors.Wrapf(err, "failed to decode snapshot metadata for height %v format %v", 84 height, format) 85 } 86 if snapshot.Metadata.ChunkHashes == nil { 87 snapshot.Metadata.ChunkHashes = [][]byte{} 88 } 89 return snapshot, nil 90 } 91 92 // Get fetches the latest snapshot from the database, if any. 93 func (s *Store) GetLatest() (*types.Snapshot, error) { 94 iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32)) 95 if err != nil { 96 return nil, sdkerrors.Wrap(err, "failed to find latest snapshot") 97 } 98 defer iter.Close() 99 100 var snapshot *types.Snapshot 101 if iter.Valid() { 102 snapshot = &types.Snapshot{} 103 err := proto.Unmarshal(iter.Value(), snapshot) 104 if err != nil { 105 return nil, sdkerrors.Wrap(err, "failed to decode latest snapshot") 106 } 107 } 108 err = iter.Error() 109 return snapshot, sdkerrors.Wrap(err, "failed to find latest snapshot") 110 } 111 112 // List lists snapshots, in reverse order (newest first). 113 func (s *Store) List() ([]*types.Snapshot, error) { 114 iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32)) 115 if err != nil { 116 return nil, sdkerrors.Wrap(err, "failed to list snapshots") 117 } 118 defer iter.Close() 119 120 snapshots := make([]*types.Snapshot, 0) 121 for ; iter.Valid(); iter.Next() { 122 snapshot := &types.Snapshot{} 123 err := proto.Unmarshal(iter.Value(), snapshot) 124 if err != nil { 125 return nil, sdkerrors.Wrap(err, "failed to decode snapshot info") 126 } 127 snapshots = append(snapshots, snapshot) 128 } 129 return snapshots, iter.Error() 130 } 131 132 // Load loads a snapshot (both metadata and binary chunks). The chunks must be consumed and closed. 133 // Returns nil if the snapshot does not exist. 134 func (s *Store) Load(height uint64, format uint32) (*types.Snapshot, <-chan io.ReadCloser, error) { 135 snapshot, err := s.Get(height, format) 136 if snapshot == nil || err != nil { 137 return nil, nil, err 138 } 139 140 ch := make(chan io.ReadCloser) 141 go func() { 142 defer close(ch) 143 for i := uint32(0); i < snapshot.Chunks; i++ { 144 pr, pw := io.Pipe() 145 ch <- pr 146 chunk, err := s.loadChunkFile(height, format, i) 147 if err != nil { 148 pw.CloseWithError(err) 149 return 150 } 151 defer chunk.Close() 152 _, err = io.Copy(pw, chunk) 153 if err != nil { 154 pw.CloseWithError(err) 155 return 156 } 157 chunk.Close() 158 pw.Close() 159 } 160 }() 161 162 return snapshot, ch, nil 163 } 164 165 // LoadChunk loads a chunk from disk, or returns nil if it does not exist. The caller must call 166 // Close() on it when done. 167 func (s *Store) LoadChunk(height uint64, format uint32, chunk uint32) (io.ReadCloser, error) { 168 path := s.pathChunk(height, format, chunk) 169 file, err := os.Open(path) 170 if os.IsNotExist(err) { 171 return nil, nil 172 } 173 return file, err 174 } 175 176 // loadChunkFile loads a chunk from disk, and errors if it does not exist. 177 func (s *Store) loadChunkFile(height uint64, format uint32, chunk uint32) (io.ReadCloser, error) { 178 path := s.pathChunk(height, format, chunk) 179 return os.Open(path) 180 } 181 182 // Prune removes old snapshots. The given number of most recent heights (regardless of format) are retained. 183 func (s *Store) Prune(retain uint32) (uint64, error) { 184 iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32)) 185 if err != nil { 186 return 0, sdkerrors.Wrap(err, "failed to prune snapshots") 187 } 188 defer iter.Close() 189 190 pruned := uint64(0) 191 prunedHeights := make(map[uint64]bool) 192 skip := make(map[uint64]bool) 193 for ; iter.Valid(); iter.Next() { 194 height, format, err := decodeKey(iter.Key()) 195 if err != nil { 196 return 0, sdkerrors.Wrap(err, "failed to prune snapshots") 197 } 198 if skip[height] || uint32(len(skip)) < retain { 199 skip[height] = true 200 continue 201 } 202 err = s.Delete(height, format) 203 if err != nil { 204 return 0, sdkerrors.Wrap(err, "failed to prune snapshots") 205 } 206 pruned++ 207 prunedHeights[height] = true 208 } 209 // Since Delete() deletes a specific format, while we want to prune a height, we clean up 210 // the height directory as well 211 for height, ok := range prunedHeights { 212 if ok { 213 err = os.Remove(s.pathHeight(height)) 214 if err != nil { 215 return 0, sdkerrors.Wrapf(err, "failed to remove snapshot directory for height %v", height) 216 } 217 } 218 } 219 return pruned, iter.Error() 220 } 221 222 // Save saves a snapshot to disk, returning it. 223 func (s *Store) Save( 224 height uint64, format uint32, chunks <-chan io.ReadCloser, 225 ) (*types.Snapshot, error) { 226 defer DrainChunks(chunks) 227 if height == 0 { 228 return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "snapshot height cannot be 0") 229 } 230 231 s.mtx.Lock() 232 saving := s.saving[height] 233 s.saving[height] = true 234 s.mtx.Unlock() 235 if saving { 236 return nil, sdkerrors.Wrapf(sdkerrors.ErrConflict, 237 "a snapshot for height %v is already being saved", height) 238 } 239 defer func() { 240 s.mtx.Lock() 241 delete(s.saving, height) 242 s.mtx.Unlock() 243 }() 244 245 exists, err := s.db.Has(encodeKey(height, format)) 246 if err != nil { 247 return nil, err 248 } 249 if exists { 250 return nil, sdkerrors.Wrapf(sdkerrors.ErrConflict, 251 "snapshot already exists for height %v format %v", height, format) 252 } 253 254 snapshot := &types.Snapshot{ 255 Height: height, 256 Format: format, 257 } 258 index := uint32(0) 259 snapshotHasher := sha256.New() 260 chunkHasher := sha256.New() 261 for chunkBody := range chunks { 262 defer chunkBody.Close() //nolint: staticcheck 263 dir := s.pathSnapshot(height, format) 264 err = os.MkdirAll(dir, 0o755) 265 if err != nil { 266 return nil, sdkerrors.Wrapf(err, "failed to create snapshot directory %q", dir) 267 } 268 path := s.pathChunk(height, format, index) 269 file, err := os.Create(path) 270 if err != nil { 271 return nil, sdkerrors.Wrapf(err, "failed to create snapshot chunk file %q", path) 272 } 273 defer file.Close() //nolint: staticcheck 274 275 chunkHasher.Reset() 276 _, err = io.Copy(io.MultiWriter(file, chunkHasher, snapshotHasher), chunkBody) 277 if err != nil { 278 return nil, sdkerrors.Wrapf(err, "failed to generate snapshot chunk %v", index) 279 } 280 err = file.Close() 281 if err != nil { 282 return nil, sdkerrors.Wrapf(err, "failed to close snapshot chunk %v", index) 283 } 284 err = chunkBody.Close() 285 if err != nil { 286 return nil, sdkerrors.Wrapf(err, "failed to close snapshot chunk %v", index) 287 } 288 snapshot.Metadata.ChunkHashes = append(snapshot.Metadata.ChunkHashes, chunkHasher.Sum(nil)) 289 index++ 290 } 291 snapshot.Chunks = index 292 snapshot.Hash = snapshotHasher.Sum(nil) 293 return snapshot, s.saveSnapshot(snapshot) 294 } 295 296 // saveSnapshot saves snapshot metadata to the database. 297 func (s *Store) saveSnapshot(snapshot *types.Snapshot) error { 298 value, err := proto.Marshal(snapshot) 299 if err != nil { 300 return sdkerrors.Wrap(err, "failed to encode snapshot metadata") 301 } 302 err = s.db.SetSync(encodeKey(snapshot.Height, snapshot.Format), value) 303 return sdkerrors.Wrap(err, "failed to store snapshot") 304 } 305 306 // pathHeight generates the path to a height, containing multiple snapshot formats. 307 func (s *Store) pathHeight(height uint64) string { 308 return filepath.Join(s.dir, strconv.FormatUint(height, 10)) 309 } 310 311 // pathSnapshot generates a snapshot path, as a specific format under a height. 312 func (s *Store) pathSnapshot(height uint64, format uint32) string { 313 return filepath.Join(s.pathHeight(height), strconv.FormatUint(uint64(format), 10)) 314 } 315 316 // pathChunk generates a snapshot chunk path. 317 func (s *Store) pathChunk(height uint64, format uint32, chunk uint32) string { 318 return filepath.Join(s.pathSnapshot(height, format), strconv.FormatUint(uint64(chunk), 10)) 319 } 320 321 // decodeKey decodes a snapshot key. 322 func decodeKey(k []byte) (uint64, uint32, error) { 323 if len(k) != 13 { 324 return 0, 0, sdkerrors.Wrapf(sdkerrors.ErrLogic, "invalid snapshot key with length %v", len(k)) 325 } 326 if k[0] != keyPrefixSnapshot { 327 return 0, 0, sdkerrors.Wrapf(sdkerrors.ErrLogic, "invalid snapshot key prefix %x", k[0]) 328 } 329 height := binary.BigEndian.Uint64(k[1:9]) 330 format := binary.BigEndian.Uint32(k[9:13]) 331 return height, format, nil 332 } 333 334 // encodeKey encodes a snapshot key. 335 func encodeKey(height uint64, format uint32) []byte { 336 k := make([]byte, 13) 337 k[0] = keyPrefixSnapshot 338 binary.BigEndian.PutUint64(k[1:], height) 339 binary.BigEndian.PutUint32(k[9:], format) 340 return k 341 }