mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-03 03:35:19 +00:00
* cmd: add integration test and fix bug in rollback command (#7315)
(cherry picked from commit bca2080c01)
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
This commit is contained in:
@@ -79,7 +79,7 @@ func DefaultConfig(dir string) *Config {
|
||||
|
||||
// NewApplication creates the application.
|
||||
func NewApplication(cfg *Config) (*Application, error) {
|
||||
state, err := NewState(filepath.Join(cfg.Dir, "state.json"), cfg.PersistInterval)
|
||||
state, err := NewState(cfg.Dir, cfg.PersistInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -254,6 +254,10 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a
|
||||
return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
|
||||
}
|
||||
|
||||
func (app *Application) Rollback() error {
|
||||
return app.state.Rollback()
|
||||
}
|
||||
|
||||
// validatorUpdates generates a validator set update.
|
||||
func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, error) {
|
||||
updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)]
|
||||
|
||||
@@ -8,10 +8,14 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const stateFileName = "app_state.json"
|
||||
const prevStateFileName = "prev_app_state.json"
|
||||
|
||||
// State is the application state.
|
||||
type State struct {
|
||||
sync.RWMutex
|
||||
@@ -20,16 +24,19 @@ type State struct {
|
||||
Hash []byte
|
||||
|
||||
// private fields aren't marshaled to disk.
|
||||
file string
|
||||
currentFile string
|
||||
// app saves current and previous state for rollback functionality
|
||||
previousFile string
|
||||
persistInterval uint64
|
||||
initialHeight uint64
|
||||
}
|
||||
|
||||
// NewState creates a new state.
|
||||
func NewState(file string, persistInterval uint64) (*State, error) {
|
||||
func NewState(dir string, persistInterval uint64) (*State, error) {
|
||||
state := &State{
|
||||
Values: make(map[string]string),
|
||||
file: file,
|
||||
currentFile: filepath.Join(dir, stateFileName),
|
||||
previousFile: filepath.Join(dir, prevStateFileName),
|
||||
persistInterval: persistInterval,
|
||||
}
|
||||
state.Hash = hashItems(state.Values)
|
||||
@@ -45,13 +52,22 @@ func NewState(file string, persistInterval uint64) (*State, error) {
|
||||
// load loads state from disk. It does not take out a lock, since it is called
|
||||
// during construction.
|
||||
func (s *State) load() error {
|
||||
bz, err := ioutil.ReadFile(s.file)
|
||||
bz, err := ioutil.ReadFile(s.currentFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read state from %q: %w", s.file, err)
|
||||
// if the current state doesn't exist then we try recover from the previous state
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
bz, err = ioutil.ReadFile(s.previousFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read both current and previous state (%q): %w",
|
||||
s.previousFile, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to read state from %q: %w", s.currentFile, err)
|
||||
}
|
||||
}
|
||||
err = json.Unmarshal(bz, s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid state data in %q: %w", s.file, err)
|
||||
return fmt.Errorf("invalid state data in %q: %w", s.currentFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -65,12 +81,19 @@ func (s *State) save() error {
|
||||
}
|
||||
// We write the state to a separate file and move it to the destination, to
|
||||
// make it atomic.
|
||||
newFile := fmt.Sprintf("%v.new", s.file)
|
||||
newFile := fmt.Sprintf("%v.new", s.currentFile)
|
||||
err = ioutil.WriteFile(newFile, bz, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write state to %q: %w", s.file, err)
|
||||
return fmt.Errorf("failed to write state to %q: %w", s.currentFile, err)
|
||||
}
|
||||
return os.Rename(newFile, s.file)
|
||||
// We take the current state and move it to the previous state, replacing it
|
||||
if _, err := os.Stat(s.currentFile); err == nil {
|
||||
if err := os.Rename(s.currentFile, s.previousFile); err != nil {
|
||||
return fmt.Errorf("failed to replace previous state: %w", err)
|
||||
}
|
||||
}
|
||||
// Finally, we take the new state and replace the current state.
|
||||
return os.Rename(newFile, s.currentFile)
|
||||
}
|
||||
|
||||
// Export exports key/value pairs as JSON, used for state sync snapshots.
|
||||
@@ -136,6 +159,18 @@ func (s *State) Commit() (uint64, []byte, error) {
|
||||
return s.Height, s.Hash, nil
|
||||
}
|
||||
|
||||
func (s *State) Rollback() error {
|
||||
bz, err := ioutil.ReadFile(s.previousFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read state from %q: %w", s.previousFile, err)
|
||||
}
|
||||
err = json.Unmarshal(bz, s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid state data in %q: %w", s.previousFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashItems hashes a set of key/value items.
|
||||
func hashItems(items map[string]string) []byte {
|
||||
keys := make([]string, 0, len(items))
|
||||
|
||||
Reference in New Issue
Block a user