github.com/Oyster-zx/tendermint@v0.34.24-fork/test/e2e/app/state.go (about)

     1  package app
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"sync"
    12  )
    13  
    14  const (
    15  	stateFileName     = "app_state.json"
    16  	prevStateFileName = "prev_app_state.json"
    17  )
    18  
    19  // State is the application state.
    20  type State struct {
    21  	sync.RWMutex
    22  	Height uint64
    23  	Values map[string]string
    24  	Hash   []byte
    25  
    26  	// private fields aren't marshaled to disk.
    27  	currentFile string
    28  	// app saves current and previous state for rollback functionality
    29  	previousFile    string
    30  	persistInterval uint64
    31  	initialHeight   uint64
    32  }
    33  
    34  // NewState creates a new state.
    35  func NewState(dir string, persistInterval uint64) (*State, error) {
    36  	state := &State{
    37  		Values:          make(map[string]string),
    38  		currentFile:     filepath.Join(dir, stateFileName),
    39  		previousFile:    filepath.Join(dir, prevStateFileName),
    40  		persistInterval: persistInterval,
    41  	}
    42  	state.Hash = hashItems(state.Values)
    43  	err := state.load()
    44  	switch {
    45  	case errors.Is(err, os.ErrNotExist):
    46  	case err != nil:
    47  		return nil, err
    48  	}
    49  	return state, nil
    50  }
    51  
    52  // load loads state from disk. It does not take out a lock, since it is called
    53  // during construction.
    54  func (s *State) load() error {
    55  	bz, err := os.ReadFile(s.currentFile)
    56  	if err != nil {
    57  		// if the current state doesn't exist then we try recover from the previous state
    58  		if errors.Is(err, os.ErrNotExist) {
    59  			bz, err = os.ReadFile(s.previousFile)
    60  			if err != nil {
    61  				return fmt.Errorf("failed to read both current and previous state (%q): %w",
    62  					s.previousFile, err)
    63  			}
    64  		} else {
    65  			return fmt.Errorf("failed to read state from %q: %w", s.currentFile, err)
    66  		}
    67  	}
    68  	err = json.Unmarshal(bz, s)
    69  	if err != nil {
    70  		return fmt.Errorf("invalid state data in %q: %w", s.currentFile, err)
    71  	}
    72  	return nil
    73  }
    74  
    75  // save saves the state to disk. It does not take out a lock since it is called
    76  // internally by Commit which does lock.
    77  func (s *State) save() error {
    78  	bz, err := json.Marshal(s)
    79  	if err != nil {
    80  		return fmt.Errorf("failed to marshal state: %w", err)
    81  	}
    82  	// We write the state to a separate file and move it to the destination, to
    83  	// make it atomic.
    84  	newFile := fmt.Sprintf("%v.new", s.currentFile)
    85  	//nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less
    86  	err = os.WriteFile(newFile, bz, 0o644)
    87  	if err != nil {
    88  		return fmt.Errorf("failed to write state to %q: %w", s.currentFile, err)
    89  	}
    90  	// We take the current state and move it to the previous state, replacing it
    91  	if _, err := os.Stat(s.currentFile); err == nil {
    92  		if err := os.Rename(s.currentFile, s.previousFile); err != nil {
    93  			return fmt.Errorf("failed to replace previous state: %w", err)
    94  		}
    95  	}
    96  	// Finally, we take the new state and replace the current state.
    97  	return os.Rename(newFile, s.currentFile)
    98  }
    99  
   100  // Export exports key/value pairs as JSON, used for state sync snapshots.
   101  func (s *State) Export() ([]byte, error) {
   102  	s.RLock()
   103  	defer s.RUnlock()
   104  	return json.Marshal(s.Values)
   105  }
   106  
   107  // Import imports key/value pairs from JSON bytes, used for InitChain.AppStateBytes and
   108  // state sync snapshots. It also saves the state once imported.
   109  func (s *State) Import(height uint64, jsonBytes []byte) error {
   110  	s.Lock()
   111  	defer s.Unlock()
   112  	values := map[string]string{}
   113  	err := json.Unmarshal(jsonBytes, &values)
   114  	if err != nil {
   115  		return fmt.Errorf("failed to decode imported JSON data: %w", err)
   116  	}
   117  	s.Height = height
   118  	s.Values = values
   119  	s.Hash = hashItems(values)
   120  	return s.save()
   121  }
   122  
   123  // Get fetches a value. A missing value is returned as an empty string.
   124  func (s *State) Get(key string) string {
   125  	s.RLock()
   126  	defer s.RUnlock()
   127  	return s.Values[key]
   128  }
   129  
   130  // Set sets a value. Setting an empty value is equivalent to deleting it.
   131  func (s *State) Set(key, value string) {
   132  	s.Lock()
   133  	defer s.Unlock()
   134  	if value == "" {
   135  		delete(s.Values, key)
   136  	} else {
   137  		s.Values[key] = value
   138  	}
   139  }
   140  
   141  // Commit commits the current state.
   142  func (s *State) Commit() (uint64, []byte, error) {
   143  	s.Lock()
   144  	defer s.Unlock()
   145  	s.Hash = hashItems(s.Values)
   146  	switch {
   147  	case s.Height > 0:
   148  		s.Height++
   149  	case s.initialHeight > 0:
   150  		s.Height = s.initialHeight
   151  	default:
   152  		s.Height = 1
   153  	}
   154  	if s.persistInterval > 0 && s.Height%s.persistInterval == 0 {
   155  		err := s.save()
   156  		if err != nil {
   157  			return 0, nil, err
   158  		}
   159  	}
   160  	return s.Height, s.Hash, nil
   161  }
   162  
   163  func (s *State) Rollback() error {
   164  	bz, err := os.ReadFile(s.previousFile)
   165  	if err != nil {
   166  		return fmt.Errorf("failed to read state from %q: %w", s.previousFile, err)
   167  	}
   168  	err = json.Unmarshal(bz, s)
   169  	if err != nil {
   170  		return fmt.Errorf("invalid state data in %q: %w", s.previousFile, err)
   171  	}
   172  	return nil
   173  }
   174  
   175  // hashItems hashes a set of key/value items.
   176  func hashItems(items map[string]string) []byte {
   177  	keys := make([]string, 0, len(items))
   178  	for key := range items {
   179  		keys = append(keys, key)
   180  	}
   181  	sort.Strings(keys)
   182  
   183  	hasher := sha256.New()
   184  	for _, key := range keys {
   185  		_, _ = hasher.Write([]byte(key))
   186  		_, _ = hasher.Write([]byte{0})
   187  		_, _ = hasher.Write([]byte(items[key]))
   188  		_, _ = hasher.Write([]byte{0})
   189  	}
   190  	return hasher.Sum(nil)
   191  }