From 51d2de582d036ece4af10267e889f6fdd97f8acf Mon Sep 17 00:00:00 2001 From: yihuang Date: Tue, 30 Aug 2022 13:50:00 +0800 Subject: [PATCH] fix: rollback command don't actually delete multistore versions (#11361) * rollback command don't actually delete multistore versions Closes: #11333 - add unit tests - use LoadVersionForOverwriting - update tendermint dependency to 0.35.x release branch Co-authored-by: Aleksandr Bezobchuk flushMetadata after rollback Update server/rollback.go Co-authored-by: Aleksandr Bezobchuk fix build gofumpt * fix unit test --- baseapp/baseapp.go | 5 +-- server/mock/store.go | 4 ++ server/rollback.go | 11 +++-- server/types/app.go | 4 ++ server/util.go | 2 +- store/iavl/store.go | 6 +++ store/iavl/tree.go | 5 +++ store/rootmulti/rollback_test.go | 69 ++++++++++++++++++++++++++++++++ store/rootmulti/store.go | 35 ++++++++-------- store/types/store.go | 3 ++ 10 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 store/rootmulti/rollback_test.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index d9df732dac82..3cd69facbd00 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -283,11 +283,8 @@ func DefaultStoreLoader(ms sdk.CommitMultiStore) error { // CommitMultiStore returns the root multi-store. // App constructor can use this to access the `cms`. -// UNSAFE: only safe to use during app initialization. +// UNSAFE: must not be used during the abci life cycle. func (app *BaseApp) CommitMultiStore() sdk.CommitMultiStore { - if app.sealed { - panic("cannot call CommitMultiStore() after baseapp is sealed") - } return app.cms } diff --git a/server/mock/store.go b/server/mock/store.go index ebc4caaa421f..8eaa002457ba 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -144,6 +144,10 @@ func (ms multiStore) Restore( panic("not implemented") } +func (ms multiStore) RollbackToVersion(version int64) error { + panic("not implemented") +} + var _ sdk.KVStore = kvStore{} type kvStore struct { diff --git a/server/rollback.go b/server/rollback.go index e99088dd73cc..c3dba335466b 100644 --- a/server/rollback.go +++ b/server/rollback.go @@ -4,13 +4,13 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/cosmos/cosmos-sdk/server/types" "github.com/spf13/cobra" tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" ) // NewRollbackCmd creates a command to rollback tendermint and multistore state by one height. -func NewRollbackCmd(defaultNodeHome string) *cobra.Command { +func NewRollbackCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command { cmd := &cobra.Command{ Use: "rollback", Short: "rollback cosmos-sdk and tendermint state by one height", @@ -30,14 +30,17 @@ application. if err != nil { return err } + app := appCreator(ctx.Logger, db, nil, ctx.Viper) // rollback tendermint state height, hash, err := tmcmd.RollbackState(ctx.Config) if err != nil { return fmt.Errorf("failed to rollback tendermint state: %w", err) } // rollback the multistore - cms := rootmulti.NewStore(db, ctx.Logger) - cms.RollbackToVersion(height) + + if err := app.CommitMultiStore().RollbackToVersion(height); err != nil { + return fmt.Errorf("failed to rollback to version: %w", err) + } fmt.Printf("Rolled back state to height %d and hash %X", height, hash) return nil diff --git a/server/types/app.go b/server/types/app.go index 467f627c605f..69cc0476f046 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" ) // ServerStartTime defines the time duration that the server need to stay running after startup @@ -51,6 +52,9 @@ type ( // RegisterTendermintService registers the gRPC Query service for tendermint queries. RegisterTendermintService(clientCtx client.Context) + + // Return the multistore instance + CommitMultiStore() sdk.CommitMultiStore } // AppCreator is a function that allows us to lazily initialize an diff --git a/server/util.go b/server/util.go index 7e6df6eec038..4cd5aaf4fe9f 100644 --- a/server/util.go +++ b/server/util.go @@ -290,7 +290,7 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type tendermintCmd, ExportCmd(appExport, defaultNodeHome), version.NewVersionCommand(), - NewRollbackCmd(defaultNodeHome), + NewRollbackCmd(appCreator, defaultNodeHome), ) } diff --git a/store/iavl/store.go b/store/iavl/store.go index fd57436a4fa5..6437971f923b 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -234,6 +234,12 @@ func (st *Store) DeleteVersions(versions ...int64) error { return st.tree.DeleteVersions(versions...) } +// LoadVersionForOverwriting attempts to load a tree at a previously committed +// version, or the latest version below it. Any versions greater than targetVersion will be deleted. +func (st *Store) LoadVersionForOverwriting(targetVersion int64) (int64, error) { + return st.tree.LoadVersionForOverwriting(targetVersion) +} + // Implements types.KVStore. func (st *Store) Iterator(start, end []byte) types.Iterator { iterator, err := st.tree.Iterator(start, end, true) diff --git a/store/iavl/tree.go b/store/iavl/tree.go index 210663a21509..81350fc34a8b 100644 --- a/store/iavl/tree.go +++ b/store/iavl/tree.go @@ -34,6 +34,7 @@ type ( SetInitialVersion(version uint64) Iterator(start, end []byte, ascending bool) (types.Iterator, error) AvailableVersions() []int + LoadVersionForOverwriting(targetVersion int64) (int64, error) } // immutableTree is a simple wrapper around a reference to an iavl.ImmutableTree @@ -99,3 +100,7 @@ func (it *immutableTree) GetImmutable(version int64) (*iavl.ImmutableTree, error func (it *immutableTree) AvailableVersions() []int { return []int{} } + +func (it *immutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { + panic("cannot call 'LoadVersionForOverwriting' on an immutable IAVL tree") +} diff --git a/store/rootmulti/rollback_test.go b/store/rootmulti/rollback_test.go new file mode 100644 index 000000000000..5bb94acbb74f --- /dev/null +++ b/store/rootmulti/rollback_test.go @@ -0,0 +1,69 @@ +package rootmulti_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/simapp" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" +) + +func TestRollback(t *testing.T) { + db := dbm.NewMemDB() + options := simapp.SetupOptions{ + Logger: log.NewNopLogger(), + DB: db, + AppOpts: simtestutil.NewAppOptionsWithFlagHome(simapp.DefaultNodeHome), + } + app := simapp.NewSimappWithCustomOptions(t, false, options) + app.Commit() + ver0 := app.LastBlockHeight() + // commit 10 blocks + for i := int64(1); i <= 10; i++ { + header := tmproto.Header{ + Height: ver0 + i, + AppHash: app.LastCommitID().Hash, + } + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + ctx := app.NewContext(false, header) + store := ctx.KVStore(app.GetKey("bank")) + store.Set([]byte("key"), []byte(fmt.Sprintf("value%d", i))) + app.Commit() + } + + require.Equal(t, ver0+10, app.LastBlockHeight()) + store := app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank")) + require.Equal(t, []byte("value10"), store.Get([]byte("key"))) + + // rollback 5 blocks + target := ver0 + 5 + require.NoError(t, app.CommitMultiStore().RollbackToVersion(target)) + require.Equal(t, target, app.LastBlockHeight()) + + // recreate app to have clean check state + app = simapp.NewSimApp(options.Logger, options.DB, nil, true, simtestutil.NewAppOptionsWithFlagHome(simapp.DefaultNodeHome)) + store = app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank")) + require.Equal(t, []byte("value5"), store.Get([]byte("key"))) + + // commit another 5 blocks with different values + for i := int64(6); i <= 10; i++ { + header := tmproto.Header{ + Height: ver0 + i, + AppHash: app.LastCommitID().Hash, + } + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + ctx := app.NewContext(false, header) + store := ctx.KVStore(app.GetKey("bank")) + store.Set([]byte("key"), []byte(fmt.Sprintf("VALUE%d", i))) + app.Commit() + } + + require.Equal(t, ver0+10, app.LastBlockHeight()) + store = app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank")) + require.Equal(t, []byte("VALUE10"), store.Get([]byte("key"))) +} diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index f44b53d50969..0372c4b9d10b 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -933,29 +933,26 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo { } // RollbackToVersion delete the versions after `target` and update the latest version. -func (rs *Store) RollbackToVersion(target int64) int64 { - if target < 0 { - panic("Negative rollback target") - } - current := getLatestVersion(rs.db) - if target >= current { - return current - } - for ; current > target; current-- { - rs.pruningManager.HandleHeight(current) - } - if err := rs.pruneStores(); err != nil { - panic(err) +func (rs *Store) RollbackToVersion(target int64) error { + if target <= 0 { + return fmt.Errorf("invalid rollback height target: %d", target) } - // update latest height - bz, err := gogotypes.StdInt64Marshal(current) - if err != nil { - panic(err) + for key, store := range rs.stores { + if store.GetStoreType() == types.StoreTypeIAVL { + // If the store is wrapped with an inter-block cache, we must first unwrap + // it to get the underlying IAVL store. + store = rs.GetCommitKVStore(key) + _, err := store.(*iavl.Store).LoadVersionForOverwriting(target) + if err != nil { + return err + } + } } - rs.db.Set([]byte(latestVersionKey), bz) - return current + rs.flushMetadata(rs.db, target, rs.buildCommitInfo(target)) + + return rs.LoadLatestVersion() } func (rs *Store) flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo) { diff --git a/store/types/store.go b/store/types/store.go index 37f583900909..0ef43576d426 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -188,6 +188,9 @@ type CommitMultiStore interface { // SetIAVLCacheSize sets the cache size of the IAVL tree. SetIAVLCacheSize(size int) + + // RollbackToVersion rollback the db to specific version(height). + RollbackToVersion(version int64) error } //---------subsp-------------------------------