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  }