Skip to content

Commit

Permalink
fix!: invalid return value from keeper GetLastCompleteUpgrade - break…
Browse files Browse the repository at this point in the history
…ing change version (#11800)
  • Loading branch information
daeMOn63 authored May 2, 2022
1 parent 76895e6 commit b6c3756
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### State Machine Breaking

* (x/upgrade) [\#11800](https://github.com/cosmos/cosmos-sdk/pull/11800) Fix `GetLastCompleteUpgrade` to properly return the latest upgrade.
* [\#10564](https://github.com/cosmos/cosmos-sdk/pull/10564) Fix bug when updating allowance inside AllowedMsgAllowance
* (x/auth)[\#9596](https://github.com/cosmos/cosmos-sdk/pull/9596) Enable creating periodic vesting accounts with a transactions instead of requiring them to be created in genesis.
* (x/bank) [\#9611](https://github.com/cosmos/cosmos-sdk/pull/9611) Introduce a new index to act as a reverse index between a denomination and address allowing to query for
Expand Down
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 upgrade name and height from the done key
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:9])
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)) // 9 = donebyte + uint64 len
key[0] = types.DoneByte
binary.BigEndian.PutUint64(key[1:9], uint64(height))
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
36 changes: 36 additions & 0 deletions x/upgrade/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,42 @@ func (s *KeeperTestSuite) TestLastCompletedUpgrade() {
require.Equal(int64(15), height)
}

// This test ensures that `GetLastDoneUpgrade` always returns the last upgrade according to the block height
// it was executed at, rather than using an ordering based on upgrade names.
func (s *KeeperTestSuite) TestLastCompletedUpgradeOrdering() {
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)
}
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))
}
}
}
9 changes: 8 additions & 1 deletion x/upgrade/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func init() {
types.RegisterLegacyAminoCodec(codec.NewLegacyAmino())
}

const (
consensusVersion uint64 = 2
)

var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
Expand Down Expand Up @@ -95,6 +99,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 @@ -118,7 +125,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 consensusVersion }

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

0 comments on commit b6c3756

Please sign in to comment.