diff --git a/CHANGELOG.md b/CHANGELOG.md index a0604a5697e0..34eb102e5211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +## Features + +* [#16060](https://github.com/cosmos/cosmos-sdk/pull/16060) Support saving restoring snapshot locally. + ### Improvements * (deps) [#15973](https://github.com/cosmos/cosmos-sdk/pull/15973) Bump CometBFT to [v0.34.28](https://github.com/cometbft/cometbft/blob/v0.34.28/CHANGELOG.md#v03428). diff --git a/store/snapshots/manager.go b/store/snapshots/manager.go index 717a74ae73fc..e4b9bb65cc25 100644 --- a/store/snapshots/manager.go +++ b/store/snapshots/manager.go @@ -59,7 +59,7 @@ type Manager struct { opts types.SnapshotOptions // multistore is the store from which snapshots are taken. multistore types.Snapshotter - logger storetypes.Logger + logger log.Logger mtx sync.Mutex operation operation @@ -319,7 +319,7 @@ func (m *Manager) Restore(snapshot types.Snapshot) error { dir := m.store.pathSnapshot(snapshot.Height, snapshot.Format) if err := os.MkdirAll(dir, 0o750); err != nil { - return errorsmod.Wrapf(err, "failed to create snapshot directory %q", dir) + return sdkerrors.Wrapf(err, "failed to create snapshot directory %q", dir) } chChunks := m.loadChunkStream(snapshot.Height, snapshot.Format, chChunkIDs) @@ -362,10 +362,9 @@ func (m *Manager) loadChunkStream(height uint64, format uint32, chunkIDs <-chan func (m *Manager) doRestoreSnapshot(snapshot types.Snapshot, chChunks <-chan io.ReadCloser) error { dir := m.store.pathSnapshot(snapshot.Height, snapshot.Format) if err := os.MkdirAll(dir, 0o750); err != nil { - return errorsmod.Wrapf(err, "failed to create snapshot directory %q", dir) + return sdkerrors.Wrapf(err, "failed to create snapshot directory %q", dir) } - var nextItem types.SnapshotItem streamReader, err := NewStreamReader(chChunks) if err != nil { return err @@ -431,7 +430,7 @@ func (m *Manager) RestoreChunk(chunk []byte) (bool, error) { } if int(m.restoreChunkIndex) >= len(m.restoreSnapshot.Metadata.ChunkHashes) { - return false, errorsmod.Wrap(storetypes.ErrLogic, "received unexpected chunk") + return false, sdkerrors.Wrap(sdkerrors.ErrLogic, "received unexpected chunk") } // Check if any errors have occurred yet. @@ -454,7 +453,7 @@ func (m *Manager) RestoreChunk(chunk []byte) (bool, error) { } if err := m.store.saveChunkContent(chunk, m.restoreChunkIndex, m.restoreSnapshot); err != nil { - return false, errorsmod.Wrapf(err, "save chunk content %d", m.restoreChunkIndex) + return false, sdkerrors.Wrapf(err, "save chunk content %d", m.restoreChunkIndex) } // Pass the chunk to the restore, and wait for completion if it was the final one. @@ -468,7 +467,7 @@ func (m *Manager) RestoreChunk(chunk []byte) (bool, error) { // the chunks are all written into files, we can save the snapshot to the db, // even if the restoration may not completed yet. if err := m.store.saveSnapshot(m.restoreSnapshot); err != nil { - return false, errorsmod.Wrap(err, "save restoring snapshot") + return false, sdkerrors.Wrap(err, "save restoring snapshot") } done := <-m.chRestoreDone @@ -505,7 +504,7 @@ func (m *Manager) RestoreLocalSnapshot(height uint64, format uint32) error { } defer m.endLocked() - return m.restoreSnapshot(*snapshot, ch) + return m.doRestoreSnapshot(*snapshot, ch) } // sortedExtensionNames sort extension names for deterministic iteration. diff --git a/store/snapshots/manager_test.go b/store/snapshots/manager_test.go index 8c5c107c4597..4e7eb6d8dd08 100644 --- a/store/snapshots/manager_test.go +++ b/store/snapshots/manager_test.go @@ -214,6 +214,13 @@ func TestManager_Restore(t *testing.T) { assert.Equal(t, expectItems, target.items) assert.Equal(t, 10, len(extSnapshotter.state)) + // The snapshot is saved in local snapshot store + snapshots, err := store.List() + require.NoError(t, err) + snapshot := snapshots[0] + require.Equal(t, uint64(3), snapshot.Height) + require.Equal(t, types.CurrentFormat, snapshot.Format) + // Starting a new restore should fail now, because the target already has contents. err = manager.Restore(types.Snapshot{ Height: 3, diff --git a/store/snapshots/store.go b/store/snapshots/store.go index 149f3b4a8765..df6692e762b2 100644 --- a/store/snapshots/store.go +++ b/store/snapshots/store.go @@ -315,6 +315,12 @@ func (s *Store) saveChunk(chunkBody io.ReadCloser, index uint32, snapshot *types return nil } +// saveChunkContent save the chunk to disk +func (s *Store) saveChunkContent(chunk []byte, index uint32, snapshot *types.Snapshot) error { + path := s.PathChunk(snapshot.Height, snapshot.Format, index) + return os.WriteFile(path, chunk, 0o600) +} + // saveSnapshot saves snapshot metadata to the database. func (s *Store) saveSnapshot(snapshot *types.Snapshot) error { value, err := proto.Marshal(snapshot)