Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: invalid return value from keeper GetLastCompleteUpgrade - breaking change version #11800

Merged
44 changes: 28 additions & 16 deletions x/upgrade/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
storetypes "github.com/cosmos/cosmos-sdk/store/types"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/internal/conv"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -239,28 +238,43 @@ func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]
func (k Keeper) GetLastCompletedUpgrade(ctx sdk.Context) (string, int64) {
iter := sdk.KVStoreReversePrefixIterator(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
defer iter.Close()

if iter.Valid() {
return parseDoneKey(iter.Key()), int64(binary.BigEndian.Uint64(iter.Value()))
return parseDoneKey(iter.Key())
}

return "", 0
}

// parseDoneKey - split upgrade name from the done key
func parseDoneKey(key []byte) string {
kv.AssertKeyAtLeastLength(key, 2)
return string(key[1:])
// parseDoneKey - split height and upgrade name from the done key
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
func parseDoneKey(key []byte) (string, int64) {
// 1 byte for the DoneByte + 8 bytes height + at least 1 byte for the name
kv.AssertKeyAtLeastLength(key, 10)
height := binary.BigEndian.Uint64(key[1:])
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
return string(key[9:]), int64(height)
}

// encodeDoneKey - concatenate DoneByte, height and upgrade name to form the done key
func encodeDoneKey(name string, height int64) []byte {
key := make([]byte, 9+len(name))
daeMOn63 marked this conversation as resolved.
Show resolved Hide resolved
key[0] = types.DoneByte
binary.BigEndian.PutUint64(key[1:], uint64(height))
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
copy(key[9:], name)
return key
}

// GetDoneHeight returns the height at which the given upgrade was executed
func (k Keeper) GetDoneHeight(ctx sdk.Context, name string) int64 {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
bz := store.Get(conv.UnsafeStrToBytes(name))
if len(bz) == 0 {
return 0
}
iter := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
defer iter.Close()

return int64(binary.BigEndian.Uint64(bz))
for ; iter.Valid(); iter.Next() {
upgradeName, height := parseDoneKey(iter.Key())
if upgradeName == name {
return height
}
}
return 0
}

// ClearIBCState clears any planned IBC state
Expand Down Expand Up @@ -303,10 +317,8 @@ func (k Keeper) GetUpgradePlan(ctx sdk.Context) (plan types.Plan, havePlan bool)

// setDone marks this upgrade name as being done so the name can't be reused accidentally
func (k Keeper) setDone(ctx sdk.Context, name string) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, uint64(ctx.BlockHeight()))
store.Set([]byte(name), bz)
store := ctx.KVStore(k.storeKey)
store.Set(encodeDoneKey(name, ctx.BlockHeight()), []byte{1})
}

// HasHandler returns true iff there is a handler registered for this name
Expand Down
34 changes: 34 additions & 0 deletions x/upgrade/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,40 @@ func (s *KeeperTestSuite) TestLastCompletedUpgrade() {
require.Equal(int64(15), height)
}

func (s *KeeperTestSuite) TestLastCompletedUpgradeOrdering() {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
keeper := s.app.UpgradeKeeper
require := s.Require()

// apply first upgrade
keeper.SetUpgradeHandler("test-v0.9", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
})

keeper.ApplyUpgrade(s.ctx, types.Plan{
Name: "test-v0.9",
Height: 10,
})

name, height := keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("test-v0.9", name)
require.Equal(int64(10), height)

// apply second upgrade
keeper.SetUpgradeHandler("test-v0.10", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
})

newCtx := s.ctx.WithBlockHeight(15)
keeper.ApplyUpgrade(newCtx, types.Plan{
Name: "test-v0.10",
Height: 15,
})

name, height = keeper.GetLastCompletedUpgrade(newCtx)
require.Equal("test-v0.10", name)
require.Equal(int64(15), height)
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
43 changes: 43 additions & 0 deletions x/upgrade/keeper/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package keeper

import (
"encoding/binary"

"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
)

// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper Keeper
}

// NewMigrator returns a new Migrator.
func NewMigrator(keeper Keeper) Migrator {
return Migrator{keeper: keeper}
}

// Migrate1to2 migrates from version 1 to 2.
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
return migrateDoneUpgradeKeys(ctx, m.keeper.storeKey)
}

func migrateDoneUpgradeKeys(ctx sdk.Context, storeKey storetypes.StoreKey) error {
store := ctx.KVStore(storeKey)
oldDoneStore := prefix.NewStore(store, []byte{types.DoneByte})
oldDoneStoreIter := oldDoneStore.Iterator(nil, nil)
defer oldDoneStoreIter.Close()

for ; oldDoneStoreIter.Valid(); oldDoneStoreIter.Next() {
oldKey := oldDoneStoreIter.Key()
upgradeName := string(oldKey)
upgradeHeight := int64(binary.BigEndian.Uint64(oldDoneStoreIter.Value()))
newKey := encodeDoneKey(upgradeName, upgradeHeight)

store.Set(newKey, []byte{1})
oldDoneStore.Delete(oldKey)
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}
59 changes: 59 additions & 0 deletions x/upgrade/keeper/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package keeper

import (
"encoding/binary"
"testing"

"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
"github.com/stretchr/testify/require"
)

type storedUpgrade struct {
name string
height int64
}

func encodeOldDoneKey(upgrade storedUpgrade) []byte {
return append([]byte{types.DoneByte}, []byte(upgrade.name)...)
}

func TestMigrateDoneUpgradeKeys(t *testing.T) {
upgradeKey := sdk.NewKVStoreKey("upgrade")
ctx := testutil.DefaultContext(upgradeKey, sdk.NewTransientStoreKey("transient_test"))
store := ctx.KVStore(upgradeKey)

testCases := []struct {
name string
upgrades []storedUpgrade
}{
{
name: "valid upgrades",
upgrades: []storedUpgrade{
{name: "some-other-upgrade", height: 1},
{name: "test02", height: 2},
{name: "test01", height: 3},
},
},
}

for _, tc := range testCases {
for _, upgrade := range tc.upgrades {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, uint64(upgrade.height))
oldKey := encodeOldDoneKey(upgrade)
store.Set(oldKey, bz)
}

err := migrateDoneUpgradeKeys(ctx, upgradeKey)
require.NoError(t, err)

for _, upgrade := range tc.upgrades {
newKey := encodeDoneKey(upgrade.name, upgrade.height)
oldKey := encodeOldDoneKey(upgrade)
require.Nil(t, store.Get(oldKey))
require.Equal(t, []byte{1}, store.Get(newKey))
}
}
}
5 changes: 4 additions & 1 deletion x/upgrade/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd
func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
types.RegisterQueryServer(cfg.QueryServer(), am.keeper)

m := keeper.NewMigrator(am.keeper)
cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2)
}

// InitGenesis is ignored, no sense in serializing future upgrades
Expand All @@ -124,7 +127,7 @@ func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMe
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 1 }
func (AppModule) ConsensusVersion() uint64 { return 2 }
daeMOn63 marked this conversation as resolved.
Show resolved Hide resolved

// BeginBlock calls the upgrade module hooks
//
Expand Down