Skip to content

Commit 46d7d87

Browse files
committed
cli: add --unsafe-remove-modules flag to Rollback (#549)
* rollback command accepts list of store keys names to forcibly delete this is useful for rolling back an upgrade that adds modules. rollbacks are performed by loading & committing the previous version. without this new functionality, the rollback will fail because no store version will exist for modules added during the upgrade. to properly rollback the state, pass in a list of the added module names and they will be completely removed before the rollback of pre-existing modules takes place: ``` chain rollback --unsafe-remove-modules mynewmodule,othernewmodule ```
1 parent 0fcbbd6 commit 46d7d87

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

server/rollback.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import (
88

99
"github.com/cosmos/cosmos-sdk/client/flags"
1010
"github.com/cosmos/cosmos-sdk/server/types"
11+
"github.com/cosmos/cosmos-sdk/store/rootmulti"
1112
)
1213

1314
// NewRollbackCmd creates a command to rollback tendermint and multistore state by one height.
1415
func NewRollbackCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
1516
var removeBlock bool
17+
moduleKeysToDelete := make([]string, 0)
1618

1719
cmd := &cobra.Command{
1820
Use: "rollback",
@@ -39,6 +41,17 @@ application.
3941
if err != nil {
4042
return fmt.Errorf("failed to rollback tendermint state: %w", err)
4143
}
44+
45+
// for rolling back upgrades that add modules, we must first forcibly delete those.
46+
// otherwise, the rollback will panic because no version of new modules will exist.
47+
store := app.CommitMultiStore().(*rootmulti.Store)
48+
for _, key := range moduleKeysToDelete {
49+
fmt.Printf("deleting latest version of KVStore with key %s\n", key)
50+
if err := store.DeleteLatestVersion(key); err != nil {
51+
return err
52+
}
53+
}
54+
4255
// rollback the multistore
4356

4457
if err := app.CommitMultiStore().RollbackToVersion(height); err != nil {
@@ -52,5 +65,6 @@ application.
5265

5366
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
5467
cmd.Flags().BoolVar(&removeBlock, "hard", false, "remove last block as well as state")
68+
cmd.Flags().StringSliceVar(&moduleKeysToDelete, "unsafe-remove-modules", []string{}, "force delete KV stores with the provided prefix. useful for rolling back an upgrade that adds a module")
5569
return cmd
5670
}

store/rootmulti/store.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,68 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo {
10111011
}
10121012
}
10131013

1014+
// DeleteLatestVersion finds a store with the given key name and deletes its latest version.
1015+
// The store is deregistered from the rootmulti store. Calls to buildCommitInfo will not include it.
1016+
// For stores with IAVL types, the deletion is written to the disk.
1017+
// This is a destructive operation to be used with caution.
1018+
// The reason it was added was to allow for rollbacks of upgrades that add modules.
1019+
// Stores that do not exist in the version prior to upgrade can be forcibly deleted
1020+
// before calling Rollback()
1021+
func (rs *Store) DeleteLatestVersion(keyName string) error {
1022+
ver := GetLatestVersion(rs.db)
1023+
if ver == 0 {
1024+
return fmt.Errorf("unable to delete KVStore with key name %s, latest version is 0", keyName)
1025+
}
1026+
1027+
// Find the store key with the provided name
1028+
var key types.StoreKey = nil
1029+
for k := range rs.storesParams {
1030+
if k.Name() == keyName {
1031+
key = k
1032+
break
1033+
}
1034+
}
1035+
if key == nil {
1036+
return fmt.Errorf("no store found with key name %s", keyName)
1037+
}
1038+
1039+
// Get the KVStore for that key
1040+
cInfo, err := rs.GetCommitInfo(ver)
1041+
if err != nil {
1042+
return err
1043+
}
1044+
infos := make(map[string]types.StoreInfo)
1045+
for _, storeInfo := range cInfo.StoreInfos {
1046+
infos[storeInfo.Name] = storeInfo
1047+
}
1048+
commitID := rs.getCommitID(infos, key.Name())
1049+
store, err := rs.loadCommitStoreFromParams(key, commitID, rs.storesParams[key])
1050+
if err != nil {
1051+
return errors.Wrap(err, "failed to load store")
1052+
}
1053+
1054+
rs.logger.Debug("deleting KVStore", "key", key.Name(), "latest version", ver)
1055+
1056+
// for IAVL stores, commit the deletion of the latest version to disk.
1057+
if store.GetStoreType() == types.StoreTypeIAVL {
1058+
// unwrap the caching layer
1059+
store = rs.GetCommitKVStore(key)
1060+
if err := store.(*iavl.Store).DeleteVersions(ver); err != nil {
1061+
return errors.Wrapf(err, "failed to delete version %d of %s store", ver, key.Name())
1062+
}
1063+
}
1064+
1065+
// deregister store from the rootmulti store
1066+
// Any future buildCommitInfo will no longer include the store.
1067+
if _, ok := rs.stores[key]; ok {
1068+
delete(rs.stores, key)
1069+
delete(rs.storesParams, key)
1070+
delete(rs.keysByName, key.Name())
1071+
}
1072+
1073+
return nil
1074+
}
1075+
10141076
// RollbackToVersion delete the versions after `target` and update the latest version.
10151077
func (rs *Store) RollbackToVersion(target int64) error {
10161078
if target <= 0 {

0 commit comments

Comments
 (0)