Skip to content

Commit a95c626

Browse files
mergify[bot]yihuang
andauthored
fix: rollback command don't actually delete multistore versions (backport cosmos#11361) (cosmos#13089)
* fix: rollback command don't actually delete multistore versions (cosmos#11361) * rollback command don't actually delete multistore versions Closes: cosmos#11333 - add unit tests - use LoadVersionForOverwriting - update tendermint dependency to 0.35.x release branch Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> flushMetadata after rollback Update server/rollback.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> fix build gofumpt * fix unit test (cherry picked from commit 51d2de5) * fix unit test * changelog * api breaking changelog Co-authored-by: yihuang <huang@crypto.com>
1 parent 78c24eb commit a95c626

File tree

11 files changed

+123
-28
lines changed

11 files changed

+123
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
3737

3838
## [Unreleased]
3939

40+
### API Breaking Changes
41+
42+
- (cli) [#13089](https://github.com/cosmos/cosmos-sdk/pull/13089) Fix rollback command don't actually delete multistore versions, added method `RollbackToVersion` to interface `CommitMultiStore` and added method `CommitMultiStore` to `Application` interface.
43+
4044
### Features
4145

4246
* (x/authz) [#13047](https://github.com/cosmos/cosmos-sdk/pull/13047) Add a GetAuthorization function to the keeper.

baseapp/baseapp.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,8 @@ func DefaultStoreLoader(ms sdk.CommitMultiStore) error {
276276

277277
// CommitMultiStore returns the root multi-store.
278278
// App constructor can use this to access the `cms`.
279-
// UNSAFE: only safe to use during app initialization.
279+
// UNSAFE: must not be used during the abci life cycle.
280280
func (app *BaseApp) CommitMultiStore() sdk.CommitMultiStore {
281-
if app.sealed {
282-
panic("cannot call CommitMultiStore() after baseapp is sealed")
283-
}
284281
return app.cms
285282
}
286283

server/mock/store.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ func (ms multiStore) Restore(
144144
panic("not implemented")
145145
}
146146

147+
func (ms multiStore) RollbackToVersion(version int64) error {
148+
panic("not implemented")
149+
}
150+
147151
var _ sdk.KVStore = kvStore{}
148152

149153
type kvStore struct {

server/rollback.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import (
44
"fmt"
55

66
"github.com/cosmos/cosmos-sdk/client/flags"
7-
"github.com/cosmos/cosmos-sdk/store/rootmulti"
7+
"github.com/cosmos/cosmos-sdk/server/types"
88
"github.com/spf13/cobra"
99
tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
1010
)
1111

1212
// NewRollbackCmd creates a command to rollback tendermint and multistore state by one height.
13-
func NewRollbackCmd(defaultNodeHome string) *cobra.Command {
13+
func NewRollbackCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
1414
cmd := &cobra.Command{
1515
Use: "rollback",
1616
Short: "rollback cosmos-sdk and tendermint state by one height",
@@ -30,14 +30,17 @@ application.
3030
if err != nil {
3131
return err
3232
}
33+
app := appCreator(ctx.Logger, db, nil, ctx.Viper)
3334
// rollback tendermint state
3435
height, hash, err := tmcmd.RollbackState(ctx.Config)
3536
if err != nil {
3637
return fmt.Errorf("failed to rollback tendermint state: %w", err)
3738
}
3839
// rollback the multistore
39-
cms := rootmulti.NewStore(db, ctx.Logger)
40-
cms.RollbackToVersion(height)
40+
41+
if err := app.CommitMultiStore().RollbackToVersion(height); err != nil {
42+
return fmt.Errorf("failed to rollback to version: %w", err)
43+
}
4144

4245
fmt.Printf("Rolled back state to height %d and hash %X", height, hash)
4346
return nil

server/types/app.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/cosmos/cosmos-sdk/client"
1616
"github.com/cosmos/cosmos-sdk/server/api"
1717
"github.com/cosmos/cosmos-sdk/server/config"
18+
sdk "github.com/cosmos/cosmos-sdk/types"
1819
)
1920

2021
// ServerStartTime defines the time duration that the server need to stay running after startup
@@ -51,6 +52,9 @@ type (
5152

5253
// RegisterTendermintService registers the gRPC Query service for tendermint queries.
5354
RegisterTendermintService(clientCtx client.Context)
55+
56+
// Return the multistore instance
57+
CommitMultiStore() sdk.CommitMultiStore
5458
}
5559

5660
// AppCreator is a function that allows us to lazily initialize an

server/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type
290290
tendermintCmd,
291291
ExportCmd(appExport, defaultNodeHome),
292292
version.NewVersionCommand(),
293-
NewRollbackCmd(defaultNodeHome),
293+
NewRollbackCmd(appCreator, defaultNodeHome),
294294
)
295295
}
296296

store/iavl/store.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ func (st *Store) DeleteVersions(versions ...int64) error {
234234
return st.tree.DeleteVersions(versions...)
235235
}
236236

237+
// LoadVersionForOverwriting attempts to load a tree at a previously committed
238+
// version, or the latest version below it. Any versions greater than targetVersion will be deleted.
239+
func (st *Store) LoadVersionForOverwriting(targetVersion int64) (int64, error) {
240+
return st.tree.LoadVersionForOverwriting(targetVersion)
241+
}
242+
237243
// Implements types.KVStore.
238244
func (st *Store) Iterator(start, end []byte) types.Iterator {
239245
iterator, err := st.tree.Iterator(start, end, true)

store/iavl/tree.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type (
3434
SetInitialVersion(version uint64)
3535
Iterator(start, end []byte, ascending bool) (types.Iterator, error)
3636
AvailableVersions() []int
37+
LoadVersionForOverwriting(targetVersion int64) (int64, error)
3738
}
3839

3940
// 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
99100
func (it *immutableTree) AvailableVersions() []int {
100101
return []int{}
101102
}
103+
104+
func (it *immutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) {
105+
panic("cannot call 'LoadVersionForOverwriting' on an immutable IAVL tree")
106+
}

store/rootmulti/rollback_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package rootmulti_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/cosmos/cosmos-sdk/simapp"
8+
"github.com/stretchr/testify/require"
9+
abci "github.com/tendermint/tendermint/abci/types"
10+
"github.com/tendermint/tendermint/libs/log"
11+
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
12+
dbm "github.com/tendermint/tm-db"
13+
)
14+
15+
func TestRollback(t *testing.T) {
16+
db := dbm.NewMemDB()
17+
options := simapp.SetupOptions{
18+
Logger: log.NewNopLogger(),
19+
DB: db,
20+
InvCheckPeriod: 0,
21+
EncConfig: simapp.MakeTestEncodingConfig(),
22+
HomePath: simapp.DefaultNodeHome,
23+
SkipUpgradeHeights: map[int64]bool{},
24+
AppOpts: simapp.EmptyAppOptions{},
25+
}
26+
app := simapp.NewSimappWithCustomOptions(t, false, options)
27+
app.Commit()
28+
ver0 := app.LastBlockHeight()
29+
// commit 10 blocks
30+
for i := int64(1); i <= 10; i++ {
31+
header := tmproto.Header{
32+
Height: ver0 + i,
33+
AppHash: app.LastCommitID().Hash,
34+
}
35+
app.BeginBlock(abci.RequestBeginBlock{Header: header})
36+
ctx := app.NewContext(false, header)
37+
store := ctx.KVStore(app.GetKey("bank"))
38+
store.Set([]byte("key"), []byte(fmt.Sprintf("value%d", i)))
39+
app.Commit()
40+
}
41+
42+
require.Equal(t, ver0+10, app.LastBlockHeight())
43+
store := app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank"))
44+
require.Equal(t, []byte("value10"), store.Get([]byte("key")))
45+
46+
// rollback 5 blocks
47+
target := ver0 + 5
48+
require.NoError(t, app.CommitMultiStore().RollbackToVersion(target))
49+
require.Equal(t, target, app.LastBlockHeight())
50+
51+
// recreate app to have clean check state
52+
app = simapp.NewSimApp(options.Logger, options.DB, nil, true, map[int64]bool{}, simapp.DefaultNodeHome, options.InvCheckPeriod, options.EncConfig, options.AppOpts)
53+
store = app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank"))
54+
require.Equal(t, []byte("value5"), store.Get([]byte("key")))
55+
56+
// commit another 5 blocks with different values
57+
for i := int64(6); i <= 10; i++ {
58+
header := tmproto.Header{
59+
Height: ver0 + i,
60+
AppHash: app.LastCommitID().Hash,
61+
}
62+
app.BeginBlock(abci.RequestBeginBlock{Header: header})
63+
ctx := app.NewContext(false, header)
64+
store := ctx.KVStore(app.GetKey("bank"))
65+
store.Set([]byte("key"), []byte(fmt.Sprintf("VALUE%d", i)))
66+
app.Commit()
67+
}
68+
69+
require.Equal(t, ver0+10, app.LastBlockHeight())
70+
store = app.NewContext(true, tmproto.Header{}).KVStore(app.GetKey("bank"))
71+
require.Equal(t, []byte("VALUE10"), store.Get([]byte("key")))
72+
}

store/rootmulti/store.go

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -924,29 +924,26 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo {
924924
}
925925

926926
// RollbackToVersion delete the versions after `target` and update the latest version.
927-
func (rs *Store) RollbackToVersion(target int64) int64 {
928-
if target < 0 {
929-
panic("Negative rollback target")
930-
}
931-
current := getLatestVersion(rs.db)
932-
if target >= current {
933-
return current
934-
}
935-
for ; current > target; current-- {
936-
rs.pruningManager.HandleHeight(current)
937-
}
938-
if err := rs.pruneStores(); err != nil {
939-
panic(err)
927+
func (rs *Store) RollbackToVersion(target int64) error {
928+
if target <= 0 {
929+
return fmt.Errorf("invalid rollback height target: %d", target)
940930
}
941931

942-
// update latest height
943-
bz, err := gogotypes.StdInt64Marshal(current)
944-
if err != nil {
945-
panic(err)
932+
for key, store := range rs.stores {
933+
if store.GetStoreType() == types.StoreTypeIAVL {
934+
// If the store is wrapped with an inter-block cache, we must first unwrap
935+
// it to get the underlying IAVL store.
936+
store = rs.GetCommitKVStore(key)
937+
_, err := store.(*iavl.Store).LoadVersionForOverwriting(target)
938+
if err != nil {
939+
return err
940+
}
941+
}
946942
}
947943

948-
rs.db.Set([]byte(latestVersionKey), bz)
949-
return current
944+
rs.flushMetadata(rs.db, target, rs.buildCommitInfo(target))
945+
946+
return rs.LoadLatestVersion()
950947
}
951948

952949
func (rs *Store) flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo) {

0 commit comments

Comments
 (0)