diff --git a/CHANGELOG.md b/CHANGELOG.md index 9799793678..526d82baae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Some PRs from v2.0.0 may reappear from other releases below. This is due to the ## PRs included in v2.0.0 +* (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) * (deps) Bump github.com/cosmos/ibc-go/v4 from 4.4.0 to 4.4.2 [#982](https://github.com/cosmos/interchain-security/pull/982) * (fix) partially revert key assignment type safety PR [#980](https://github.com/cosmos/interchain-security/pull/980) * (fix) Remove panics on failure to send IBC packets [#876](https://github.com/cosmos/interchain-security/pull/876) diff --git a/app/consumer-democracy/app.go b/app/consumer-democracy/app.go index 13c5f50dae..ee299d6221 100644 --- a/app/consumer-democracy/app.go +++ b/app/consumer-democracy/app.go @@ -117,7 +117,7 @@ import ( const ( AppName = "interchain-security-cd" - upgradeName = "v07-Theta" // arbitrary name, define your own appropriately named upgrade + upgradeName = "ics-v1-to-v2" // arbitrary name, define your own appropriately named upgrade AccountAddressPrefix = "cosmos" ) @@ -441,7 +441,7 @@ func New( // register slashing module StakingHooks to the consumer keeper app.ConsumerKeeper = *app.ConsumerKeeper.SetHooks(app.SlashingKeeper.Hooks()) - consumerModule := consumer.NewAppModule(app.ConsumerKeeper) + consumerModule := consumer.NewAppModule(app.ConsumerKeeper, app.GetSubspace(consumertypes.ModuleName)) app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, @@ -628,6 +628,8 @@ func New( app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) + // Note this upgrade handler is just an example and may not be exactly what you need to implement. + // See https://docs.cosmos.network/v0.45/building-modules/upgrade.html app.UpgradeKeeper.SetUpgradeHandler( upgradeName, func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { diff --git a/app/consumer/app.go b/app/consumer/app.go index c100a0385e..343349b040 100644 --- a/app/consumer/app.go +++ b/app/consumer/app.go @@ -90,7 +90,7 @@ import ( const ( AppName = "interchain-security-c" - upgradeName = "v07-Theta" + upgradeName = "ics-v1-to-v2" AccountAddressPrefix = "cosmos" ) @@ -343,7 +343,7 @@ func New( // register slashing module Slashing hooks to the consumer keeper app.ConsumerKeeper = *app.ConsumerKeeper.SetHooks(app.SlashingKeeper.Hooks()) - consumerModule := ibcconsumer.NewAppModule(app.ConsumerKeeper) + consumerModule := ibcconsumer.NewAppModule(app.ConsumerKeeper, app.GetSubspace(ibcconsumertypes.ModuleName)) app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, @@ -510,6 +510,8 @@ func New( app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) + // Note this upgrade handler is just an example and may not be exactly what you need to implement. + // See https://docs.cosmos.network/v0.45/building-modules/upgrade.html app.UpgradeKeeper.SetUpgradeHandler( upgradeName, func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { diff --git a/app/provider/app.go b/app/provider/app.go index 20aa133dee..94719cf2c6 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -109,7 +109,7 @@ import ( const ( AppName = "interchain-security-p" - upgradeName = "v07-Theta" + upgradeName = "ics-v1-to-v2" AccountAddressPrefix = "cosmos" ) @@ -415,7 +415,7 @@ func New( authtypes.FeeCollectorName, ) - providerModule := ibcprovider.NewAppModule(&app.ProviderKeeper) + providerModule := ibcprovider.NewAppModule(&app.ProviderKeeper, app.GetSubspace(providertypes.ModuleName)) // register the proposal types govRouter := govtypes.NewRouter() @@ -616,6 +616,8 @@ func New( app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) + // Note this upgrade handler is just an example and may not be exactly what you need to implement. + // See https://docs.cosmos.network/v0.45/building-modules/upgrade.html app.UpgradeKeeper.SetUpgradeHandler( upgradeName, func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { diff --git a/x/ccv/consumer/ibc_module_test.go b/x/ccv/consumer/ibc_module_test.go index fcc82d6d2f..fc6c3d2200 100644 --- a/x/ccv/consumer/ibc_module_test.go +++ b/x/ccv/consumer/ibc_module_test.go @@ -121,9 +121,10 @@ func TestOnChanOpenInit(t *testing.T) { for _, tc := range testCases { // Common setup + keeperParams := testkeeper.NewInMemKeeperParams(t) consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) - consumerModule := consumer.NewAppModule(consumerKeeper) + t, keeperParams) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) consumerKeeper.SetPort(ctx, ccv.ConsumerPortID) consumerKeeper.SetProviderClientID(ctx, "clientIDToProvider") @@ -172,10 +173,11 @@ func TestOnChanOpenInit(t *testing.T) { // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-ccf-cotry1 // Spec tag: [CCV-CCF-COTRY.1] func TestOnChanOpenTry(t *testing.T) { - consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) // No external keeper methods should be called defer ctrl.Finish() - consumerModule := consumer.NewAppModule(consumerKeeper) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) // OnOpenTry must error even with correct arguments _, err := consumerModule.OnChanOpenTry( @@ -266,9 +268,10 @@ func TestOnChanOpenAck(t *testing.T) { for _, tc := range testCases { // Common setup + keeperParams := testkeeper.NewInMemKeeperParams(t) consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) - consumerModule := consumer.NewAppModule(consumerKeeper) + t, keeperParams) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) // Instantiate valid params as default. Individual test cases mutate these as needed. params := params{ @@ -316,9 +319,10 @@ func TestOnChanOpenAck(t *testing.T) { // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-ccf-coconfirm1 // Spec tag: [CCV-CCF-COCONFIRM.1] func TestOnChanOpenConfirm(t *testing.T) { - consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) defer ctrl.Finish() - consumerModule := consumer.NewAppModule(consumerKeeper) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) err := consumerModule.OnChanOpenConfirm(ctx, ccv.ConsumerPortID, "channel-1") require.Error(t, err, "OnChanOpenConfirm callback must error on consumer chain") @@ -356,8 +360,9 @@ func TestOnChanCloseInit(t *testing.T) { } for _, tc := range testCases { - consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - consumerModule := consumer.NewAppModule(consumerKeeper) + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) if tc.establishedProviderExists { consumerKeeper.SetProviderChannel(ctx, "provider") @@ -379,12 +384,13 @@ func TestOnChanCloseInit(t *testing.T) { // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-ccconfirm1// Spec tag: [CCV-CCF-CCINIT.1] // Spec tag: [CCV-PCF-CCCONFIRM.1] func TestOnChanCloseConfirm(t *testing.T) { - consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) // No external keeper methods should be called defer ctrl.Finish() - consumerModule := consumer.NewAppModule(consumerKeeper) + consumerModule := consumer.NewAppModule(consumerKeeper, *keeperParams.ParamsSubspace) // Nothing happens, no error returned err := consumerModule.OnChanCloseConfirm(ctx, "portID", "channelID") diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index ec89052ac0..54c1cd5577 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -92,6 +92,12 @@ func (k *Keeper) SetStandaloneStakingKeeper(sk ccv.StakingKeeper) { k.standaloneStakingKeeper = sk } +// SetParamSpace sets the param space for the consumer keeper. +// Note: this is only used for testing! +func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { + k.paramStore = ps +} + // Validates that the consumer keeper is initialized with non-zero and // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { diff --git a/x/ccv/consumer/keeper/migration.go b/x/ccv/consumer/keeper/migration.go new file mode 100644 index 0000000000..a44b89404b --- /dev/null +++ b/x/ccv/consumer/keeper/migration.go @@ -0,0 +1,79 @@ +package keeper + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" + ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + ccvConsumerKeeper Keeper + ccvConsumerParamSpace paramtypes.Subspace +} + +// NewMigrator returns a new Migrator. +func NewMigrator(ccvConsumerKeeper Keeper, ccvConsumerParamSpace paramtypes.Subspace) Migrator { + return Migrator{ccvConsumerKeeper: ccvConsumerKeeper, ccvConsumerParamSpace: ccvConsumerParamSpace} +} + +// Note: If migrating from v1.2.0-multiden to v2.0.0, there are no migrations required. +// This is due to the fact that the former version includes both of: +// - https://github.com/cosmos/interchain-security/commit/54e9852d3c89a2513cd0170a56c6eec894fc878d +// - https://github.com/cosmos/interchain-security/pull/833 +// both of which handle the introduction of new params. + +// Migratev1Tov2 migrates a consumer from v1.0.0 to v2.0.0. +func (m Migrator) Migratev1Tov2(ctx sdk.Context) error { + // Migrate params + MigrateParamsv1Tov2(ctx, m.ccvConsumerParamSpace) + + return nil +} + +// MigrateParamsv1Tov2 migrates the consumer CCV module params from v1.0.0 to v2.0.0, +// setting default values for new params. +func MigrateParamsv1Tov2(ctx sdk.Context, paramsSubspace paramtypes.Subspace) { + // Get old params + var enabled bool + paramsSubspace.Get(ctx, consumertypes.KeyEnabled, &enabled) + var blocksPerDistributionTransmission int64 + paramsSubspace.Get(ctx, consumertypes.KeyBlocksPerDistributionTransmission, &blocksPerDistributionTransmission) + var distributionTransmissionChannel string + paramsSubspace.Get(ctx, consumertypes.KeyDistributionTransmissionChannel, &distributionTransmissionChannel) + var providerFeePoolAddrStr string + paramsSubspace.Get(ctx, consumertypes.KeyProviderFeePoolAddrStr, &providerFeePoolAddrStr) + var ccvTimeoutPeriod time.Duration + paramsSubspace.Get(ctx, ccvtypes.KeyCCVTimeoutPeriod, &ccvTimeoutPeriod) + var transferTimeoutPeriod time.Duration + paramsSubspace.Get(ctx, consumertypes.KeyTransferTimeoutPeriod, &transferTimeoutPeriod) + var consumerRedistributionFrac string + paramsSubspace.Get(ctx, consumertypes.KeyConsumerRedistributionFrac, &consumerRedistributionFrac) + var historicalEntries int64 + paramsSubspace.Get(ctx, consumertypes.KeyHistoricalEntries, &historicalEntries) + var unbondingPeriod time.Duration + paramsSubspace.Get(ctx, consumertypes.KeyConsumerUnbondingPeriod, &unbondingPeriod) + + // Recycle old params, set new params to default values + defaultParams := consumertypes.DefaultParams() + newParams := consumertypes.NewParams( + enabled, + blocksPerDistributionTransmission, + distributionTransmissionChannel, + providerFeePoolAddrStr, + ccvTimeoutPeriod, + transferTimeoutPeriod, + consumerRedistributionFrac, + historicalEntries, + unbondingPeriod, + defaultParams.SoftOptOutThreshold, + defaultParams.RewardDenoms, + defaultParams.ProviderRewardDenoms, + ) + + // Persist new params + paramsSubspace.SetParamSet(ctx, &newParams) +} diff --git a/x/ccv/consumer/keeper/migration_test.go b/x/ccv/consumer/keeper/migration_test.go new file mode 100644 index 0000000000..ee14d2bcc8 --- /dev/null +++ b/x/ccv/consumer/keeper/migration_test.go @@ -0,0 +1,146 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + consumerkeeper "github.com/cosmos/interchain-security/v2/x/ccv/consumer/keeper" + consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" + ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmdb "github.com/tendermint/tm-db" +) + +func TestMigrateParamsv1Tov2(t *testing.T) { + // Setup raw store + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + storeKey := sdk.NewKVStoreKey(paramtypes.StoreKey) + memStoreKey := storetypes.NewMemoryStoreKey("mem_key") + stateStore.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(memStoreKey, sdk.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + require.NoError(t, stateStore.LoadLatestVersion()) + + // Create new empty subspace + subspace := paramtypes.NewSubspace(cdc, + codec.NewLegacyAmino(), + storeKey, + memStoreKey, + paramtypes.ModuleName, + ).WithKeyTable(v1KeyTable()) // Note that new param key table is set in keeper constructor + + // Set 9 params from v1.0.0 + subspace.Set(ctx, consumertypes.KeyEnabled, true) + subspace.Set(ctx, consumertypes.KeyBlocksPerDistributionTransmission, int64(10)) + subspace.Set(ctx, consumertypes.KeyDistributionTransmissionChannel, "channel-0") + subspace.Set(ctx, consumertypes.KeyProviderFeePoolAddrStr, "cosmos17p3erf5gv2436fd4vyjwmudakts563a497syuz") + subspace.Set(ctx, ccvtypes.KeyCCVTimeoutPeriod, time.Hour) + subspace.Set(ctx, consumertypes.KeyTransferTimeoutPeriod, time.Hour) + subspace.Set(ctx, consumertypes.KeyConsumerRedistributionFrac, "0.5") + subspace.Set(ctx, consumertypes.KeyHistoricalEntries, int64(10)) + subspace.Set(ctx, consumertypes.KeyConsumerUnbondingPeriod, time.Hour) + + // Confirm 3 new params cannot be set with old key table + require.Panics(t, func() { + subspace.Set(ctx, consumertypes.KeySoftOptOutThreshold, "0.05") + }) + require.Panics(t, func() { + subspace.Set(ctx, consumertypes.KeyRewardDenoms, []string{"untrn"}) + }) + require.Panics(t, func() { + subspace.Set(ctx, consumertypes.KeyProviderRewardDenoms, []string{"uatom"}) + }) + + // Now create new subspace, mocking an upgrade where app initialization happens again + subspace = paramtypes.NewSubspace(cdc, + codec.NewLegacyAmino(), + storeKey, + memStoreKey, + paramtypes.ModuleName, + ).WithKeyTable(consumertypes.ParamKeyTable()) // Use v2 key table, this would be set in keeper constructor upon app init + + // Run migration + consumerkeeper.MigrateParamsv1Tov2(ctx, subspace) + + // Use keeper to confirm params are set correctly + keeper := consumerkeeper.Keeper{} + keeper.SetParamSpace(ctx, subspace) + + params := keeper.GetParams(ctx) + require.Equal(t, true, params.Enabled) + require.Equal(t, int64(10), params.BlocksPerDistributionTransmission) + require.Equal(t, "channel-0", params.DistributionTransmissionChannel) + require.Equal(t, "cosmos17p3erf5gv2436fd4vyjwmudakts563a497syuz", params.ProviderFeePoolAddrStr) + require.Equal(t, time.Hour, params.CcvTimeoutPeriod) + require.Equal(t, time.Hour, params.TransferTimeoutPeriod) + require.Equal(t, "0.5", params.ConsumerRedistributionFraction) + require.Equal(t, int64(10), params.HistoricalEntries) + require.Equal(t, time.Hour, params.UnbondingPeriod) + // 3 new params are set to default values + require.Equal(t, "0.05", params.SoftOptOutThreshold) + require.Equal(t, []string(nil), params.RewardDenoms) + require.Equal(t, []string(nil), params.ProviderRewardDenoms) + + // Set new params to other values + params.SoftOptOutThreshold = "0.1" + params.RewardDenoms = []string{"untrn"} + params.ProviderRewardDenoms = []string{"uatom"} + keeper.SetParams(ctx, params) + + require.Equal(t, "0.1", keeper.GetSoftOptOutThreshold(ctx)) + require.Equal(t, []string{"untrn"}, keeper.GetRewardDenoms(ctx)) + require.Equal(t, []string{"uatom"}, keeper.GetProviderRewardDenoms(ctx)) +} + +// v1KeyTable is a copy of the ParamKeyTable method from v1.0.0 +func v1KeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&v1Params{}) +} + +// ParamSetPairs implements params.ParamSet for v1Params +func (p *v1Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(consumertypes.KeyEnabled, p.Enabled, ccvtypes.ValidateBool), + paramtypes.NewParamSetPair(consumertypes.KeyBlocksPerDistributionTransmission, + p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), + paramtypes.NewParamSetPair(consumertypes.KeyDistributionTransmissionChannel, + p.DistributionTransmissionChannel, consumertypes.ValidateDistributionTransmissionChannel), + paramtypes.NewParamSetPair(consumertypes.KeyProviderFeePoolAddrStr, + p.ProviderFeePoolAddrStr, consumertypes.ValidateProviderFeePoolAddrStr), + paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, + p.CcvTimeoutPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(consumertypes.KeyTransferTimeoutPeriod, + p.TransferTimeoutPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(consumertypes.KeyConsumerRedistributionFrac, + p.ConsumerRedistributionFraction, ccvtypes.ValidateStringFraction), + paramtypes.NewParamSetPair(consumertypes.KeyHistoricalEntries, + p.HistoricalEntries, ccvtypes.ValidatePositiveInt64), + paramtypes.NewParamSetPair(consumertypes.KeyConsumerUnbondingPeriod, + p.UnbondingPeriod, ccvtypes.ValidateDuration), + } +} + +// v1Params is a copy of the pb generated Params struct from v1.0.0 +type v1Params struct { + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + BlocksPerDistributionTransmission int64 `protobuf:"varint,2,opt,name=blocks_per_distribution_transmission,json=blocksPerDistributionTransmission,proto3" json:"blocks_per_distribution_transmission,omitempty"` + DistributionTransmissionChannel string `protobuf:"bytes,3,opt,name=distribution_transmission_channel,json=distributionTransmissionChannel,proto3" json:"distribution_transmission_channel,omitempty"` + ProviderFeePoolAddrStr string `protobuf:"bytes,4,opt,name=provider_fee_pool_addr_str,json=providerFeePoolAddrStr,proto3" json:"provider_fee_pool_addr_str,omitempty"` + CcvTimeoutPeriod time.Duration `protobuf:"bytes,5,opt,name=ccv_timeout_period,json=ccvTimeoutPeriod,proto3,stdduration" json:"ccv_timeout_period"` + TransferTimeoutPeriod time.Duration `protobuf:"bytes,6,opt,name=transfer_timeout_period,json=transferTimeoutPeriod,proto3,stdduration" json:"transfer_timeout_period"` + ConsumerRedistributionFraction string `protobuf:"bytes,7,opt,name=consumer_redistribution_fraction,json=consumerRedistributionFraction,proto3" json:"consumer_redistribution_fraction,omitempty"` + HistoricalEntries int64 `protobuf:"varint,8,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty"` + UnbondingPeriod time.Duration `protobuf:"bytes,9,opt,name=unbonding_period,json=unbondingPeriod,proto3,stdduration" json:"unbonding_period"` +} diff --git a/x/ccv/consumer/module.go b/x/ccv/consumer/module.go index 444b255156..61d62293f2 100644 --- a/x/ccv/consumer/module.go +++ b/x/ccv/consumer/module.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -91,13 +92,15 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { // AppModule represents the AppModule for this module type AppModule struct { AppModuleBasic - keeper keeper.Keeper + keeper keeper.Keeper + paramSpace paramtypes.Subspace } // NewAppModule creates a new consumer module -func NewAppModule(k keeper.Keeper) AppModule { +func NewAppModule(k keeper.Keeper, paramSpace paramtypes.Subspace) AppModule { return AppModule{ - keeper: k, + keeper: k, + paramSpace: paramSpace, } } @@ -122,9 +125,13 @@ func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { } // RegisterServices registers module services. -// TODO func (am AppModule) RegisterServices(cfg module.Configurator) { consumertypes.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + m := keeper.NewMigrator(am.keeper, am.paramSpace) + if err := cfg.RegisterMigration(consumertypes.ModuleName, 1, m.Migratev1Tov2); err != nil { + panic(fmt.Sprintf("failed to register migrator: %s", err)) + } } // InitGenesis performs genesis initialization for the consumer module. It returns @@ -143,7 +150,14 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements AppModule/ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 1 } +func (AppModule) ConsensusVersion() uint64 { + // Note that v1.0.0 consumers should technically be on a different consensus version + // than v1.2.0-multiden and v2.0.0. However, Neutron was the first consumer to launch + // in prod, and they've started on v1.2.0-multiden (which has a ConsensusVersion of 1). + // + // v1.2.0-multiden and v2.0.0 are consensus compatible, so they need return the same ConsensusVersion of 1. + return 1 +} // BeginBlock implements the AppModule interface // Set the VSC ID for the subsequent block to the same value as the current block diff --git a/x/ccv/consumer/types/params.go b/x/ccv/consumer/types/params.go index 5f7b73eb57..509e3725b4 100644 --- a/x/ccv/consumer/types/params.go +++ b/x/ccv/consumer/types/params.go @@ -113,10 +113,10 @@ func (p Params) Validate() error { if err := ccvtypes.ValidatePositiveInt64(p.BlocksPerDistributionTransmission); err != nil { return err } - if err := validateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { + if err := ValidateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { return err } - if err := validateProviderFeePoolAddrStr(p.ProviderFeePoolAddrStr); err != nil { + if err := ValidateProviderFeePoolAddrStr(p.ProviderFeePoolAddrStr); err != nil { return err } if err := ccvtypes.ValidateDuration(p.CcvTimeoutPeriod); err != nil { @@ -134,13 +134,13 @@ func (p Params) Validate() error { if err := ccvtypes.ValidateDuration(p.UnbondingPeriod); err != nil { return err } - if err := validateSoftOptOutThreshold(p.SoftOptOutThreshold); err != nil { + if err := ValidateSoftOptOutThreshold(p.SoftOptOutThreshold); err != nil { return err } - if err := validateDenoms(p.RewardDenoms); err != nil { + if err := ValidateDenoms(p.RewardDenoms); err != nil { return err } - if err := validateDenoms(p.ProviderRewardDenoms); err != nil { + if err := ValidateDenoms(p.ProviderRewardDenoms); err != nil { return err } return nil @@ -153,9 +153,9 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyBlocksPerDistributionTransmission, p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), paramtypes.NewParamSetPair(KeyDistributionTransmissionChannel, - p.DistributionTransmissionChannel, validateDistributionTransmissionChannel), + p.DistributionTransmissionChannel, ValidateDistributionTransmissionChannel), paramtypes.NewParamSetPair(KeyProviderFeePoolAddrStr, - p.ProviderFeePoolAddrStr, validateProviderFeePoolAddrStr), + p.ProviderFeePoolAddrStr, ValidateProviderFeePoolAddrStr), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, p.CcvTimeoutPeriod, ccvtypes.ValidateDuration), paramtypes.NewParamSetPair(KeyTransferTimeoutPeriod, @@ -167,15 +167,15 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyConsumerUnbondingPeriod, p.UnbondingPeriod, ccvtypes.ValidateDuration), paramtypes.NewParamSetPair(KeySoftOptOutThreshold, - p.SoftOptOutThreshold, validateSoftOptOutThreshold), + p.SoftOptOutThreshold, ValidateSoftOptOutThreshold), paramtypes.NewParamSetPair(KeyRewardDenoms, - p.RewardDenoms, validateDenoms), + p.RewardDenoms, ValidateDenoms), paramtypes.NewParamSetPair(KeyProviderRewardDenoms, - p.ProviderRewardDenoms, validateDenoms), + p.ProviderRewardDenoms, ValidateDenoms), } } -func validateDistributionTransmissionChannel(i interface{}) error { +func ValidateDistributionTransmissionChannel(i interface{}) error { // Accept empty string as valid, since this will be the default value on genesis if i == "" { return nil @@ -184,7 +184,7 @@ func validateDistributionTransmissionChannel(i interface{}) error { return ccvtypes.ValidateChannelIdentifier(i) } -func validateProviderFeePoolAddrStr(i interface{}) error { +func ValidateProviderFeePoolAddrStr(i interface{}) error { // Accept empty string as valid, since this will be the default value on genesis if i == "" { return nil @@ -193,7 +193,7 @@ func validateProviderFeePoolAddrStr(i interface{}) error { return ccvtypes.ValidateBech32(i) } -func validateSoftOptOutThreshold(i interface{}) error { +func ValidateSoftOptOutThreshold(i interface{}) error { str, ok := i.(string) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -211,7 +211,7 @@ func validateSoftOptOutThreshold(i interface{}) error { return nil } -func validateDenoms(i interface{}) error { +func ValidateDenoms(i interface{}) error { v, ok := i.([]string) if !ok { return fmt.Errorf("invalid parameter type: %T", i) diff --git a/x/ccv/provider/ibc_module_test.go b/x/ccv/provider/ibc_module_test.go index dfe8790c32..c7027bf8e1 100644 --- a/x/ccv/provider/ibc_module_test.go +++ b/x/ccv/provider/ibc_module_test.go @@ -24,10 +24,11 @@ import ( // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-coinit1 // Spec Tag: [CCV-PCF-COINIT.1] func TestOnChanOpenInit(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) + t, keeperParams) defer ctrl.Finish() - providerModule := provider.NewAppModule(&providerKeeper) + providerModule := provider.NewAppModule(&providerKeeper, *keeperParams.ParamsSubspace) // OnChanOpenInit must error for provider even with correct arguments _, err := providerModule.OnChanOpenInit( @@ -112,9 +113,10 @@ func TestOnChanOpenTry(t *testing.T) { for _, tc := range testCases { // Setup + keeperParams := testkeeper.NewInMemKeeperParams(t) providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) - providerModule := provider.NewAppModule(&providerKeeper) + t, keeperParams) + providerModule := provider.NewAppModule(&providerKeeper, *keeperParams.ParamsSubspace) providerKeeper.SetPort(ctx, ccv.ProviderPortID) providerKeeper.SetConsumerClientId(ctx, "consumerChainID", "clientIDToConsumer") @@ -181,10 +183,11 @@ func TestOnChanOpenTry(t *testing.T) { // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-coack1 // Spec tag: [CCV-PCF-COACK.1] func TestOnChanOpenAck(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) + t, keeperParams) defer ctrl.Finish() - providerModule := provider.NewAppModule(&providerKeeper) + providerModule := provider.NewAppModule(&providerKeeper, *keeperParams.ParamsSubspace) // OnChanOpenAck must error for provider even with correct arguments err := providerModule.OnChanOpenAck( @@ -296,8 +299,9 @@ func TestOnChanOpenConfirm(t *testing.T) { for _, tc := range testCases { + keeperParams := testkeeper.NewInMemKeeperParams(t) providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( - t, testkeeper.NewInMemKeeperParams(t)) + t, keeperParams) gomock.InOrder(tc.mockExpectations(ctx, mocks)...) @@ -305,7 +309,7 @@ func TestOnChanOpenConfirm(t *testing.T) { providerKeeper.SetChainToChannel(ctx, "consumerChainID", "existingChannelID") } - providerModule := provider.NewAppModule(&providerKeeper) + providerModule := provider.NewAppModule(&providerKeeper, *keeperParams.ParamsSubspace) err := providerModule.OnChanOpenConfirm(ctx, "providerPortID", "channelID") diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 025272351a..7494057643 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -82,6 +82,12 @@ func NewKeeper( return k } +// SetParamSpace sets the param space for the provider keeper. +// Note: this is only used for testing! +func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { + k.paramSpace = ps +} + // Validates that the provider keeper is initialized with non-zero and // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { @@ -1054,3 +1060,7 @@ func (k Keeper) GetSlashLog( bz := store.Get(types.SlashLogKey(providerAddr)) return bz != nil } + +func (k Keeper) BondDenom(ctx sdk.Context) string { + return k.stakingKeeper.BondDenom(ctx) +} diff --git a/x/ccv/provider/keeper/migration.go b/x/ccv/provider/keeper/migration.go new file mode 100644 index 0000000000..1254faf463 --- /dev/null +++ b/x/ccv/provider/keeper/migration.go @@ -0,0 +1,178 @@ +package keeper + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + providertypes "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + ccvProviderKeeper Keeper + ccvProviderParamSpace paramtypes.Subspace +} + +// NewMigrator returns a new Migrator. +func NewMigrator(ccvProviderKeeper Keeper, ccvProviderParamSpace paramtypes.Subspace, +) Migrator { + return Migrator{ccvProviderKeeper: ccvProviderKeeper, ccvProviderParamSpace: ccvProviderParamSpace} +} + +// Migratev1Tov2 migrates a provider from v1.0.0 to v2.0.0. +func (m Migrator) Migratev1Tov2(ctx sdk.Context) error { + // Migrate params + MigrateParamsv1Tov2(ctx, + m.ccvProviderParamSpace, + // See https://github.com/cosmos/interchain-security/blob/7861804cb311507ec6aebebbfad60ea42eb8ed4b/x/ccv/provider/keeper/params.go#L84 + // The v1.1.0-multiden version of ICS hardcodes this param as 10 of bond type: k.stakingKeeper.BondDenom(ctx). + // Here we use the same starting value, but the param can now be changed through governance. + sdk.NewCoin(m.ccvProviderKeeper.BondDenom(ctx), sdk.NewInt(10000000)), + ) + + // Delete select consumer genesis states for consumers that're launched + MigrateConsumerGenesisStatesv1Tov2(ctx, m.ccvProviderKeeper) + + // Migrate keys to accommodate fix from https://github.com/cosmos/interchain-security/pull/786 + MigrateKeysv1Tov2(ctx, m.ccvProviderKeeper) + + return nil +} + +// MigrateParamsv1Tov2 migrates the provider CCV module params from v1.0.0 to v2.0.0, +// setting default values for new params. +func MigrateParamsv1Tov2(ctx sdk.Context, paramsSubspace paramtypes.Subspace, consumerRewardDenomRegistrationFee sdk.Coin) { + // Get old params + var templateClient ibctmtypes.ClientState + paramsSubspace.Get(ctx, providertypes.KeyTemplateClient, &templateClient) + var trustingPeriodFraction string + paramsSubspace.Get(ctx, providertypes.KeyTrustingPeriodFraction, &trustingPeriodFraction) + var ccvTimeoutPeriod time.Duration + paramsSubspace.Get(ctx, ccvtypes.KeyCCVTimeoutPeriod, &ccvTimeoutPeriod) + var initTimeoutPeriod time.Duration + paramsSubspace.Get(ctx, providertypes.KeyInitTimeoutPeriod, &initTimeoutPeriod) + var vscTimeoutPeriod time.Duration + paramsSubspace.Get(ctx, providertypes.KeyVscTimeoutPeriod, &vscTimeoutPeriod) + var slashMeterReplenishPeriod time.Duration + paramsSubspace.Get(ctx, providertypes.KeySlashMeterReplenishPeriod, &slashMeterReplenishPeriod) + var slashMeterReplenishFraction string + paramsSubspace.Get(ctx, providertypes.KeySlashMeterReplenishFraction, &slashMeterReplenishFraction) + var maxThrottledPackets int64 + paramsSubspace.Get(ctx, providertypes.KeyMaxThrottledPackets, &maxThrottledPackets) + + // Recycle old params, set new param to input value + newParams := providertypes.NewParams( + &templateClient, + trustingPeriodFraction, + ccvTimeoutPeriod, + initTimeoutPeriod, + vscTimeoutPeriod, + slashMeterReplenishPeriod, + slashMeterReplenishFraction, + maxThrottledPackets, + consumerRewardDenomRegistrationFee, + ) + + // Persist new params + paramsSubspace.SetParamSet(ctx, &newParams) +} + +func MigrateConsumerGenesisStatesv1Tov2(ctx sdk.Context, providerKeeper Keeper) { + // We could try to migrate existing ConsumerGenesisStates, but they're not needed after consumer launch. + // Hence we delete them strategically. + providerKeeper.DeleteConsumerGenesis(ctx, "neutron-1") // See https://github.com/neutron-org/mainnet-assets#parameters + + // TODO: determine if any other ConsumerGenesisStates need to be deleted, or actually migrated! +} + +// Due to https://github.com/cosmos/interchain-security/pull/786, +// validators' slash logs are stored under the key prefix for slash acks. +// This method will extract "slash logs" from the slash acks part of the store, and put the slash logs +// in their appropriate store location. +func MigrateKeysv1Tov2(ctx sdk.Context, providerKeeper Keeper) { + keys := providerKeeper.getAllKeysUnderSlashAcksPrefix(ctx) + + // Get valid consumer chainIDs + consumers := providerKeeper.GetAllConsumerChains(ctx) + consumerChainIds := make(map[string]struct{}) + for _, consumer := range consumers { + consumerChainIds[consumer.ChainId] = struct{}{} + } + + keysToMigrate := [][]byte{} + + // iterate through all keys under slash acks prefix + for _, key := range keys { + bzAfterPrefix := key[1:] + // If bz after prefix is in consumerChainIds, + // then this key is a valid slash acks key, no migration needed + if _, ok := consumerChainIds[string(bzAfterPrefix)]; ok { + continue + } + // Otherwise this key is potentially/hopefully a slash log key to migrate + + // Validate that after the prefix, it's just a cons address stored in the key + if err := sdk.VerifyAddressFormat(bzAfterPrefix); err != nil { + // We could panic here, but prob best to log corrupted key and move on. + // This case should not happen! + ctx.Logger().Error("unexpected key under slash acks prefix", "key", key) + continue + } + keysToMigrate = append(keysToMigrate, key) + } + + // Migrate slash logs to their correct store location + store := ctx.KVStore(providerKeeper.storeKey) + for _, key := range keysToMigrate { + keyNoPrefix := key[1:] + keyCorrectPrefix := append([]byte{providertypes.SlashLogBytePrefix}, keyNoPrefix...) + valueBz := store.Get(key) + store.Set(keyCorrectPrefix, valueBz) + store.Delete(key) + } +} + +func (k Keeper) getAllKeysUnderSlashAcksPrefix(ctx sdk.Context) [][]byte { + store := ctx.KVStore(k.storeKey) + prefix := []byte{providertypes.SlashAcksBytePrefix} + iterator := sdk.KVStorePrefixIterator(store, prefix) + defer iterator.Close() + keys := [][]byte{} + for ; iterator.Valid(); iterator.Next() { + keys = append(keys, iterator.Key()) // Values are not used for migration, just keys + } + return keys +} + +// TODO: the following hackyness could be removed if we're able to reference older versions of ICS. +// This would likely require go.mod split, and a testing module that could depend on multiple ICS versions. + +// LEGACY METHOD USED FOR TESTING MIGRATION ONLY. DO NOT USE! +// This method is copy/pasted from ICS v1.0.0. +func SlashLogKeyOnlyForTesting(providerAddr sdk.ConsAddress) []byte { + return append([]byte{providertypes.SlashAcksBytePrefix}, providerAddr.Bytes()...) +} + +// LEGACY METHOD USED FOR TESTING MIGRATION ONLY. DO NOT USE! +// This method mimics SetSlashLog from ICS v1.0.0. +func (k Keeper) SetSlashLogOnlyForTesting( + ctx sdk.Context, + providerAddr sdk.ConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Set(SlashLogKeyOnlyForTesting(providerAddr), []byte{}) +} + +// LEGACY METHOD USED FOR TESTING MIGRATION ONLY. DO NOT USE! +// This method mimics GetSlashLog from ICS v1.0.0. +func (k Keeper) GetSlashLogOnlyForTesting( + ctx sdk.Context, + providerAddr sdk.ConsAddress, +) (found bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(SlashLogKeyOnlyForTesting(providerAddr)) + return bz != nil +} diff --git a/x/ccv/provider/keeper/migration_test.go b/x/ccv/provider/keeper/migration_test.go new file mode 100644 index 0000000000..858aa193e6 --- /dev/null +++ b/x/ccv/provider/keeper/migration_test.go @@ -0,0 +1,190 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + types2 "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + "github.com/cosmos/interchain-security/v2/testutil/crypto" + testutil "github.com/cosmos/interchain-security/v2/testutil/keeper" + consumertypes "github.com/cosmos/interchain-security/v2/x/ccv/consumer/types" + providerkeeper "github.com/cosmos/interchain-security/v2/x/ccv/provider/keeper" + providertypes "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v2/x/ccv/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmdb "github.com/tendermint/tm-db" +) + +func TestMigrateParamsv1Tov2(t *testing.T) { + // Setup raw store + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + storeKey := sdk.NewKVStoreKey(paramtypes.StoreKey) + memStoreKey := storetypes.NewMemoryStoreKey("mem_key") + stateStore.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(memStoreKey, sdk.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + require.NoError(t, stateStore.LoadLatestVersion()) + + // Create new empty subspace + subspace := paramtypes.NewSubspace(cdc, + codec.NewLegacyAmino(), + storeKey, + memStoreKey, + paramtypes.ModuleName, + ).WithKeyTable(v1KeyTable()) // Note that new param key table is set in keeper constructor + + // Set 8 params from v1.0.0 + subspace.Set(ctx, providertypes.KeyTemplateClient, providertypes.DefaultParams().TemplateClient) + subspace.Set(ctx, providertypes.KeyTrustingPeriodFraction, "0.75") + subspace.Set(ctx, ccvtypes.KeyCCVTimeoutPeriod, time.Hour) + subspace.Set(ctx, providertypes.KeyInitTimeoutPeriod, time.Hour) + subspace.Set(ctx, providertypes.KeyVscTimeoutPeriod, time.Hour) + subspace.Set(ctx, providertypes.KeySlashMeterReplenishPeriod, time.Hour) + subspace.Set(ctx, providertypes.KeySlashMeterReplenishFraction, "0.5") + subspace.Set(ctx, providertypes.KeyMaxThrottledPackets, int64(10)) + + // Confirm new param cannot be set with old key table + require.Panics(t, func() { + subspace.Set(ctx, providertypes.KeyConsumerRewardDenomRegistrationFee, sdk.NewInt64Coin("uatom", 100)) + }) + + // Now create new subspace, mocking an upgrade where app initialization happens again + subspace = paramtypes.NewSubspace(cdc, + codec.NewLegacyAmino(), + storeKey, + memStoreKey, + paramtypes.ModuleName, + ).WithKeyTable(providertypes.ParamKeyTable()) // Use v2 key table, this would be set in keeper constructor upon app init + + // Run migration + providerkeeper.MigrateParamsv1Tov2(ctx, subspace, sdk.NewCoin("uatom", sdk.NewInt(100000))) + + // Use keeper to confirm params are set correctly + keeper := providerkeeper.Keeper{} + keeper.SetParamSpace(ctx, subspace) + + params := keeper.GetParams(ctx) + require.Equal(t, providertypes.DefaultParams().TemplateClient, params.TemplateClient) + require.Equal(t, "0.75", params.TrustingPeriodFraction) + require.Equal(t, time.Hour, params.CcvTimeoutPeriod) + require.Equal(t, time.Hour, params.InitTimeoutPeriod) + require.Equal(t, time.Hour, params.VscTimeoutPeriod) + require.Equal(t, time.Hour, params.SlashMeterReplenishPeriod) + require.Equal(t, "0.5", params.SlashMeterReplenishFraction) + require.Equal(t, int64(10), params.MaxThrottledPackets) + // New param should be set + require.Equal(t, sdk.NewCoin("uatom", sdk.NewInt(100000)), params.ConsumerRewardDenomRegistrationFee) + + // Set new param to other values + params.ConsumerRewardDenomRegistrationFee = sdk.NewCoin("uatom", sdk.NewInt(1000000000)) + keeper.SetParams(ctx, params) + require.Equal(t, sdk.NewCoin("uatom", sdk.NewInt(1000000000)), keeper.GetParams(ctx).ConsumerRewardDenomRegistrationFee) +} + +// v1KeyTable is a copy of the ParamKeyTable method from v1.0.0 +func v1KeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&v1Params{}) +} + +// ParamSetPairs implements params.ParamSet for v1Params +func (p *v1Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(providertypes.KeyTemplateClient, p.TemplateClient, providertypes.ValidateTemplateClient), + paramtypes.NewParamSetPair(providertypes.KeyTrustingPeriodFraction, p.TrustingPeriodFraction, ccvtypes.ValidateStringFraction), + paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, p.CcvTimeoutPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(providertypes.KeyInitTimeoutPeriod, p.InitTimeoutPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(providertypes.KeyVscTimeoutPeriod, p.VscTimeoutPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(providertypes.KeySlashMeterReplenishPeriod, p.SlashMeterReplenishPeriod, ccvtypes.ValidateDuration), + paramtypes.NewParamSetPair(providertypes.KeySlashMeterReplenishFraction, p.SlashMeterReplenishFraction, ccvtypes.ValidateStringFraction), + paramtypes.NewParamSetPair(providertypes.KeyMaxThrottledPackets, p.MaxThrottledPackets, ccvtypes.ValidatePositiveInt64), + } +} + +// v1Params is a copy of the Params struct from v1.0.0 +type v1Params struct { + TemplateClient *types2.ClientState `protobuf:"bytes,1,opt,name=template_client,json=templateClient,proto3" json:"template_client,omitempty"` + TrustingPeriodFraction string `protobuf:"bytes,2,opt,name=trusting_period_fraction,json=trustingPeriodFraction,proto3" json:"trusting_period_fraction,omitempty"` + CcvTimeoutPeriod time.Duration `protobuf:"bytes,3,opt,name=ccv_timeout_period,json=ccvTimeoutPeriod,proto3,stdduration" json:"ccv_timeout_period"` + InitTimeoutPeriod time.Duration `protobuf:"bytes,4,opt,name=init_timeout_period,json=initTimeoutPeriod,proto3,stdduration" json:"init_timeout_period"` + VscTimeoutPeriod time.Duration `protobuf:"bytes,5,opt,name=vsc_timeout_period,json=vscTimeoutPeriod,proto3,stdduration" json:"vsc_timeout_period"` + SlashMeterReplenishPeriod time.Duration `protobuf:"bytes,6,opt,name=slash_meter_replenish_period,json=slashMeterReplenishPeriod,proto3,stdduration" json:"slash_meter_replenish_period"` + SlashMeterReplenishFraction string `protobuf:"bytes,7,opt,name=slash_meter_replenish_fraction,json=slashMeterReplenishFraction,proto3" json:"slash_meter_replenish_fraction,omitempty"` + MaxThrottledPackets int64 `protobuf:"varint,8,opt,name=max_throttled_packets,json=maxThrottledPackets,proto3" json:"max_throttled_packets,omitempty"` +} + +func TestMigrateConsumerGenesisv1Tov2(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testutil.GetProviderKeeperAndCtx(t, testutil.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + _, found := providerKeeper.GetConsumerGenesis(ctx, "neutron-1") + require.False(t, found) + + providerKeeper.SetConsumerGenesis(ctx, "neutron-1", consumertypes.GenesisState{}) + + _, found = providerKeeper.GetConsumerGenesis(ctx, "neutron-1") + require.True(t, found) + + providerkeeper.MigrateConsumerGenesisStatesv1Tov2(ctx, providerKeeper) + + _, found = providerKeeper.GetConsumerGenesis(ctx, "neutron-1") + require.False(t, found) +} + +func TestMigrateKeysv1Tov2(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testutil.GetProviderKeeperAndCtx(t, testutil.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // First we setup a scenario that may show up in prod for v1, + // where both slash logs and slash acks are persisted under the same key prefix + cIds := crypto.GenMultipleCryptoIds(3, 349823489230) + providerKeeper.SetSlashLogOnlyForTesting(ctx, cIds[0].SDKValConsAddress()) // This is the old (incorrect) method of storing slash logs + providerKeeper.SetSlashLogOnlyForTesting(ctx, cIds[1].SDKValConsAddress()) + providerKeeper.SetSlashLogOnlyForTesting(ctx, cIds[2].SDKValConsAddress()) + + // Setup slash acks + p := []string{"alice", "bob", "frank"} + providerKeeper.SetSlashAcks(ctx, "chain-1", p) + p = []string{"charlie", "mac", "dennis"} + providerKeeper.SetSlashAcks(ctx, "chain-2", p) + + // Mock two clients being established with chain-1 and chain-2, + // This is needed for migration logic. + providerKeeper.SetConsumerClientId(ctx, "chain-1", "client-1") + providerKeeper.SetConsumerClientId(ctx, "chain-2", "client-2") + + // Confirm slash logs and slash acks exist together + require.True(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[0].SDKValConsAddress())) + require.True(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[1].SDKValConsAddress())) + require.True(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[2].SDKValConsAddress())) + require.Len(t, providerKeeper.GetSlashAcks(ctx, "chain-1"), 3) + require.Len(t, providerKeeper.GetSlashAcks(ctx, "chain-2"), 3) + + // Run migration + providerkeeper.MigrateKeysv1Tov2(ctx, providerKeeper) + + // Confirm slash logs cannot be found from legacy methods + require.False(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[0].SDKValConsAddress())) + require.False(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[1].SDKValConsAddress())) + require.False(t, providerKeeper.GetSlashLogOnlyForTesting(ctx, cIds[2].SDKValConsAddress())) + + // Slash acks remain unchanged + require.Len(t, providerKeeper.GetSlashAcks(ctx, "chain-1"), 3) + require.Len(t, providerKeeper.GetSlashAcks(ctx, "chain-2"), 3) + + // Confirm slash logs can be found from new/correct methods + require.True(t, providerKeeper.GetSlashLog(ctx, cIds[0].ProviderConsAddress())) + require.True(t, providerKeeper.GetSlashLog(ctx, cIds[1].ProviderConsAddress())) + require.True(t, providerKeeper.GetSlashLog(ctx, cIds[2].ProviderConsAddress())) +} diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index 742dc9b743..bb15b75ab1 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -12,6 +12,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" porttypes "github.com/cosmos/ibc-go/v4/modules/core/05-port/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/client/cli" "github.com/cosmos/interchain-security/v2/x/ccv/provider/keeper" @@ -88,13 +89,15 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { // AppModule represents the AppModule for this module type AppModule struct { AppModuleBasic - keeper *keeper.Keeper + keeper *keeper.Keeper + paramSpace paramtypes.Subspace } // NewAppModule creates a new provider module -func NewAppModule(k *keeper.Keeper) AppModule { +func NewAppModule(k *keeper.Keeper, paramSpace paramtypes.Subspace) AppModule { return AppModule{ - keeper: k, + keeper: k, + paramSpace: paramSpace, } } @@ -122,6 +125,11 @@ func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { func (am AppModule) RegisterServices(cfg module.Configurator) { providertypes.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) providertypes.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + m := keeper.NewMigrator(*am.keeper, am.paramSpace) + if err := cfg.RegisterMigration(providertypes.ModuleName, 1, m.Migratev1Tov2); err != nil { + panic(fmt.Sprintf("failed to register migrator: %s", err)) + } } // InitGenesis performs genesis initialization for the provider module. It returns no validator updates. @@ -144,7 +152,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements AppModule/ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 1 } +func (AppModule) ConsensusVersion() uint64 { return 2 } // BeginBlock implements the AppModule interface func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { diff --git a/x/ccv/provider/module_test.go b/x/ccv/provider/module_test.go index ee81daa4bf..3e2776c4e7 100644 --- a/x/ccv/provider/module_test.go +++ b/x/ccv/provider/module_test.go @@ -94,7 +94,7 @@ func TestInitGenesis(t *testing.T) { keeperParams := testkeeper.NewInMemKeeperParams(t) providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - appModule := provider.NewAppModule(&providerKeeper) + appModule := provider.NewAppModule(&providerKeeper, *keeperParams.ParamsSubspace) genState := types.NewGenesisState( providerKeeper.GetValidatorSetUpdateId(ctx), nil, diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index 2e3657eaf3..3966232501 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -121,7 +121,7 @@ func (p Params) Validate() error { if p.TemplateClient == nil { return fmt.Errorf("template client is nil") } - if err := validateTemplateClient(*p.TemplateClient); err != nil { + if err := ValidateTemplateClient(*p.TemplateClient); err != nil { return err } if err := ccvtypes.ValidateStringFraction(p.TrustingPeriodFraction); err != nil { @@ -145,7 +145,7 @@ func (p Params) Validate() error { if err := ccvtypes.ValidatePositiveInt64(p.MaxThrottledPackets); err != nil { return fmt.Errorf("max throttled packets is invalid: %s", err) } - if err := validateCoin(p.ConsumerRewardDenomRegistrationFee); err != nil { + if err := ValidateCoin(p.ConsumerRewardDenomRegistrationFee); err != nil { return fmt.Errorf("consumer reward denom registration fee is invalid: %s", err) } return nil @@ -154,7 +154,7 @@ func (p Params) Validate() error { // ParamSetPairs implements params.ParamSet func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ - paramtypes.NewParamSetPair(KeyTemplateClient, p.TemplateClient, validateTemplateClient), + paramtypes.NewParamSetPair(KeyTemplateClient, p.TemplateClient, ValidateTemplateClient), paramtypes.NewParamSetPair(KeyTrustingPeriodFraction, p.TrustingPeriodFraction, ccvtypes.ValidateStringFraction), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, p.CcvTimeoutPeriod, ccvtypes.ValidateDuration), paramtypes.NewParamSetPair(KeyInitTimeoutPeriod, p.InitTimeoutPeriod, ccvtypes.ValidateDuration), @@ -162,11 +162,11 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeySlashMeterReplenishPeriod, p.SlashMeterReplenishPeriod, ccvtypes.ValidateDuration), paramtypes.NewParamSetPair(KeySlashMeterReplenishFraction, p.SlashMeterReplenishFraction, ccvtypes.ValidateStringFraction), paramtypes.NewParamSetPair(KeyMaxThrottledPackets, p.MaxThrottledPackets, ccvtypes.ValidatePositiveInt64), - paramtypes.NewParamSetPair(KeyConsumerRewardDenomRegistrationFee, p.ConsumerRewardDenomRegistrationFee, validateCoin), + paramtypes.NewParamSetPair(KeyConsumerRewardDenomRegistrationFee, p.ConsumerRewardDenomRegistrationFee, ValidateCoin), } } -func validateTemplateClient(i interface{}) error { +func ValidateTemplateClient(i interface{}) error { cs, ok := i.(ibctmtypes.ClientState) if !ok { return fmt.Errorf("invalid parameter type: %T, expected: %T", i, ibctmtypes.ClientState{}) @@ -193,7 +193,7 @@ func validateTemplateClient(i interface{}) error { return nil } -func validateCoin(i interface{}) error { +func ValidateCoin(i interface{}) error { v, ok := i.(sdk.Coin) if !ok { return fmt.Errorf("invalid parameter type: %T", i)