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

This commit is contained in:
daeMOn 2022-05-02 16:23:27 +02:00 committed by GitHub
parent 76895e61c0
commit b6c3756c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 17 deletions

View File

@ -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

View File

@ -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"
@ -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
@ -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

View File

@ -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))
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -22,6 +22,10 @@ func init() {
types.RegisterLegacyAminoCodec(codec.NewLegacyAmino())
}
const (
consensusVersion uint64 = 2
)
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
@ -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
@ -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
//