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