cosmos-sdk/x/upgrade/keeper/keeper_test.go

442 lines
13 KiB
Go

package keeper_test
import (
"context"
"path/filepath"
"testing"
cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1"
cmttypes "github.com/cometbft/cometbft/types"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/header"
coretesting "cosmossdk.io/core/testing"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/upgrade"
"cosmossdk.io/x/upgrade/keeper"
upgradetestutil "cosmossdk.io/x/upgrade/testutil"
"cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/baseapp"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
type KeeperTestSuite struct {
suite.Suite
key *storetypes.KVStoreKey
baseApp *baseapp.BaseApp
upgradeKeeper *keeper.Keeper
homeDir string
ctx sdk.Context
msgSrvr types.MsgServer
addrs []sdk.AccAddress
encodedAddrs []string
encodedAuthority string
encCfg moduletestutil.TestEncodingConfig
}
func (s *KeeperTestSuite) SetupTest() {
s.encCfg = moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, upgrade.AppModule{})
key := storetypes.NewKVStoreKey(types.StoreKey)
storeService := runtime.NewKVStoreService(key)
env := runtime.NewEnvironment(storeService, coretesting.NewNopLogger())
s.key = key
testCtx := testutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test"))
s.ctx = testCtx.Ctx.WithHeaderInfo(header.Info{Height: 10})
s.baseApp = baseapp.NewBaseApp(
"upgrade",
log.NewNopLogger(),
testCtx.DB,
s.encCfg.TxConfig.TxDecoder(),
)
s.baseApp.SetParamStore(&paramStore{params: cmttypes.DefaultConsensusParams().ToProto()})
s.baseApp.SetVersionModifier(newMockedVersionModifier(0))
appVersion, err := s.baseApp.AppVersion(context.Background())
s.Require().NoError(err)
s.Require().Equal(uint64(0), appVersion)
skipUpgradeHeights := make(map[int64]bool)
homeDir := filepath.Join(s.T().TempDir(), "x_upgrade_keeper_test")
ac := addresscodec.NewBech32Codec("cosmos")
authority, err := ac.BytesToString(authtypes.NewModuleAddress(types.GovModuleName))
s.Require().NoError(err)
s.encodedAuthority = authority
ctrl := gomock.NewController(s.T())
s.upgradeKeeper = keeper.NewKeeper(env, skipUpgradeHeights, s.encCfg.Codec, homeDir, s.baseApp, authority, upgradetestutil.NewMockConsensusKeeper(ctrl))
s.T().Log("home dir:", homeDir)
s.homeDir = homeDir
s.msgSrvr = keeper.NewMsgServerImpl(s.upgradeKeeper)
s.addrs = simtestutil.CreateIncrementalAccounts(1)
encodedAddr, err := ac.BytesToString(s.addrs[0].Bytes())
s.Require().NoError(err)
s.encodedAddrs = []string{encodedAddr}
}
func (s *KeeperTestSuite) TestReadUpgradeInfoFromDisk() {
// require no error when the upgrade info file does not exist
_, err := s.upgradeKeeper.ReadUpgradeInfoFromDisk()
s.Require().NoError(err)
expected := types.Plan{
Name: "test_upgrade",
Height: 100,
}
// create an upgrade info file
s.Require().NoError(s.upgradeKeeper.DumpUpgradeInfoToDisk(101, expected))
ui, err := s.upgradeKeeper.ReadUpgradeInfoFromDisk()
s.Require().NoError(err)
expected.Height = 101
s.Require().Equal(expected, ui)
// create invalid upgrade plan (with empty name)
expected.Name = ""
s.Require().NoError(s.upgradeKeeper.DumpUpgradeInfoToDisk(101, expected))
_, err = s.upgradeKeeper.ReadUpgradeInfoFromDisk()
s.Require().ErrorContains(err, "name cannot be empty: invalid request")
}
func (s *KeeperTestSuite) TestScheduleUpgrade() {
cases := []struct {
name string
plan types.Plan
setup func()
expPass bool
}{
{
name: "successful height schedule",
plan: types.Plan{
Name: "all-good",
Info: "some text here",
Height: 123450000,
},
setup: func() {},
expPass: true,
},
{
name: "successful overwrite",
plan: types.Plan{
Name: "all-good",
Info: "some text here",
Height: 123450000,
},
setup: func() {
err := s.upgradeKeeper.ScheduleUpgrade(s.ctx, types.Plan{
Name: "alt-good",
Info: "new text here",
Height: 543210000,
})
s.Require().NoError(err)
},
expPass: true,
},
{
name: "unsuccessful schedule: invalid plan",
plan: types.Plan{
Height: 123450000,
},
setup: func() {},
expPass: false,
},
{
name: "unsuccessful height schedule: due date in past",
plan: types.Plan{
Name: "all-good",
Info: "some text here",
Height: 1,
},
setup: func() {},
expPass: false,
},
{
name: "unsuccessful schedule: schedule already executed",
plan: types.Plan{
Name: "all-good",
Info: "some text here",
Height: 123450000,
},
setup: func() {
s.upgradeKeeper.SetUpgradeHandler("all-good", func(ctx context.Context, plan types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
err := s.upgradeKeeper.ApplyUpgrade(s.ctx, types.Plan{
Name: "all-good",
Info: "some text here",
Height: 123450000,
})
s.Require().NoError(err)
},
expPass: false,
},
}
for _, tc := range cases {
s.Run(tc.name, func() {
// reset suite
s.SetupTest()
// setup test case
tc.setup()
err := s.upgradeKeeper.ScheduleUpgrade(s.ctx, tc.plan)
if tc.expPass {
s.Require().NoError(err, "valid test case failed")
} else {
s.Require().Error(err, "invalid test case passed")
}
})
}
}
func (s *KeeperTestSuite) TestSetUpgradedClient() {
cs := []byte("IBC client state")
cases := []struct {
name string
height int64
setup func()
exists bool
}{
{
name: "no upgraded client exists",
height: 10,
setup: func() {},
exists: false,
},
{
name: "success",
height: 10,
setup: func() {
err := s.upgradeKeeper.SetUpgradedClient(s.ctx, 10, cs)
s.Require().NoError(err)
},
exists: true,
},
}
for _, tc := range cases {
// reset suite
s.SetupTest()
// setup test case
tc.setup()
gotCs, err := s.upgradeKeeper.GetUpgradedClient(s.ctx, tc.height)
if tc.exists {
s.Require().Equal(cs, gotCs, "valid case: %s did not retrieve correct client state", tc.name)
s.Require().NoError(err, "valid case: %s did not retrieve client state", tc.name)
} else {
s.Require().Nil(gotCs, "invalid case: %s retrieved valid client state", tc.name)
s.Require().Error(err, "invalid case: %s retrieved valid client state", tc.name)
}
}
}
func (s *KeeperTestSuite) TestIsSkipHeight() {
var skipOne int64 = 9
ok := s.upgradeKeeper.IsSkipHeight(11)
s.Require().False(ok)
skip := map[int64]bool{skipOne: true}
storeService := runtime.NewKVStoreService(s.key)
env := runtime.NewEnvironment(storeService, coretesting.NewNopLogger())
ctrl := gomock.NewController(s.T())
upgradeKeeper := keeper.NewKeeper(env, skip, s.encCfg.Codec, s.T().TempDir(), s.baseApp, s.encodedAuthority, upgradetestutil.NewMockConsensusKeeper(ctrl))
s.Require().True(upgradeKeeper.IsSkipHeight(9))
s.Require().False(upgradeKeeper.IsSkipHeight(10))
}
func (s *KeeperTestSuite) TestUpgradedConsensusState() {
cs := []byte("IBC consensus state")
s.Require().NoError(s.upgradeKeeper.SetUpgradedConsensusState(s.ctx, 10, cs))
bz, err := s.upgradeKeeper.GetUpgradedConsensusState(s.ctx, 10)
s.Require().Equal(cs, bz)
s.Require().NoError(err)
}
func (s *KeeperTestSuite) TestDowngradeVerified() {
s.upgradeKeeper.SetDowngradeVerified(true)
ok := s.upgradeKeeper.DowngradeVerified()
s.Require().True(ok)
}
// Test that the protocol version successfully increments after an
// upgrade and is successfully set on BaseApp's appVersion.
func (s *KeeperTestSuite) TestIncrementProtocolVersion() {
oldProtocolVersion, err := s.baseApp.AppVersion(context.Background())
s.Require().NoError(err)
res := s.upgradeKeeper.HasHandler("dummy")
s.Require().False(res)
dummyPlan := types.Plan{
Name: "dummy",
Info: "some text here",
Height: 100,
}
err = s.upgradeKeeper.ApplyUpgrade(s.ctx, dummyPlan)
s.Require().EqualError(err, "ApplyUpgrade should never be called without first checking HasHandler")
s.upgradeKeeper.SetUpgradeHandler("dummy", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
err = s.upgradeKeeper.ApplyUpgrade(s.ctx, dummyPlan)
s.Require().NoError(err)
upgradedProtocolVersion, err := s.baseApp.AppVersion(s.ctx)
s.Require().NoError(err)
s.Require().Equal(oldProtocolVersion+1, upgradedProtocolVersion)
}
// Tests that the underlying state of x/upgrade is set correctly after
// an upgrade.
func (s *KeeperTestSuite) TestMigrations() {
initialVM := appmodule.VersionMap{"bank": uint64(1)}
err := s.upgradeKeeper.SetModuleVersionMap(s.ctx, initialVM)
s.Require().NoError(err)
vmBefore, err := s.upgradeKeeper.GetModuleVersionMap(s.ctx)
s.Require().NoError(err)
s.upgradeKeeper.SetUpgradeHandler("dummy", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
// simulate upgrading the bank module
vm["bank"]++
return vm, nil
})
dummyPlan := types.Plan{
Name: "dummy",
Info: "some text here",
Height: 123450000,
}
s.Require().NoError(s.upgradeKeeper.ApplyUpgrade(s.ctx, dummyPlan))
vm, err := s.upgradeKeeper.GetModuleVersionMap(s.ctx)
s.Require().Equal(vmBefore["bank"]+1, vm["bank"])
s.Require().NoError(err)
}
func (s *KeeperTestSuite) TestLastCompletedUpgrade() {
keeper := s.upgradeKeeper
require := s.Require()
s.T().Log("verify empty name if applied upgrades are empty")
name, height, err := keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("", name)
require.Equal(int64(0), height)
require.NoError(err)
keeper.SetUpgradeHandler("test0", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
require.True(keeper.HasHandler("test0"))
err = keeper.ApplyUpgrade(s.ctx, types.Plan{
Name: "test0",
Height: 10,
})
require.NoError(err)
s.T().Log("verify valid upgrade name and height")
name, height, err = keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("test0", name)
require.Equal(int64(10), height)
require.NoError(err)
keeper.SetUpgradeHandler("test1", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
newCtx := s.ctx.WithHeaderInfo(header.Info{Height: 15})
err = keeper.ApplyUpgrade(newCtx, types.Plan{
Name: "test1",
Height: 15,
})
require.NoError(err)
s.T().Log("verify valid upgrade name and height with multiple upgrades")
name, height, err = keeper.GetLastCompletedUpgrade(newCtx)
require.Equal("test1", name)
require.Equal(int64(15), height)
require.NoError(err)
}
// 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.upgradeKeeper
require := s.Require()
// apply first upgrade
keeper.SetUpgradeHandler("test-v0.9", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
err := keeper.ApplyUpgrade(s.ctx, types.Plan{
Name: "test-v0.9",
Height: 10,
})
require.NoError(err)
name, height, err := keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("test-v0.9", name)
require.Equal(int64(10), height)
require.NoError(err)
// apply second upgrade
keeper.SetUpgradeHandler("test-v0.10", func(_ context.Context, _ types.Plan, vm appmodule.VersionMap) (appmodule.VersionMap, error) {
return vm, nil
})
newCtx := s.ctx.WithHeaderInfo(header.Info{Height: 15})
err = keeper.ApplyUpgrade(newCtx, types.Plan{
Name: "test-v0.10",
Height: 15,
})
require.NoError(err)
name, height, err = keeper.GetLastCompletedUpgrade(newCtx)
require.Equal("test-v0.10", name)
require.Equal(int64(15), height)
require.NoError(err)
}
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
type paramStore struct {
params cmtproto.ConsensusParams
}
var _ baseapp.ParamStore = (*paramStore)(nil)
func (ps *paramStore) Set(_ context.Context, value cmtproto.ConsensusParams) error {
ps.params = value
return nil
}
func (ps paramStore) Has(_ context.Context) (bool, error) {
return true, nil
}
func (ps paramStore) Get(_ context.Context) (cmtproto.ConsensusParams, error) {
return ps.params, nil
}