You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
4.4 KiB

  1. // nolint: gosec
  2. package app
  3. import (
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "math"
  9. "os"
  10. "path/filepath"
  11. "sync"
  12. abci "github.com/tendermint/tendermint/abci/types"
  13. )
  14. const (
  15. snapshotChunkSize = 1e6
  16. // Keep only the most recent 10 snapshots. Older snapshots are pruned
  17. maxSnapshotCount = 10
  18. )
  19. // SnapshotStore stores state sync snapshots. Snapshots are stored simply as
  20. // JSON files, and chunks are generated on-the-fly by splitting the JSON data
  21. // into fixed-size chunks.
  22. type SnapshotStore struct {
  23. sync.RWMutex
  24. dir string
  25. metadata []abci.Snapshot
  26. }
  27. // NewSnapshotStore creates a new snapshot store.
  28. func NewSnapshotStore(dir string) (*SnapshotStore, error) {
  29. store := &SnapshotStore{dir: dir}
  30. if err := os.MkdirAll(dir, 0755); err != nil {
  31. return nil, err
  32. }
  33. if err := store.loadMetadata(); err != nil {
  34. return nil, err
  35. }
  36. return store, nil
  37. }
  38. // loadMetadata loads snapshot metadata. Does not take out locks, since it's
  39. // called internally on construction.
  40. func (s *SnapshotStore) loadMetadata() error {
  41. file := filepath.Join(s.dir, "metadata.json")
  42. metadata := []abci.Snapshot{}
  43. bz, err := ioutil.ReadFile(file)
  44. switch {
  45. case errors.Is(err, os.ErrNotExist):
  46. case err != nil:
  47. return fmt.Errorf("failed to load snapshot metadata from %q: %w", file, err)
  48. }
  49. if len(bz) != 0 {
  50. err = json.Unmarshal(bz, &metadata)
  51. if err != nil {
  52. return fmt.Errorf("invalid snapshot data in %q: %w", file, err)
  53. }
  54. }
  55. s.metadata = metadata
  56. return nil
  57. }
  58. // saveMetadata saves snapshot metadata. Does not take out locks, since it's
  59. // called internally from e.g. Create().
  60. func (s *SnapshotStore) saveMetadata() error {
  61. bz, err := json.Marshal(s.metadata)
  62. if err != nil {
  63. return err
  64. }
  65. // save the file to a new file and move it to make saving atomic.
  66. newFile := filepath.Join(s.dir, "metadata.json.new")
  67. file := filepath.Join(s.dir, "metadata.json")
  68. err = ioutil.WriteFile(newFile, bz, 0644) // nolint: gosec
  69. if err != nil {
  70. return err
  71. }
  72. return os.Rename(newFile, file)
  73. }
  74. // Create creates a snapshot of the given application state's key/value pairs.
  75. func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) {
  76. s.Lock()
  77. defer s.Unlock()
  78. bz, err := state.Export()
  79. if err != nil {
  80. return abci.Snapshot{}, err
  81. }
  82. snapshot := abci.Snapshot{
  83. Height: state.Height,
  84. Format: 1,
  85. Hash: hashItems(state.Values),
  86. Chunks: byteChunks(bz),
  87. }
  88. err = ioutil.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644)
  89. if err != nil {
  90. return abci.Snapshot{}, err
  91. }
  92. s.metadata = append(s.metadata, snapshot)
  93. err = s.saveMetadata()
  94. if err != nil {
  95. return abci.Snapshot{}, err
  96. }
  97. return snapshot, nil
  98. }
  99. // Prune removes old snapshots ensuring only the most recent n snapshots remain
  100. func (s *SnapshotStore) Prune(n int) error {
  101. s.Lock()
  102. defer s.Unlock()
  103. // snapshots are appended to the metadata struct, hence pruning removes from
  104. // the front of the array
  105. i := 0
  106. for ; i < len(s.metadata)-n; i++ {
  107. h := s.metadata[i].Height
  108. if err := os.Remove(filepath.Join(s.dir, fmt.Sprintf("%v.json", h))); err != nil {
  109. return err
  110. }
  111. }
  112. // update metadata by removing the deleted snapshots
  113. pruned := make([]abci.Snapshot, len(s.metadata[i:]))
  114. copy(pruned, s.metadata[i:])
  115. s.metadata = pruned
  116. return nil
  117. }
  118. // List lists available snapshots.
  119. func (s *SnapshotStore) List() ([]*abci.Snapshot, error) {
  120. s.RLock()
  121. defer s.RUnlock()
  122. snapshots := make([]*abci.Snapshot, len(s.metadata))
  123. for idx := range s.metadata {
  124. snapshots[idx] = &s.metadata[idx]
  125. }
  126. return snapshots, nil
  127. }
  128. // LoadChunk loads a snapshot chunk.
  129. func (s *SnapshotStore) LoadChunk(height uint64, format uint32, chunk uint32) ([]byte, error) {
  130. s.RLock()
  131. defer s.RUnlock()
  132. for _, snapshot := range s.metadata {
  133. if snapshot.Height == height && snapshot.Format == format {
  134. bz, err := ioutil.ReadFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", height)))
  135. if err != nil {
  136. return nil, err
  137. }
  138. return byteChunk(bz, chunk), nil
  139. }
  140. }
  141. return nil, nil
  142. }
  143. // byteChunk returns the chunk at a given index from the full byte slice.
  144. func byteChunk(bz []byte, index uint32) []byte {
  145. start := int(index * snapshotChunkSize)
  146. end := int((index + 1) * snapshotChunkSize)
  147. switch {
  148. case start >= len(bz):
  149. return nil
  150. case end >= len(bz):
  151. return bz[start:]
  152. default:
  153. return bz[start:end]
  154. }
  155. }
  156. // byteChunks calculates the number of chunks in the byte slice.
  157. func byteChunks(bz []byte) uint32 {
  158. return uint32(math.Ceil(float64(len(bz)) / snapshotChunkSize))
  159. }