diff --git a/cmd/tendermint/commands/rollback_test.go b/cmd/tendermint/commands/rollback_test.go index 760dbf0ec..189329468 100644 --- a/cmd/tendermint/commands/rollback_test.go +++ b/cmd/tendermint/commands/rollback_test.go @@ -46,6 +46,13 @@ func TestRollbackIntegration(t *testing.T) { height, _, err = commands.RollbackState(cfg) require.NoError(t, err, "%d", height) }) + t.Run("Rollback agian", func(t *testing.T) { + // should be able to rollback agian. + require.NoError(t, app.Rollback()) + height2, _, err := commands.RollbackState(cfg) + require.NoError(t, err, "%d", height2) + require.Equal(t, height-1, height2) + }) t.Run("Restart", func(t *testing.T) { require.True(t, height > 0, "%d", height) @@ -68,7 +75,7 @@ func TestRollbackIntegration(t *testing.T) { status, err := client.Status(ctx) require.NoError(t, err) - if status.SyncInfo.LatestBlockHeight > height { + if status.SyncInfo.LatestBlockHeight > height+2 { return } } diff --git a/test/e2e/app/state.go b/test/e2e/app/state.go index e82a22539..302eaab66 100644 --- a/test/e2e/app/state.go +++ b/test/e2e/app/state.go @@ -12,8 +12,7 @@ import ( "sync" ) -const stateFileName = "app_state.json" -const prevStateFileName = "prev_app_state.json" +const stateFileName = "app_state_%d.json" // State is the application state. type State struct { @@ -23,9 +22,9 @@ type State struct { Hash []byte // private fields aren't marshaled to disk. - currentFile string - // app saves current and previous state for rollback functionality - previousFile string + // app saves current and historical states for rollback functionality + stateFileTemplate string + persistInterval uint64 initialHeight uint64 } @@ -33,10 +32,9 @@ type State struct { // NewState creates a new state. func NewState(dir string, persistInterval uint64) (*State, error) { state := &State{ - Values: make(map[string]string), - currentFile: filepath.Join(dir, stateFileName), - previousFile: filepath.Join(dir, prevStateFileName), - persistInterval: persistInterval, + Values: make(map[string]string), + stateFileTemplate: filepath.Join(dir, stateFileName), + persistInterval: persistInterval, } state.Hash = hashItems(state.Values) err := state.load() @@ -48,25 +46,37 @@ func NewState(dir string, persistInterval uint64) (*State, error) { return state, nil } +func (s *State) currentFile() string { + return s.stateFileAtVersion(s.Height) +} + +func (s *State) previousFile() string { + return s.stateFileAtVersion(s.Height - 1) +} + +func (s *State) stateFileAtVersion(i uint64) string { + return fmt.Sprintf(s.stateFileTemplate, i) +} + // 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 := os.ReadFile(s.currentFile) + bz, err := os.ReadFile(s.currentFile()) if err != nil { // if the current state doesn't exist then we try recover from the previous state if errors.Is(err, os.ErrNotExist) { - bz, err = os.ReadFile(s.previousFile) + bz, err = os.ReadFile(s.previousFile()) if err != nil { return fmt.Errorf("failed to read both current and previous state (%q): %w", - s.previousFile, err) + s.previousFile(), err) } } else { - return fmt.Errorf("failed to read state from %q: %w", s.currentFile, err) + 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.currentFile, err) + return fmt.Errorf("invalid state data in %q: %w", s.currentFile(), err) } return nil } @@ -80,19 +90,13 @@ 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.currentFile) + newFile := fmt.Sprintf("%v.new", s.currentFile()) err = os.WriteFile(newFile, bz, 0644) if err != nil { - return fmt.Errorf("failed to write state to %q: %w", s.currentFile, err) - } - // 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) - } + return fmt.Errorf("failed to write state to %q: %w", s.currentFile(), err) } // Finally, we take the new state and replace the current state. - return os.Rename(newFile, s.currentFile) + return os.Rename(newFile, s.currentFile()) } // Export exports key/value pairs as JSON, used for state sync snapshots. @@ -159,13 +163,13 @@ func (s *State) Commit() (uint64, []byte, error) { } func (s *State) Rollback() error { - bz, err := os.ReadFile(s.previousFile) + bz, err := os.ReadFile(s.previousFile()) if err != nil { - return fmt.Errorf("failed to read state from %q: %w", s.previousFile, err) + 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 fmt.Errorf("invalid state data in %q: %w", s.previousFile(), err) } return nil }