bitbucket.org/number571/tendermint@v0.8.14/test/e2e/app/state.go (about) 1 //nolint: gosec 2 package main 3 4 import ( 5 ghash "bitbucket.org/number571/go-cryptopro/gost_r_34_11_2012" 6 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "sort" 13 "sync" 14 ) 15 16 // State is the application state. 17 type State struct { 18 sync.RWMutex 19 Height uint64 20 Values map[string]string 21 Hash []byte 22 23 // private fields aren't marshaled to disk. 24 file string 25 persistInterval uint64 26 initialHeight uint64 27 } 28 29 // NewState creates a new state. 30 func NewState(file string, persistInterval uint64) (*State, error) { 31 state := &State{ 32 Values: make(map[string]string), 33 file: file, 34 persistInterval: persistInterval, 35 } 36 state.Hash = hashItems(state.Values) 37 err := state.load() 38 switch { 39 case errors.Is(err, os.ErrNotExist): 40 case err != nil: 41 return nil, err 42 } 43 return state, nil 44 } 45 46 // load loads state from disk. It does not take out a lock, since it is called 47 // during construction. 48 func (s *State) load() error { 49 bz, err := ioutil.ReadFile(s.file) 50 if err != nil { 51 return fmt.Errorf("failed to read state from %q: %w", s.file, err) 52 } 53 err = json.Unmarshal(bz, s) 54 if err != nil { 55 return fmt.Errorf("invalid state data in %q: %w", s.file, err) 56 } 57 return nil 58 } 59 60 // save saves the state to disk. It does not take out a lock since it is called 61 // internally by Commit which does lock. 62 func (s *State) save() error { 63 bz, err := json.Marshal(s) 64 if err != nil { 65 return fmt.Errorf("failed to marshal state: %w", err) 66 } 67 // We write the state to a separate file and move it to the destination, to 68 // make it atomic. 69 newFile := fmt.Sprintf("%v.new", s.file) 70 err = ioutil.WriteFile(newFile, bz, 0644) 71 if err != nil { 72 return fmt.Errorf("failed to write state to %q: %w", s.file, err) 73 } 74 return os.Rename(newFile, s.file) 75 } 76 77 // Export exports key/value pairs as JSON, used for state sync snapshots. 78 func (s *State) Export() ([]byte, error) { 79 s.RLock() 80 defer s.RUnlock() 81 return json.Marshal(s.Values) 82 } 83 84 // Import imports key/value pairs from JSON bytes, used for InitChain.AppStateBytes and 85 // state sync snapshots. It also saves the state once imported. 86 func (s *State) Import(height uint64, jsonBytes []byte) error { 87 s.Lock() 88 defer s.Unlock() 89 values := map[string]string{} 90 err := json.Unmarshal(jsonBytes, &values) 91 if err != nil { 92 return fmt.Errorf("failed to decode imported JSON data: %w", err) 93 } 94 s.Height = height 95 s.Values = values 96 s.Hash = hashItems(values) 97 return s.save() 98 } 99 100 // Get fetches a value. A missing value is returned as an empty string. 101 func (s *State) Get(key string) string { 102 s.RLock() 103 defer s.RUnlock() 104 return s.Values[key] 105 } 106 107 // Set sets a value. Setting an empty value is equivalent to deleting it. 108 func (s *State) Set(key, value string) { 109 s.Lock() 110 defer s.Unlock() 111 if value == "" { 112 delete(s.Values, key) 113 } else { 114 s.Values[key] = value 115 } 116 } 117 118 // Commit commits the current state. 119 func (s *State) Commit() (uint64, []byte, error) { 120 s.Lock() 121 defer s.Unlock() 122 s.Hash = hashItems(s.Values) 123 switch { 124 case s.Height > 0: 125 s.Height++ 126 case s.initialHeight > 0: 127 s.Height = s.initialHeight 128 default: 129 s.Height = 1 130 } 131 if s.persistInterval > 0 && s.Height%s.persistInterval == 0 { 132 err := s.save() 133 if err != nil { 134 return 0, nil, err 135 } 136 } 137 return s.Height, s.Hash, nil 138 } 139 140 // hashItems hashes a set of key/value items. 141 func hashItems(items map[string]string) []byte { 142 keys := make([]string, 0, len(items)) 143 for key := range items { 144 keys = append(keys, key) 145 } 146 sort.Strings(keys) 147 148 hasher := ghash.New() 149 for _, key := range keys { 150 _, _ = hasher.Write([]byte(key)) 151 _, _ = hasher.Write([]byte{0}) 152 _, _ = hasher.Write([]byte(items[key])) 153 _, _ = hasher.Write([]byte{0}) 154 } 155 return hasher.Sum(nil) 156 }