diff --git a/Dockerfile b/Dockerfile index de183457d0..aeaf114783 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ RUN make install FROM --platform=linux/amd64 ghcr.io/informalsystems/hermes:v1.7.0 AS hermes-builder # Get CometMock -FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder +FROM ghcr.io/informalsystems/cometmock:v0.38.0 as cometmock-builder # Get GoRelayer FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim AS gorelayer-builder diff --git a/app/provider/app.go b/app/provider/app.go index 00c775043e..656e800abc 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - stdlog "log" "os" "path/filepath" @@ -32,6 +31,7 @@ import ( "cosmossdk.io/x/evidence" evidencekeeper "cosmossdk.io/x/evidence/keeper" evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/tx/signing" "cosmossdk.io/x/upgrade" upgradekeeper "cosmossdk.io/x/upgrade/keeper" upgradetypes "cosmossdk.io/x/upgrade/types" @@ -41,6 +41,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/runtime" runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" @@ -59,6 +60,8 @@ import ( authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/auth/posthandler" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/auth/vesting" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" @@ -104,6 +107,8 @@ import ( tmos "github.com/cometbft/cometbft/libs/os" dbm "github.com/cosmos/cosmos-db" + sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + appparams "github.com/cosmos/interchain-security/v3/app/params" testutil "github.com/cosmos/interchain-security/v3/testutil/integration" icsprovider "github.com/cosmos/interchain-security/v3/x/ccv/provider" @@ -113,9 +118,8 @@ import ( ) const ( - AppName = "interchain-security-p" - upgradeName = "ics-v1-to-v2" - AccountAddressPrefix = "cosmos" + AppName = "interchain-security-p" + upgradeName = "ics-v1-to-v2" ) // this line is used by starport scaffolding # stargate/wasm/app/enabledProposals @@ -230,7 +234,7 @@ type App struct { // nolint: golint func init() { userHomeDir, err := os.UserHomeDir() if err != nil { - stdlog.Println("Failed to get home dir %2", err) + panic(fmt.Sprintf("Failed to get home dir %v", err)) } DefaultNodeHome = filepath.Join(userHomeDir, "."+AppName) @@ -245,15 +249,23 @@ func New( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *App { - encodingConfig := makeEncodingConfig() + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) - interfaceRegistry := encodingConfig.InterfaceRegistry - appCodec := encodingConfig.Codec - legacyAmino := encodingConfig.Amino - txConfig := encodingConfig.TxConfig std.RegisterLegacyAminoCodec(legacyAmino) std.RegisterInterfaces(interfaceRegistry) - // ABCI++, v50 voteExtOp := func(bApp *baseapp.BaseApp) { voteExtHandler := NewVoteExtensionHandler() @@ -560,6 +572,21 @@ func New( ModuleBasics.RegisterLegacyAminoCodec(app.legacyAmino) ModuleBasics.RegisterInterfaces(app.interfaceRegistry) + enabledSignModes := append(authtx.DefaultSignModes, + sigtypes.SignMode_SIGN_MODE_TEXTUAL) + txConfigOpts := authtx.ConfigOptions{ + EnabledSignModes: enabledSignModes, + TextualCoinMetadataQueryFn: txmodule.NewBankKeeperCoinMetadataQueryFn(app.BankKeeper), + } + txConfig, err := authtx.NewTxConfigWithOptions( + appCodec, + txConfigOpts, + ) + if err != nil { + panic(err) + } + app.txConfig = txConfig + app.MM.SetOrderPreBlockers( upgradetypes.ModuleName, ) @@ -638,7 +665,7 @@ func New( app.MM.RegisterInvariants(&app.CrisisKeeper) app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) - err := app.MM.RegisterServices(app.configurator) + err = app.MM.RegisterServices(app.configurator) if err != nil { panic(err) } @@ -699,7 +726,7 @@ func New( HandlerOptions: ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SignModeHandler: txConfig.SignModeHandler(), SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, IBCKeeper: app.IBCKeeper, diff --git a/cmd/interchain-security-pd/cmd/root.go b/cmd/interchain-security-pd/cmd/root.go index 780aba17f8..5d22aca9f1 100644 --- a/cmd/interchain-security-pd/cmd/root.go +++ b/cmd/interchain-security-pd/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "cosmossdk.io/client/v2/autocli" confixcmd "cosmossdk.io/tools/confix/cmd" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" @@ -16,19 +17,24 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/pruning" "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" serverconfig "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/crisis" genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" "cosmossdk.io/log" - tmcfg "github.com/cometbft/cometbft/config" + cmtcfg "github.com/cometbft/cometbft/config" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/interchain-security/v3/app/params" @@ -39,7 +45,7 @@ import ( // main function. func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration - tempApp := providerApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(providerApp.DefaultNodeHome)) + tempApp := providerApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(tempDir())) encodingConfig := params.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), @@ -50,7 +56,6 @@ func NewRootCmd() *cobra.Command { initClientCtx := client.Context{}. WithCodec(encodingConfig.Codec). WithInterfaceRegistry(encodingConfig.InterfaceRegistry). - WithTxConfig(encodingConfig.TxConfig). WithLegacyAmino(encodingConfig.Amino). WithInput(os.Stdin). WithAccountRetriever(types.AccountRetriever{}). @@ -58,8 +63,9 @@ func NewRootCmd() *cobra.Command { WithViper("") // In simapp, we don't use any prefix for env variables. rootCmd := &cobra.Command{ - Use: "simd", - Short: "simulation app", + Use: "simd", + Short: "simulation app", + SilenceErrors: false, PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { // set the default command outputs cmd.SetOut(cmd.OutOrStdout()) @@ -76,66 +82,78 @@ func NewRootCmd() *cobra.Command { return err } + // This needs to go after ReadFromClientConfig, as that function + // sets the RPC client needed for SIGN_MODE_TEXTUAL. This sign mode + // is only available if the client is online. + if !initClientCtx.Offline { + txConfigOpts := tx.ConfigOptions{ + EnabledSignModes: append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL), + TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), + } + txConfigWithTextual, err := tx.NewTxConfigWithOptions( + codec.NewProtoCodec(encodingConfig.InterfaceRegistry), + txConfigOpts, + ) + if err != nil { + return err + } + initClientCtx = initClientCtx.WithTxConfig(txConfigWithTextual) + } + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { return err } customAppTemplate, customAppConfig := initAppConfig() - customTMConfig := initTendermintConfig() + customCMTConfig := initCometBFTConfig() - return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig) + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) }, } initRootCmd(rootCmd, encodingConfig) - // autocli opts - autoCliOpts := tempApp.AutoCliOpts() - initClientCtx, _ = config.ReadFromClientConfig(initClientCtx) - autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) - autoCliOpts.ClientCtx = initClientCtx + autoCliOpts, err := enrichAutoCliOpts(tempApp.AutoCliOpts(), initClientCtx) + if err != nil { + panic(err) + } if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { panic(err) } + return rootCmd } -// initTendermintConfig helps to override default Tendermint Config values. -// return tmcfg.DefaultConfig if no custom configuration is required for the application. -func initTendermintConfig() *tmcfg.Config { - cfg := tmcfg.DefaultConfig() - - // these values put a higher strain on node memory - // cfg.P2P.MaxNumInboundPeers = 100 - // cfg.P2P.MaxNumOutboundPeers = 40 +func enrichAutoCliOpts(autoCliOpts autocli.AppOptions, clientCtx client.Context) (autocli.AppOptions, error) { + autoCliOpts.AddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) + autoCliOpts.ValidatorAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()) + autoCliOpts.ConsensusAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()) - return cfg -} + var err error + clientCtx, err = config.ReadFromClientConfig(clientCtx) + if err != nil { + return autocli.AppOptions{}, err + } -func txCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "tx", - Short: "Transactions subcommands", - DisableFlagParsing: false, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, + autoCliOpts.ClientCtx = clientCtx + autoCliOpts.Keyring, err = keyring.NewAutoCLIKeyring(clientCtx.Keyring) + if err != nil { + return autocli.AppOptions{}, err } - cmd.AddCommand( - authcmd.GetSignCommand(), - authcmd.GetSignBatchCommand(), - authcmd.GetMultiSignCommand(), - authcmd.GetMultiSignBatchCmd(), - authcmd.GetValidateSignaturesCommand(), - authcmd.GetBroadcastCommand(), - authcmd.GetEncodeCommand(), - authcmd.GetDecodeCommand(), - authcmd.GetSimulateCmd(), - ) + return autoCliOpts, nil +} - providerApp.ModuleBasics.AddTxCommands(cmd) +// initCometBFTConfig helps to override default CometBFT Config values. +// return cmtcfg.DefaultConfig if no custom configuration is required for the application. +func initCometBFTConfig() *cmtcfg.Config { + cfg := cmtcfg.DefaultConfig() - return cmd + // these values put a higher strain on node memory + // cfg.P2P.MaxNumInboundPeers = 100 + // cfg.P2P.MaxNumOutboundPeers = 40 + + return cfg } // initAppConfig helps to override default appConfig template and configs. @@ -204,6 +222,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { debug.Cmd(), pruning.Cmd(newApp, providerApp.DefaultNodeHome), confixcmd.ConfigCommand(), + server.QueryBlockResultsCmd(), ) server.AddCommands(rootCmd, providerApp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) @@ -212,12 +231,75 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { rootCmd.AddCommand( server.StatusCommand(), genesisCommand(encodingConfig), - queryCommand(), txCommand(), + queryCommand(), keys.Commands(), ) } +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + rpc.ValidatorCommand(), + server.QueryBlockCmd(), + authcmd.QueryTxsByEventsCmd(), + server.QueryBlocksCmd(), + authcmd.QueryTxCmd(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + authcmd.GetSimulateCmd(), + ) + + // NOTE: this must be registered for now so that submit-legacy-proposal + // message can be routed to the provider handler and processed correctly. + providerApp.ModuleBasics.AddTxCommands(cmd) + + return cmd +} + +// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter +func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { + cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, providerApp.ModuleBasics, providerApp.DefaultNodeHome) + for _, sub_cmd := range cmds { + cmd.AddCommand(sub_cmd) + } + return cmd +} + // newApp is an appCreator // newApp creates the application func newApp( @@ -277,42 +359,12 @@ func appExport( return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) } -func addModuleInitFlags(startCmd *cobra.Command) { - crisis.AddModuleInitFlags(startCmd) -} - -// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { - cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, providerApp.ModuleBasics, providerApp.DefaultNodeHome) - for _, sub_cmd := range cmds { - cmd.AddCommand(sub_cmd) - } - return cmd -} - -func queryCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "query", - Aliases: []string{"q"}, - Short: "Querying subcommands", - DisableFlagParsing: false, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, +var tempDir = func() string { + dir, err := os.MkdirTemp("", "."+providerApp.AppName) + if err != nil { + dir = providerApp.DefaultNodeHome } + defer os.RemoveAll(dir) - cmd.AddCommand( - rpc.ValidatorCommand(), - server.QueryBlockCmd(), - server.QueryBlocksCmd(), - server.QueryBlockResultsCmd(), - authcmd.QueryTxsByEventsCmd(), - authcmd.QueryTxCmd(), - authcmd.GetEncodeCommand(), - authcmd.GetDecodeCommand(), - authcmd.GetSimulateCmd(), - ) - - providerApp.ModuleBasics.AddQueryCommands(cmd) - - return cmd + return dir } diff --git a/cmd/interchain-security-sd/cmd/root.go b/cmd/interchain-security-sd/cmd/root.go index 5d5810eeb1..e052fb625e 100644 --- a/cmd/interchain-security-sd/cmd/root.go +++ b/cmd/interchain-security-sd/cmd/root.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/pruning" "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" serverconfig "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" @@ -86,6 +87,12 @@ func NewRootCmd() *cobra.Command { } initRootCmd(rootCmd, encodingConfig) + // autocli opts + autoCliOpts := tempApp.AutoCliOpts() + initClientCtx, _ = config.ReadFromClientConfig(initClientCtx) + autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) + autoCliOpts.ClientCtx = initClientCtx + if err := tempApp.AutoCliOpts().EnhanceRootCommand(rootCmd); err != nil { panic(err) } diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 4f4b74a435..19850fc210 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -1408,7 +1408,8 @@ func (tr TestRun) cancelUnbondTokens( if err != nil { log.Fatal(err, "\n", string(bz)) } - creationHeight := gjson.Get(string(bz), "entries.0.creation_height").Int() + + creationHeight := gjson.Get(string(bz), "unbond.entries.0.creation_height").Int() if creationHeight == 0 { log.Fatal("invalid creation height") } @@ -1600,18 +1601,18 @@ func (tr TestRun) unjailValidator(action unjailValidatorAction, verbose bool) { "tx", "slashing", "unjail", // Validator is sender here `--from`, `validator`+fmt.Sprint(action.validator), + `--keyring-backend`, `test`, + `--keyring-dir`, tr.getValidatorHome(action.provider, action.validator), `--chain-id`, string(tr.chainConfigs[action.provider].chainId), - `--home`, tr.getValidatorHome(action.provider, action.validator), `--node`, tr.getValidatorNode(action.provider, action.validator), `--gas`, "900000", - `--keyring-backend`, `test`, `-y`, ) if verbose { fmt.Println("unjail cmd:", cmd.String()) } - + fmt.Println("unjail cmd:", cmd.String()) bz, err := cmd.CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 19f52f800a..9372e88b57 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -16,7 +16,7 @@ func concatSteps(steps ...[]Step) []Step { var happyPathSteps = concatSteps( stepsStartChains([]string{"consu"}, false), stepsDelegate("consu"), - stepsAssignConsumerKeyOnStartedChain("consu", "bob"), + // stepsAssignConsumerKeyOnStartedChain("consu", "bob"), stepsUnbond("consu"), stepsCancelUnbond("consu"), stepsRedelegateForOptOut("consu"), diff --git a/tests/integration/normal_operations.go b/tests/integration/normal_operations.go index cd8e07f323..db302cfc5b 100644 --- a/tests/integration/normal_operations.go +++ b/tests/integration/normal_operations.go @@ -5,10 +5,16 @@ import ( tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) +// TODO: Refactor -> this test is difficult to figure out and follow +// * There are new validators being added +// * There are calls to NextBlock() that are not obvious why they are there and what is going on (or why) +// * There is a tmproto.Header that is being used for something that is not obvious +// // Tests the tracking of historical info in the context of new blocks being committed func (k CCVTestSuite) TestHistoricalInfo() { //nolint:govet // this is a test so we can copy locks consumerKeeper := k.consumerApp.GetConsumerKeeper() @@ -60,30 +66,30 @@ func (k CCVTestSuite) TestHistoricalInfo() { //nolint:govet // this is a test so // is below CurrentHeight - HistoricalEntries, and check that their valset gets updated testCases := []struct { height int64 - found bool + err error expLen int }{ { - height: initHeight + 1, - found: false, - expLen: 0, + height: initHeight, + err: nil, + expLen: initValsetLen + 1, }, { - height: initHeight + 2, - found: false, - expLen: 0, + height: initHeight + 1, + err: nil, + expLen: initValsetLen + 2, }, { - height: initHeight + ccvtypes.DefaultHistoricalEntries + 2, - found: true, - expLen: initValsetLen + 2, + height: initHeight + ccvtypes.DefaultHistoricalEntries + 3, + err: stakingtypes.ErrNoHistoricalInfo, + expLen: 0, }, } for _, tc := range testCases { cCtx().WithBlockHeight(tc.height) - hi, found := consumerKeeper.GetHistoricalInfo(cCtx().WithBlockHeight(tc.height), tc.height) - k.Require().Equal(tc.found, found) + hi, err := consumerKeeper.GetHistoricalInfo(cCtx().WithBlockHeight(tc.height), tc.height) + k.Require().ErrorIs(err, tc.err) k.Require().Len(hi.Valset, tc.expLen) } } diff --git a/testutil/ibc_testing/generic_setup.go b/testutil/ibc_testing/generic_setup.go index ab52cdba3f..e0a63bb2af 100644 --- a/testutil/ibc_testing/generic_setup.go +++ b/testutil/ibc_testing/generic_setup.go @@ -16,8 +16,9 @@ import ( tmtypes "github.com/cometbft/cometbft/types" testutil "github.com/cosmos/interchain-security/v3/testutil/integration" - testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" consumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + providertypes "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) type ( @@ -122,16 +123,28 @@ func AddConsumer[Tp testutil.ProviderApp, Tc testutil.ConsumerApp]( providerApp := providerChain.App.(Tp) providerKeeper := providerApp.GetProviderKeeper() - prop := testkeeper.GetTestConsumerAdditionProp() - prop.ChainId = chainID - // NOTE: the initial height passed to CreateConsumerClient - // must be the height on the consumer when InitGenesis is called - prop.InitialHeight = clienttypes.Height{RevisionNumber: 0, RevisionHeight: 3} - err := providerKeeper.CreateConsumerClient( - providerChain.GetContext(), - prop, - ) - s.Require().NoError(err) + prop := providertypes.ConsumerAdditionProposal{ + Title: fmt.Sprintf("start chain %s", chainID), + Description: "description", + ChainId: chainID, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 2}, + GenesisHash: []byte("gen_hash"), + BinaryHash: []byte("bin_hash"), + // NOTE: we cannot use the time.Now() because the coordinator chooses a hardcoded start time + // using time.Now() could set the spawn time to be too far in the past or too far in the future + SpawnTime: coordinator.CurrentTime, + UnbondingPeriod: ccvtypes.DefaultConsumerUnbondingPeriod, + CcvTimeoutPeriod: ccvtypes.DefaultBlocksPerDistributionTransmission, + TransferTimeoutPeriod: ccvtypes.DefaultCCVTimeoutPeriod, + ConsumerRedistributionFraction: ccvtypes.DefaultConsumerRedistributeFrac, + BlocksPerDistributionTransmission: ccvtypes.DefaultBlocksPerDistributionTransmission, + HistoricalEntries: ccvtypes.DefaultHistoricalEntries, + DistributionTransmissionChannel: "", + } + + providerKeeper.SetPendingConsumerAdditionProp(providerChain.GetContext(), &prop) + propsToExecute := providerKeeper.GetConsumerAdditionPropsToExecute(providerChain.GetContext()) + s.Require().Len(propsToExecute, 1) // commit the state on the provider chain coordinator.CommitBlock(providerChain) diff --git a/testutil/ibc_testing/specific_setup.go b/testutil/ibc_testing/specific_setup.go index f128f3c2fb..4f95d092af 100644 --- a/testutil/ibc_testing/specific_setup.go +++ b/testutil/ibc_testing/specific_setup.go @@ -43,7 +43,7 @@ func ConsumerAppIniter(initValPowers []types.ValidatorUpdate) AppIniter { encoding := appConsumer.MakeTestEncodingConfig() testApp := appConsumer.New(log.NewNopLogger(), db.NewMemDB(), nil, true, simtestutil.EmptyAppOptions{}) genesisState := appConsumer.NewDefaultGenesisState(encoding.Codec) - // NOTE ibc-go/v7/testing.SetupWithGenesisValSet requires a staking module + // NOTE: starting from ibc-go/v7/testing.SetupWithGenesisValSet requires a staking module // genesisState or it panics. Feed a minimum one. genesisState[stakingtypes.ModuleName] = encoding.Codec.MustMarshalJSON( &stakingtypes.GenesisState{ diff --git a/x/ccv/consumer/keeper/changeover_test.go b/x/ccv/consumer/keeper/changeover_test.go index 625b17351b..e11200b50c 100644 --- a/x/ccv/consumer/keeper/changeover_test.go +++ b/x/ccv/consumer/keeper/changeover_test.go @@ -1,172 +1,172 @@ package keeper_test -// import ( -// "testing" - -// "github.com/golang/mock/gomock" -// "github.com/stretchr/testify/require" - -// sdkcryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" -// stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - -// abci "github.com/cometbft/cometbft/abci/types" - -// "github.com/cosmos/interchain-security/v3/testutil/crypto" -// uthelpers "github.com/cosmos/interchain-security/v3/testutil/keeper" -// ) - -// func TestChangeoverToConsumer(t *testing.T) { -// cIds := []crypto.CryptoIdentity{} -// for i := 0; i < 10; i++ { -// cIds = append(cIds, *crypto.NewCryptoIdentityFromIntSeed(i + 42834729)) -// } - -// // Instantiate 5 sov validators for use in test -// sovVals := []stakingtypes.Validator{ -// cIds[0].SDKStakingValidator(), -// cIds[1].SDKStakingValidator(), -// cIds[2].SDKStakingValidator(), -// cIds[3].SDKStakingValidator(), -// cIds[4].SDKStakingValidator(), -// } - -// // Instantiate 5 ics val updates for use in test -// initialValUpdates := []abci.ValidatorUpdate{ -// {Power: 55, PubKey: cIds[5].TMProtoCryptoPublicKey()}, -// {Power: 87324, PubKey: cIds[6].TMProtoCryptoPublicKey()}, -// {Power: 2, PubKey: cIds[7].TMProtoCryptoPublicKey()}, -// {Power: 42389479, PubKey: cIds[8].TMProtoCryptoPublicKey()}, -// {Power: 9089080, PubKey: cIds[9].TMProtoCryptoPublicKey()}, -// } - -// testCases := []struct { -// name string -// // Last standalone validators that will be mock returned from stakingKeeper.GetLastValidators() -// lastSovVals []stakingtypes.Validator -// // Val updates corresponding to initial valset set for ccv set initGenesis -// initialValUpdates []abci.ValidatorUpdate -// // Expected length of val updates returned from ChangeoverToConsumer() -// expectedReturnValUpdatesLen int -// }{ -// { -// name: "no sov vals, no initial val updates", -// lastSovVals: []stakingtypes.Validator{}, -// initialValUpdates: []abci.ValidatorUpdate{}, -// expectedReturnValUpdatesLen: 0, -// }, -// { -// name: "one sov val, no initial val updates", -// lastSovVals: []stakingtypes.Validator{sovVals[0]}, -// initialValUpdates: []abci.ValidatorUpdate{}, -// expectedReturnValUpdatesLen: 1, -// }, -// { -// name: "no sov vals, one initial val update", -// lastSovVals: []stakingtypes.Validator{}, -// initialValUpdates: []abci.ValidatorUpdate{initialValUpdates[0]}, -// expectedReturnValUpdatesLen: 1, -// }, -// { -// name: "one sov val, one initial val update", -// lastSovVals: []stakingtypes.Validator{sovVals[0]}, -// initialValUpdates: []abci.ValidatorUpdate{initialValUpdates[0]}, -// expectedReturnValUpdatesLen: 2, -// }, -// { -// name: "five sov vals, five initial val updates", -// lastSovVals: sovVals, -// initialValUpdates: initialValUpdates, -// expectedReturnValUpdatesLen: 10, -// }, -// { -// name: "validator is contained in both sov val set and initial val updates, using cIds[7]", -// lastSovVals: []stakingtypes.Validator{cIds[7].SDKStakingValidator()}, -// initialValUpdates: []abci.ValidatorUpdate{ -// {Power: 55, PubKey: cIds[7].TMProtoCryptoPublicKey()}, -// }, -// expectedReturnValUpdatesLen: 1, -// }, -// } - -// for _, tc := range testCases { - -// keeperParams := uthelpers.NewInMemKeeperParams(t) -// consumerKeeper, ctx, ctrl, mocks := uthelpers.GetConsumerKeeperAndCtx(t, keeperParams) -// defer ctrl.Finish() - -// // Set PRECCV to true, as would be done in InitGenesis -// consumerKeeper.SetPreCCVTrue(ctx) - -// // Set initial valset, as would be done in InitGenesis -// consumerKeeper.SetInitialValSet(ctx, tc.initialValUpdates) - -// // Setup mocked return value for stakingKeeper.GetLastValidators() -// gomock.InOrder( -// mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return(tc.lastSovVals), -// ) - -// // Add ref to standalone staking keeper -// consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) - -// returnedInitialValUpdates := consumerKeeper.ChangeoverToConsumer(ctx) - -// // PreCCV should now be toggled false -// require.False(t, consumerKeeper.IsPreCCV(ctx)) - -// // Cross chain validator states should be populated with initial valset -// ccVals := consumerKeeper.GetAllCCValidator(ctx) -// require.Len(t, ccVals, len(tc.initialValUpdates)) - -// // For each initial val update, assert that a corresponding ccVal entry exists -// // with the same power and pubkey -// for _, valUpdate := range tc.initialValUpdates { -// found := false -// for _, ccVal := range ccVals { -// ccvValPubKey, err := ccVal.ConsPubKey() -// require.NoError(t, err) -// tmProtoPubKey, err := sdkcryptocodec.ToTmProtoPublicKey(ccvValPubKey) -// require.NoError(t, err) -// if tmProtoPubKey.Equal(valUpdate.PubKey) { -// found = true -// require.Equal(t, valUpdate.Power, ccVal.Power) -// } -// } -// require.True(t, found) -// } - -// // Assert that initial val updates returned from ChangeoverToConsumer are formulated s.t. -// // the "old" validators returned from standalone chain's staking module -// // are given zero power, and the "new" validators are given their full power. -// for _, returnedValUpdate := range returnedInitialValUpdates { -// found := false -// // Check all initial val updates for a pubkey match -// for _, valUpdate := range tc.initialValUpdates { -// if returnedValUpdate.PubKey.Equal(valUpdate.PubKey) { -// require.Equal(t, valUpdate.Power, returnedValUpdate.Power) -// found = true -// } -// } -// // Check all standalone validators for a pubkey match -// for _, val := range tc.lastSovVals { -// ccvValPubKey, err := val.ConsPubKey() -// require.NoError(t, err) -// tmProtoPubKey, err := sdkcryptocodec.ToTmProtoPublicKey(ccvValPubKey) -// require.NoError(t, err) -// if returnedValUpdate.PubKey.Equal(tmProtoPubKey) { -// // If val was already matched to a val update for new set, it's power wont be 0 -// if found { -// continue -// } -// // Assert power of the val update is zero -// require.Equal(t, int64(0), returnedValUpdate.Power) -// found = true -// } -// } -// // Assert that a match was found -// require.True(t, found) -// } - -// // Assert no extraneous entries -// require.Len(t, returnedInitialValUpdates, tc.expectedReturnValUpdatesLen) -// } -// } +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + sdkcryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/interchain-security/v3/testutil/crypto" + uthelpers "github.com/cosmos/interchain-security/v3/testutil/keeper" +) + +func TestChangeoverToConsumer(t *testing.T) { + cIds := []crypto.CryptoIdentity{} + for i := 0; i < 10; i++ { + cIds = append(cIds, *crypto.NewCryptoIdentityFromIntSeed(i + 42834729)) + } + + // Instantiate 5 sov validators for use in test + sovVals := []stakingtypes.Validator{ + cIds[0].SDKStakingValidator(), + cIds[1].SDKStakingValidator(), + cIds[2].SDKStakingValidator(), + cIds[3].SDKStakingValidator(), + cIds[4].SDKStakingValidator(), + } + + // Instantiate 5 ics val updates for use in test + initialValUpdates := []abci.ValidatorUpdate{ + {Power: 55, PubKey: cIds[5].TMProtoCryptoPublicKey()}, + {Power: 87324, PubKey: cIds[6].TMProtoCryptoPublicKey()}, + {Power: 2, PubKey: cIds[7].TMProtoCryptoPublicKey()}, + {Power: 42389479, PubKey: cIds[8].TMProtoCryptoPublicKey()}, + {Power: 9089080, PubKey: cIds[9].TMProtoCryptoPublicKey()}, + } + + testCases := []struct { + name string + // Last standalone validators that will be mock returned from stakingKeeper.GetLastValidators() + lastSovVals []stakingtypes.Validator + // Val updates corresponding to initial valset set for ccv set initGenesis + initialValUpdates []abci.ValidatorUpdate + // Expected length of val updates returned from ChangeoverToConsumer() + expectedReturnValUpdatesLen int + }{ + { + name: "no sov vals, no initial val updates", + lastSovVals: []stakingtypes.Validator{}, + initialValUpdates: []abci.ValidatorUpdate{}, + expectedReturnValUpdatesLen: 0, + }, + { + name: "one sov val, no initial val updates", + lastSovVals: []stakingtypes.Validator{sovVals[0]}, + initialValUpdates: []abci.ValidatorUpdate{}, + expectedReturnValUpdatesLen: 1, + }, + { + name: "no sov vals, one initial val update", + lastSovVals: []stakingtypes.Validator{}, + initialValUpdates: []abci.ValidatorUpdate{initialValUpdates[0]}, + expectedReturnValUpdatesLen: 1, + }, + { + name: "one sov val, one initial val update", + lastSovVals: []stakingtypes.Validator{sovVals[0]}, + initialValUpdates: []abci.ValidatorUpdate{initialValUpdates[0]}, + expectedReturnValUpdatesLen: 2, + }, + { + name: "five sov vals, five initial val updates", + lastSovVals: sovVals, + initialValUpdates: initialValUpdates, + expectedReturnValUpdatesLen: 10, + }, + { + name: "validator is contained in both sov val set and initial val updates, using cIds[7]", + lastSovVals: []stakingtypes.Validator{cIds[7].SDKStakingValidator()}, + initialValUpdates: []abci.ValidatorUpdate{ + {Power: 55, PubKey: cIds[7].TMProtoCryptoPublicKey()}, + }, + expectedReturnValUpdatesLen: 1, + }, + } + + for _, tc := range testCases { + + keeperParams := uthelpers.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, mocks := uthelpers.GetConsumerKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + + // Set PRECCV to true, as would be done in InitGenesis + consumerKeeper.SetPreCCVTrue(ctx) + + // Set initial valset, as would be done in InitGenesis + consumerKeeper.SetInitialValSet(ctx, tc.initialValUpdates) + + // Setup mocked return value for stakingKeeper.GetLastValidators() + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return(tc.lastSovVals, nil), + ) + + // Add ref to standalone staking keeper + consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) + + returnedInitialValUpdates := consumerKeeper.ChangeoverToConsumer(ctx) + + // PreCCV should now be toggled false + require.False(t, consumerKeeper.IsPreCCV(ctx)) + + // Cross chain validator states should be populated with initial valset + ccVals := consumerKeeper.GetAllCCValidator(ctx) + require.Len(t, ccVals, len(tc.initialValUpdates)) + + // For each initial val update, assert that a corresponding ccVal entry exists + // with the same power and pubkey + for _, valUpdate := range tc.initialValUpdates { + found := false + for _, ccVal := range ccVals { + ccvValPubKey, err := ccVal.ConsPubKey() + require.NoError(t, err) + tmProtoPubKey, err := sdkcryptocodec.ToTmProtoPublicKey(ccvValPubKey) + require.NoError(t, err) + if tmProtoPubKey.Equal(valUpdate.PubKey) { + found = true + require.Equal(t, valUpdate.Power, ccVal.Power) + } + } + require.True(t, found) + } + + // Assert that initial val updates returned from ChangeoverToConsumer are formulated s.t. + // the "old" validators returned from standalone chain's staking module + // are given zero power, and the "new" validators are given their full power. + for _, returnedValUpdate := range returnedInitialValUpdates { + found := false + // Check all initial val updates for a pubkey match + for _, valUpdate := range tc.initialValUpdates { + if returnedValUpdate.PubKey.Equal(valUpdate.PubKey) { + require.Equal(t, valUpdate.Power, returnedValUpdate.Power) + found = true + } + } + // Check all standalone validators for a pubkey match + for _, val := range tc.lastSovVals { + ccvValPubKey, err := val.ConsPubKey() + require.NoError(t, err) + tmProtoPubKey, err := sdkcryptocodec.ToTmProtoPublicKey(ccvValPubKey) + require.NoError(t, err) + if returnedValUpdate.PubKey.Equal(tmProtoPubKey) { + // If val was already matched to a val update for new set, it's power wont be 0 + if found { + continue + } + // Assert power of the val update is zero + require.Equal(t, int64(0), returnedValUpdate.Power) + found = true + } + } + // Assert that a match was found + require.True(t, found) + } + + // Assert no extraneous entries + require.Len(t, returnedInitialValUpdates, tc.expectedReturnValUpdatesLen) + } +} diff --git a/x/ccv/consumer/keeper/keeper_test.go b/x/ccv/consumer/keeper/keeper_test.go index c967064934..73a1eae32e 100644 --- a/x/ccv/consumer/keeper/keeper_test.go +++ b/x/ccv/consumer/keeper/keeper_test.go @@ -1,585 +1,608 @@ package keeper_test -// // TestProviderClientID tests getter and setter functionality for the client ID stored on consumer keeper -// func TestProviderClientID(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// _, ok := consumerKeeper.GetProviderClientID(ctx) -// require.False(t, ok) -// consumerKeeper.SetProviderClientID(ctx, "someClientID") -// clientID, ok := consumerKeeper.GetProviderClientID(ctx) -// require.True(t, ok) -// require.Equal(t, "someClientID", clientID) -// } - -// // TestProviderChannel tests getter and setter functionality for the channel ID stored on consumer keeper -// func TestProviderChannel(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// _, ok := consumerKeeper.GetProviderChannel(ctx) -// require.False(t, ok) -// consumerKeeper.SetProviderChannel(ctx, "channelID") -// channelID, ok := consumerKeeper.GetProviderChannel(ctx) -// require.True(t, ok) -// require.Equal(t, "channelID", channelID) -// } - -// // TestPendingChanges tests getter, setter, and delete functionality for pending VSCs on a consumer chain -// func TestPendingChanges(t *testing.T) { -// pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) -// require.NoError(t, err) -// pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) -// require.NoError(t, err) - -// pd := ccv.NewValidatorSetChangePacketData( -// []abci.ValidatorUpdate{ -// { -// PubKey: pk1, -// Power: 30, -// }, -// { -// PubKey: pk2, -// Power: 20, -// }, -// }, -// 1, -// nil, -// ) - -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// consumerKeeper.SetPendingChanges(ctx, pd) -// gotPd, ok := consumerKeeper.GetPendingChanges(ctx) -// require.True(t, ok) -// require.Equal(t, &pd, gotPd, "packet data in store does not equal packet data set") -// consumerKeeper.DeletePendingChanges(ctx) -// gotPd, ok = consumerKeeper.GetPendingChanges(ctx) -// require.False(t, ok) -// require.Nil(t, gotPd, "got non-nil pending changes after Delete") -// } - -// // TestLastSovereignHeight tests the getter and setter for the ccv init genesis height -// func TestInitGenesisHeight(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Panics without setter -// require.Panics(t, func() { consumerKeeper.GetInitGenesisHeight(ctx) }) - -// // Set/get the height being 10 -// consumerKeeper.SetInitGenesisHeight(ctx, 10) -// require.Equal(t, int64(10), consumerKeeper.GetInitGenesisHeight(ctx)) - -// // Set/get the height being 43234426 -// consumerKeeper.SetInitGenesisHeight(ctx, 43234426) -// require.Equal(t, int64(43234426), consumerKeeper.GetInitGenesisHeight(ctx)) -// } - -// // TestPreCCV tests the getter, setter and deletion methods for the pre-CCV state flag -// func TestPreCCV(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Default value is false without any setter -// require.False(t, consumerKeeper.IsPreCCV(ctx)) - -// // Set/get the pre-CCV state to true -// consumerKeeper.SetPreCCVTrue(ctx) -// require.True(t, consumerKeeper.IsPreCCV(ctx)) - -// // Delete the pre-CCV state, setting it to false -// consumerKeeper.DeletePreCCV(ctx) -// require.False(t, consumerKeeper.IsPreCCV(ctx)) -// } - -// // TestInitialValSet tests the getter and setter methods for storing the initial validator set for a consumer -// func TestInitialValSet(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Default value is empty val update list -// require.Empty(t, consumerKeeper.GetInitialValSet(ctx)) - -// // Set/get the initial validator set -// cId1 := crypto.NewCryptoIdentityFromIntSeed(7896) -// cId2 := crypto.NewCryptoIdentityFromIntSeed(7897) -// cId3 := crypto.NewCryptoIdentityFromIntSeed(7898) -// valUpdates := []abci.ValidatorUpdate{ -// { -// PubKey: cId1.TMProtoCryptoPublicKey(), -// Power: 1097, -// }, -// { -// PubKey: cId2.TMProtoCryptoPublicKey(), -// Power: 19068, -// }, -// { -// PubKey: cId3.TMProtoCryptoPublicKey(), -// Power: 10978554, -// }, -// } - -// consumerKeeper.SetInitialValSet(ctx, valUpdates) -// require.Equal(t, []abci.ValidatorUpdate{ -// { -// PubKey: cId1.TMProtoCryptoPublicKey(), -// Power: 1097, -// }, -// { -// PubKey: cId2.TMProtoCryptoPublicKey(), -// Power: 19068, -// }, -// { -// PubKey: cId3.TMProtoCryptoPublicKey(), -// Power: 10978554, -// }, -// }, consumerKeeper.GetInitialValSet(ctx)) -// } - -// // TestGetLastSovereignValidators tests the getter method for getting the last valset -// // from the standalone staking keeper -// func TestGetLastSovereignValidators(t *testing.T) { -// ck, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Should panic if pre-CCV is true but staking keeper is not set -// ck.SetPreCCVTrue(ctx) -// require.Panics(t, func() { ck.GetLastStandaloneValidators(ctx) }) - -// // Should panic if staking keeper is set but pre-CCV is false -// ck.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) -// ck.DeletePreCCV(ctx) -// require.False(t, ck.IsPreCCV(ctx)) -// require.Panics(t, func() { ck.GetLastStandaloneValidators(ctx) }) - -// // Set the pre-CCV state to true and get the last standalone validators from mock -// ck.SetPreCCVTrue(ctx) -// require.True(t, ck.IsPreCCV(ctx)) -// cId1 := crypto.NewCryptoIdentityFromIntSeed(11) -// val := cId1.SDKStakingValidator() -// val.Description.Moniker = "sanity check this is the correctly serialized val" -// gomock.InOrder( -// mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return([]stakingtypes.Validator{ -// val, -// }), -// ) -// lastSovVals := ck.GetLastStandaloneValidators(ctx) -// require.Equal(t, []stakingtypes.Validator{val}, lastSovVals) -// require.Equal(t, "sanity check this is the correctly serialized val", -// lastSovVals[0].Description.Moniker) -// } - -// // TestPacketMaturityTime tests getter, setter, and iterator functionality for the packet maturity time of a received VSC packet -// func TestPacketMaturityTime(t *testing.T) { -// ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// now := time.Now().UTC() -// packets := []ccv.MaturingVSCPacket{ -// { -// VscId: 2, -// MaturityTime: now, -// }, -// { -// VscId: 1, -// MaturityTime: now.Add(-time.Hour), -// }, -// { -// VscId: 5, -// MaturityTime: now.Add(-2 * time.Hour), -// }, -// { -// VscId: 6, -// MaturityTime: now.Add(time.Hour), -// }, -// } -// // sort by MaturityTime and not by VscId -// expectedGetAllOrder := []ccv.MaturingVSCPacket{packets[2], packets[1], packets[0], packets[3]} -// // only packets with MaturityTime before or equal to now -// expectedGetElapsedOrder := []ccv.MaturingVSCPacket{packets[2], packets[1], packets[0]} - -// // test SetPacketMaturityTime -// for _, packet := range packets { -// ck.SetPacketMaturityTime(ctx, packet.VscId, packet.MaturityTime) -// } - -// // test PacketMaturityTimeExists -// for _, packet := range packets { -// require.True(t, ck.PacketMaturityTimeExists(ctx, packet.VscId, packet.MaturityTime)) -// } - -// // test GetAllPacketMaturityTimes -// maturingVSCPackets := ck.GetAllPacketMaturityTimes(ctx) -// require.Len(t, maturingVSCPackets, len(packets)) -// require.Equal(t, expectedGetAllOrder, maturingVSCPackets) - -// // test GetElapsedPacketMaturityTimes -// elapsedMaturingVSCPackets := ck.GetElapsedPacketMaturityTimes(ctx.WithBlockTime(now)) -// require.Equal(t, expectedGetElapsedOrder, elapsedMaturingVSCPackets) - -// // test DeletePacketMaturityTimes -// ck.DeletePacketMaturityTimes(ctx, packets[0].VscId, packets[0].MaturityTime) -// require.False(t, ck.PacketMaturityTimeExists(ctx, packets[0].VscId, packets[0].MaturityTime)) -// } - -// // TestCrossChainValidator tests the getter, setter, and deletion method for cross chain validator records -// func TestCrossChainValidator(t *testing.T) { -// keeperParams := testkeeper.NewInMemKeeperParams(t) -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) -// defer ctrl.Finish() - -// // should return false -// _, found := consumerKeeper.GetCCValidator(ctx, ed25519.GenPrivKey().PubKey().Address()) -// require.False(t, found) - -// // Obtain derived private key -// privKey := ed25519.GenPrivKey() - -// // Set cross chain validator -// ccVal, err := types.NewCCValidator(privKey.PubKey().Address(), 1000, privKey.PubKey()) -// require.NoError(t, err) -// consumerKeeper.SetCCValidator(ctx, ccVal) - -// gotCCVal, found := consumerKeeper.GetCCValidator(ctx, ccVal.Address) -// require.True(t, found) - -// // verify the returned validator values -// require.EqualValues(t, ccVal, gotCCVal) - -// // expect to return the same consensus pubkey -// pk, err := ccVal.ConsPubKey() -// require.NoError(t, err) -// gotPK, err := gotCCVal.ConsPubKey() -// require.NoError(t, err) -// require.Equal(t, pk, gotPK) - -// // delete validator -// consumerKeeper.DeleteCCValidator(ctx, ccVal.Address) - -// // should return false -// _, found = consumerKeeper.GetCCValidator(ctx, ccVal.Address) -// require.False(t, found) -// } - -// // TestGetAllCCValidator tests GetAllCCValidator behaviour correctness -// func TestGetAllCCValidator(t *testing.T) { -// keeperParams := testkeeper.NewInMemKeeperParams(t) -// ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) -// defer ctrl.Finish() - -// numValidators := 4 -// validators := []types.CrossChainValidator{} -// for i := 0; i < numValidators; i++ { -// validators = append(validators, testkeeper.GetNewCrossChainValidator(t)) -// } -// // sorting by CrossChainValidator.Address -// expectedGetAllOrder := validators -// sort.Slice(expectedGetAllOrder, func(i, j int) bool { -// return bytes.Compare(expectedGetAllOrder[i].Address, expectedGetAllOrder[j].Address) == -1 -// }) - -// for _, val := range validators { -// ck.SetCCValidator(ctx, val) -// } - -// // iterate and check all results are returned in the expected order -// result := ck.GetAllCCValidator(ctx) -// require.Len(t, result, len(validators)) -// require.Equal(t, result, expectedGetAllOrder) -// } - -// func TestPendingPackets(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Instantiate some expected packet data -// packetData := []ccv.ConsumerPacketData{ -// { -// Type: ccv.VscMaturedPacket, -// Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: ccv.NewVSCMaturedPacketData(1), -// }, -// }, -// { -// Type: ccv.VscMaturedPacket, -// Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: ccv.NewVSCMaturedPacketData(2), -// }, -// }, -// { -// Type: ccv.SlashPacket, -// Data: &ccv.ConsumerPacketData_SlashPacketData{ -// SlashPacketData: ccv.NewSlashPacketData( -// abci.Validator{Address: ed25519.GenPrivKey().PubKey().Address(), Power: int64(0)}, -// 3, -// stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN, -// ), -// }, -// }, -// { -// Type: ccv.VscMaturedPacket, -// Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: ccv.NewVSCMaturedPacketData(3), -// }, -// }, -// } - -// // Append all packets to the queue -// for _, data := range packetData { -// consumerKeeper.AppendPendingPacket(ctx, data.Type, data.Data) -// } -// storedPacketData := consumerKeeper.GetPendingPackets(ctx) -// require.NotEmpty(t, storedPacketData) -// require.Equal(t, packetData, storedPacketData) - -// slashPacket := ccv.NewSlashPacketData( -// abci.Validator{ -// Address: ed25519.GenPrivKey().PubKey().Address(), -// Power: int64(2), -// }, -// uint64(4), -// stakingtypes.Infraction_INFRACTION_DOWNTIME, -// ) -// // Append slash packet to expected packet data -// packetData = append(packetData, ccv.ConsumerPacketData{ -// Type: ccv.SlashPacket, -// Data: &ccv.ConsumerPacketData_SlashPacketData{ -// SlashPacketData: slashPacket, -// }, -// }) - -// toAppend := packetData[len(packetData)-1] -// consumerKeeper.AppendPendingPacket(ctx, toAppend.Type, toAppend.Data) -// storedPacketData = consumerKeeper.GetPendingPackets(ctx) -// require.NotEmpty(t, storedPacketData) -// require.Equal(t, packetData, storedPacketData) - -// vscMaturedPacket := ccv.NewVSCMaturedPacketData(4) -// packetData = append(packetData, ccv.ConsumerPacketData{ -// Type: ccv.VscMaturedPacket, -// Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: vscMaturedPacket, -// }, -// }) -// toAppend = packetData[len(packetData)-1] -// consumerKeeper.AppendPendingPacket(ctx, toAppend.Type, toAppend.Data) - -// storedPacketData = consumerKeeper.GetPendingPackets(ctx) -// require.NotEmpty(t, storedPacketData) -// require.Equal(t, packetData, storedPacketData) - -// // Delete packet with idx 5 (final index) -// consumerKeeper.DeletePendingDataPackets(ctx, 5) -// storedPacketData = consumerKeeper.GetPendingPackets(ctx) -// require.Equal(t, packetData[:len(packetData)-1], storedPacketData) -// pendingPacketsWithIdx := consumerKeeper.GetAllPendingPacketsWithIdx(ctx) -// require.Equal(t, uint64(4), pendingPacketsWithIdx[len(pendingPacketsWithIdx)-1].Idx) // final element should have idx 4 - -// // Delete packet with idx 0 (first index) -// consumerKeeper.DeletePendingDataPackets(ctx, 0) -// storedPacketData = consumerKeeper.GetPendingPackets(ctx) -// require.Equal(t, packetData[1:len(packetData)-1], storedPacketData) -// pendingPacketsWithIdx = consumerKeeper.GetAllPendingPacketsWithIdx(ctx) -// require.Equal(t, uint64(1), pendingPacketsWithIdx[0].Idx) // first element should have idx 1 - -// // Delete all packets -// consumerKeeper.DeleteAllPendingDataPackets(ctx) -// storedPacketData = consumerKeeper.GetPendingPackets(ctx) -// require.Empty(t, storedPacketData) -// require.Empty(t, consumerKeeper.GetAllPendingPacketsWithIdx(ctx)) -// } - -// // TestVerifyProviderChain tests the VerifyProviderChain method for the consumer keeper -// func TestVerifyProviderChain(t *testing.T) { -// testCases := []struct { -// name string -// // State-mutating setup specific to this test case -// mockSetup func(sdk.Context, testkeeper.MockedKeepers) -// connectionHops []string -// expError bool -// }{ -// { -// name: "success", -// mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { -// gomock.InOrder( -// mocks.MockConnectionKeeper.EXPECT().GetConnection( -// ctx, "connectionID", -// ).Return(conntypes.ConnectionEnd{ClientId: "clientID"}, true).Times(1), -// ) -// }, -// connectionHops: []string{"connectionID"}, -// expError: false, -// }, -// { -// name: "connection hops is not length 1", -// mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { -// // Expect no calls to GetConnection(), VerifyProviderChain will return from first step. -// gomock.InAnyOrder( -// mocks.MockConnectionKeeper.EXPECT().GetConnection(gomock.Any(), gomock.Any()).Times(0), -// ) -// }, -// connectionHops: []string{"connectionID", "otherConnID"}, -// expError: true, -// }, -// { -// name: "connection does not exist", -// mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { -// gomock.InOrder( -// mocks.MockConnectionKeeper.EXPECT().GetConnection( -// ctx, "connectionID").Return(conntypes.ConnectionEnd{}, -// false, // Found is returned as false -// ).Times(1), -// ) -// }, -// connectionHops: []string{"connectionID"}, -// expError: true, -// }, -// { -// name: "found clientID does not match expectation", -// mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { -// gomock.InOrder( -// mocks.MockConnectionKeeper.EXPECT().GetConnection( -// ctx, "connectionID").Return( -// conntypes.ConnectionEnd{ClientId: "unexpectedClientID"}, true, -// ).Times(1), -// ) -// }, -// connectionHops: []string{"connectionID"}, -// expError: true, -// }, -// } - -// for _, tc := range testCases { - -// keeperParams := testkeeper.NewInMemKeeperParams(t) -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) - -// // Common setup -// consumerKeeper.SetProviderClientID(ctx, "clientID") // Set expected provider clientID - -// // Specific mock setup -// tc.mockSetup(ctx, mocks) - -// err := consumerKeeper.VerifyProviderChain(ctx, tc.connectionHops) - -// if tc.expError { -// require.Error(t, err, "invalid case did not return error") -// } else { -// require.NoError(t, err, "valid case returned error") -// } -// ctrl.Finish() -// } -// } - -// // TestGetAllHeightToValsetUpdateIDs tests GetAllHeightToValsetUpdateIDs behaviour correctness -// func TestGetAllHeightToValsetUpdateIDs(t *testing.T) { -// ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// cases := []ccv.HeightToValsetUpdateID{ -// { -// ValsetUpdateId: 2, -// Height: 22, -// }, -// { -// ValsetUpdateId: 1, -// Height: 11, -// }, -// { -// // normal execution should not have two HeightToValsetUpdateID -// // with the same ValsetUpdateId, but let's test it anyway -// ValsetUpdateId: 1, -// Height: 44, -// }, -// { -// ValsetUpdateId: 3, -// Height: 33, -// }, -// } -// expectedGetAllOrder := cases -// // sorting by Height -// sort.Slice(expectedGetAllOrder, func(i, j int) bool { -// return expectedGetAllOrder[i].Height < expectedGetAllOrder[j].Height -// }) - -// for _, c := range cases { -// ck.SetHeightValsetUpdateID(ctx, c.Height, c.ValsetUpdateId) -// } - -// // iterate and check all results are returned -// result := ck.GetAllHeightToValsetUpdateIDs(ctx) -// require.Len(t, result, len(cases)) -// require.Equal(t, expectedGetAllOrder, result) -// } - -// // TestGetAllOutstandingDowntimes tests GetAllOutstandingDowntimes behaviour correctness -// func TestGetAllOutstandingDowntimes(t *testing.T) { -// ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// addresses := []sdk.ConsAddress{ -// sdk.ConsAddress([]byte("consAddress2")), -// sdk.ConsAddress([]byte("consAddress1")), -// sdk.ConsAddress([]byte("consAddress4")), -// sdk.ConsAddress([]byte("consAddress3")), -// } -// expectedGetAllOrder := []ccv.OutstandingDowntime{} -// for _, addr := range addresses { -// expectedGetAllOrder = append(expectedGetAllOrder, ccv.OutstandingDowntime{ValidatorConsensusAddress: addr.String()}) -// } -// // sorting by ConsAddress -// sort.Slice(expectedGetAllOrder, func(i, j int) bool { -// return bytes.Compare(addresses[i], addresses[j]) == -1 -// }) - -// for _, addr := range addresses { -// ck.SetOutstandingDowntime(ctx, addr) -// } - -// // iterate and check all results are returned in the expected order -// result := ck.GetAllOutstandingDowntimes(ctx) -// require.Len(t, result, len(addresses)) -// require.Equal(t, result, expectedGetAllOrder) -// } - -// func TestPrevStandaloneChainFlag(t *testing.T) { -// ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Test that the default value is false -// require.False(t, ck.IsPrevStandaloneChain(ctx)) - -// // Test that the value can be set and retrieved -// ck.MarkAsPrevStandaloneChain(ctx) -// require.True(t, ck.IsPrevStandaloneChain(ctx)) -// } - -// func TestDeleteHeadOfPendingPackets(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // append some pending packets -// consumerKeeper.AppendPendingPacket(ctx, ccv.VscMaturedPacket, &ccv.ConsumerPacketData_VscMaturedPacketData{}) -// consumerKeeper.AppendPendingPacket(ctx, ccv.SlashPacket, &ccv.ConsumerPacketData_SlashPacketData{}) -// consumerKeeper.AppendPendingPacket(ctx, ccv.VscMaturedPacket, &ccv.ConsumerPacketData_VscMaturedPacketData{}) - -// // Check there's 3 pending packets, vsc matured at head -// pp := consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pp, 3) -// require.Equal(t, pp[0].Type, ccv.VscMaturedPacket) - -// // Delete the head, confirm slash packet is now at head -// consumerKeeper.DeleteHeadOfPendingPackets(ctx) -// pp = consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pp, 2) -// require.Equal(t, pp[0].Type, ccv.SlashPacket) - -// // Delete the head, confirm vsc matured packet is now at head -// consumerKeeper.DeleteHeadOfPendingPackets(ctx) -// pp = consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pp, 1) -// require.Equal(t, pp[0].Type, ccv.VscMaturedPacket) -// } +import ( + "bytes" + "sort" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + abci "github.com/cometbft/cometbft/abci/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + conntypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + + "github.com/cosmos/interchain-security/v3/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" + "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" +) + +// TestProviderClientID tests getter and setter functionality for the client ID stored on consumer keeper +func TestProviderClientID(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + _, ok := consumerKeeper.GetProviderClientID(ctx) + require.False(t, ok) + consumerKeeper.SetProviderClientID(ctx, "someClientID") + clientID, ok := consumerKeeper.GetProviderClientID(ctx) + require.True(t, ok) + require.Equal(t, "someClientID", clientID) +} + +// TestProviderChannel tests getter and setter functionality for the channel ID stored on consumer keeper +func TestProviderChannel(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + _, ok := consumerKeeper.GetProviderChannel(ctx) + require.False(t, ok) + consumerKeeper.SetProviderChannel(ctx, "channelID") + channelID, ok := consumerKeeper.GetProviderChannel(ctx) + require.True(t, ok) + require.Equal(t, "channelID", channelID) +} + +// TestPendingChanges tests getter, setter, and delete functionality for pending VSCs on a consumer chain +func TestPendingChanges(t *testing.T) { + pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + require.NoError(t, err) + pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + require.NoError(t, err) + + pd := ccv.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 20, + }, + }, + 1, + nil, + ) + + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + consumerKeeper.SetPendingChanges(ctx, pd) + gotPd, ok := consumerKeeper.GetPendingChanges(ctx) + require.True(t, ok) + require.Equal(t, &pd, gotPd, "packet data in store does not equal packet data set") + consumerKeeper.DeletePendingChanges(ctx) + gotPd, ok = consumerKeeper.GetPendingChanges(ctx) + require.False(t, ok) + require.Nil(t, gotPd, "got non-nil pending changes after Delete") +} + +// TestLastSovereignHeight tests the getter and setter for the ccv init genesis height +func TestInitGenesisHeight(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Panics without setter + require.Panics(t, func() { consumerKeeper.GetInitGenesisHeight(ctx) }) + + // Set/get the height being 10 + consumerKeeper.SetInitGenesisHeight(ctx, 10) + require.Equal(t, int64(10), consumerKeeper.GetInitGenesisHeight(ctx)) + + // Set/get the height being 43234426 + consumerKeeper.SetInitGenesisHeight(ctx, 43234426) + require.Equal(t, int64(43234426), consumerKeeper.GetInitGenesisHeight(ctx)) +} + +// TestPreCCV tests the getter, setter and deletion methods for the pre-CCV state flag +func TestPreCCV(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Default value is false without any setter + require.False(t, consumerKeeper.IsPreCCV(ctx)) + + // Set/get the pre-CCV state to true + consumerKeeper.SetPreCCVTrue(ctx) + require.True(t, consumerKeeper.IsPreCCV(ctx)) + + // Delete the pre-CCV state, setting it to false + consumerKeeper.DeletePreCCV(ctx) + require.False(t, consumerKeeper.IsPreCCV(ctx)) +} + +// TestInitialValSet tests the getter and setter methods for storing the initial validator set for a consumer +func TestInitialValSet(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Default value is empty val update list + require.Empty(t, consumerKeeper.GetInitialValSet(ctx)) + + // Set/get the initial validator set + cId1 := crypto.NewCryptoIdentityFromIntSeed(7896) + cId2 := crypto.NewCryptoIdentityFromIntSeed(7897) + cId3 := crypto.NewCryptoIdentityFromIntSeed(7898) + valUpdates := []abci.ValidatorUpdate{ + { + PubKey: cId1.TMProtoCryptoPublicKey(), + Power: 1097, + }, + { + PubKey: cId2.TMProtoCryptoPublicKey(), + Power: 19068, + }, + { + PubKey: cId3.TMProtoCryptoPublicKey(), + Power: 10978554, + }, + } + + consumerKeeper.SetInitialValSet(ctx, valUpdates) + require.Equal(t, []abci.ValidatorUpdate{ + { + PubKey: cId1.TMProtoCryptoPublicKey(), + Power: 1097, + }, + { + PubKey: cId2.TMProtoCryptoPublicKey(), + Power: 19068, + }, + { + PubKey: cId3.TMProtoCryptoPublicKey(), + Power: 10978554, + }, + }, consumerKeeper.GetInitialValSet(ctx)) +} + +// TestGetLastSovereignValidators tests the getter method for getting the last valset +// from the standalone staking keeper +func TestGetLastSovereignValidators(t *testing.T) { + ck, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Should panic if pre-CCV is true but staking keeper is not set + ck.SetPreCCVTrue(ctx) + require.Panics(t, func() { ck.GetLastStandaloneValidators(ctx) }) + + // Should panic if staking keeper is set but pre-CCV is false + ck.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) + ck.DeletePreCCV(ctx) + require.False(t, ck.IsPreCCV(ctx)) + require.Panics(t, func() { ck.GetLastStandaloneValidators(ctx) }) + + // Set the pre-CCV state to true and get the last standalone validators from mock + ck.SetPreCCVTrue(ctx) + require.True(t, ck.IsPreCCV(ctx)) + cId1 := crypto.NewCryptoIdentityFromIntSeed(11) + val := cId1.SDKStakingValidator() + val.Description.Moniker = "sanity check this is the correctly serialized val" + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetLastValidators(ctx).Return([]stakingtypes.Validator{ + val, + }, nil), + ) + lastSovVals, err := ck.GetLastStandaloneValidators(ctx) + require.NoError(t, err) + require.Equal(t, []stakingtypes.Validator{val}, lastSovVals) + require.Equal(t, "sanity check this is the correctly serialized val", + lastSovVals[0].Description.Moniker) +} + +// TestPacketMaturityTime tests getter, setter, and iterator functionality for the packet maturity time of a received VSC packet +func TestPacketMaturityTime(t *testing.T) { + ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + now := time.Now().UTC() + packets := []ccv.MaturingVSCPacket{ + { + VscId: 2, + MaturityTime: now, + }, + { + VscId: 1, + MaturityTime: now.Add(-time.Hour), + }, + { + VscId: 5, + MaturityTime: now.Add(-2 * time.Hour), + }, + { + VscId: 6, + MaturityTime: now.Add(time.Hour), + }, + } + // sort by MaturityTime and not by VscId + expectedGetAllOrder := []ccv.MaturingVSCPacket{packets[2], packets[1], packets[0], packets[3]} + // only packets with MaturityTime before or equal to now + expectedGetElapsedOrder := []ccv.MaturingVSCPacket{packets[2], packets[1], packets[0]} + + // test SetPacketMaturityTime + for _, packet := range packets { + ck.SetPacketMaturityTime(ctx, packet.VscId, packet.MaturityTime) + } + + // test PacketMaturityTimeExists + for _, packet := range packets { + require.True(t, ck.PacketMaturityTimeExists(ctx, packet.VscId, packet.MaturityTime)) + } + + // test GetAllPacketMaturityTimes + maturingVSCPackets := ck.GetAllPacketMaturityTimes(ctx) + require.Len(t, maturingVSCPackets, len(packets)) + require.Equal(t, expectedGetAllOrder, maturingVSCPackets) + + // test GetElapsedPacketMaturityTimes + elapsedMaturingVSCPackets := ck.GetElapsedPacketMaturityTimes(ctx.WithBlockTime(now)) + require.Equal(t, expectedGetElapsedOrder, elapsedMaturingVSCPackets) + + // test DeletePacketMaturityTimes + ck.DeletePacketMaturityTimes(ctx, packets[0].VscId, packets[0].MaturityTime) + require.False(t, ck.PacketMaturityTimeExists(ctx, packets[0].VscId, packets[0].MaturityTime)) +} + +// TestCrossChainValidator tests the getter, setter, and deletion method for cross chain validator records +func TestCrossChainValidator(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + + // should return false + _, found := consumerKeeper.GetCCValidator(ctx, ed25519.GenPrivKey().PubKey().Address()) + require.False(t, found) + + // Obtain derived private key + privKey := ed25519.GenPrivKey() + + // Set cross chain validator + ccVal, err := types.NewCCValidator(privKey.PubKey().Address(), 1000, privKey.PubKey()) + require.NoError(t, err) + consumerKeeper.SetCCValidator(ctx, ccVal) + + gotCCVal, found := consumerKeeper.GetCCValidator(ctx, ccVal.Address) + require.True(t, found) + + // verify the returned validator values + require.EqualValues(t, ccVal, gotCCVal) + + // expect to return the same consensus pubkey + pk, err := ccVal.ConsPubKey() + require.NoError(t, err) + gotPK, err := gotCCVal.ConsPubKey() + require.NoError(t, err) + require.Equal(t, pk, gotPK) + + // delete validator + consumerKeeper.DeleteCCValidator(ctx, ccVal.Address) + + // should return false + _, found = consumerKeeper.GetCCValidator(ctx, ccVal.Address) + require.False(t, found) +} + +// TestGetAllCCValidator tests GetAllCCValidator behaviour correctness +func TestGetAllCCValidator(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) + ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + + numValidators := 4 + validators := []types.CrossChainValidator{} + for i := 0; i < numValidators; i++ { + validators = append(validators, testkeeper.GetNewCrossChainValidator(t)) + } + // sorting by CrossChainValidator.Address + expectedGetAllOrder := validators + sort.Slice(expectedGetAllOrder, func(i, j int) bool { + return bytes.Compare(expectedGetAllOrder[i].Address, expectedGetAllOrder[j].Address) == -1 + }) + + for _, val := range validators { + ck.SetCCValidator(ctx, val) + } + + // iterate and check all results are returned in the expected order + result := ck.GetAllCCValidator(ctx) + require.Len(t, result, len(validators)) + require.Equal(t, result, expectedGetAllOrder) +} + +func TestPendingPackets(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Instantiate some expected packet data + packetData := []ccv.ConsumerPacketData{ + { + Type: ccv.VscMaturedPacket, + Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: ccv.NewVSCMaturedPacketData(1), + }, + }, + { + Type: ccv.VscMaturedPacket, + Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: ccv.NewVSCMaturedPacketData(2), + }, + }, + { + Type: ccv.SlashPacket, + Data: &ccv.ConsumerPacketData_SlashPacketData{ + SlashPacketData: ccv.NewSlashPacketData( + abci.Validator{Address: ed25519.GenPrivKey().PubKey().Address(), Power: int64(0)}, + 3, + stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN, + ), + }, + }, + { + Type: ccv.VscMaturedPacket, + Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: ccv.NewVSCMaturedPacketData(3), + }, + }, + } + + // Append all packets to the queue + for _, data := range packetData { + consumerKeeper.AppendPendingPacket(ctx, data.Type, data.Data) + } + storedPacketData := consumerKeeper.GetPendingPackets(ctx) + require.NotEmpty(t, storedPacketData) + require.Equal(t, packetData, storedPacketData) + + slashPacket := ccv.NewSlashPacketData( + abci.Validator{ + Address: ed25519.GenPrivKey().PubKey().Address(), + Power: int64(2), + }, + uint64(4), + stakingtypes.Infraction_INFRACTION_DOWNTIME, + ) + // Append slash packet to expected packet data + packetData = append(packetData, ccv.ConsumerPacketData{ + Type: ccv.SlashPacket, + Data: &ccv.ConsumerPacketData_SlashPacketData{ + SlashPacketData: slashPacket, + }, + }) + + toAppend := packetData[len(packetData)-1] + consumerKeeper.AppendPendingPacket(ctx, toAppend.Type, toAppend.Data) + storedPacketData = consumerKeeper.GetPendingPackets(ctx) + require.NotEmpty(t, storedPacketData) + require.Equal(t, packetData, storedPacketData) + + vscMaturedPacket := ccv.NewVSCMaturedPacketData(4) + packetData = append(packetData, ccv.ConsumerPacketData{ + Type: ccv.VscMaturedPacket, + Data: &ccv.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: vscMaturedPacket, + }, + }) + toAppend = packetData[len(packetData)-1] + consumerKeeper.AppendPendingPacket(ctx, toAppend.Type, toAppend.Data) + + storedPacketData = consumerKeeper.GetPendingPackets(ctx) + require.NotEmpty(t, storedPacketData) + require.Equal(t, packetData, storedPacketData) + + // Delete packet with idx 5 (final index) + consumerKeeper.DeletePendingDataPackets(ctx, 5) + storedPacketData = consumerKeeper.GetPendingPackets(ctx) + require.Equal(t, packetData[:len(packetData)-1], storedPacketData) + pendingPacketsWithIdx := consumerKeeper.GetAllPendingPacketsWithIdx(ctx) + require.Equal(t, uint64(4), pendingPacketsWithIdx[len(pendingPacketsWithIdx)-1].Idx) // final element should have idx 4 + + // Delete packet with idx 0 (first index) + consumerKeeper.DeletePendingDataPackets(ctx, 0) + storedPacketData = consumerKeeper.GetPendingPackets(ctx) + require.Equal(t, packetData[1:len(packetData)-1], storedPacketData) + pendingPacketsWithIdx = consumerKeeper.GetAllPendingPacketsWithIdx(ctx) + require.Equal(t, uint64(1), pendingPacketsWithIdx[0].Idx) // first element should have idx 1 + + // Delete all packets + consumerKeeper.DeleteAllPendingDataPackets(ctx) + storedPacketData = consumerKeeper.GetPendingPackets(ctx) + require.Empty(t, storedPacketData) + require.Empty(t, consumerKeeper.GetAllPendingPacketsWithIdx(ctx)) +} + +// TestVerifyProviderChain tests the VerifyProviderChain method for the consumer keeper +func TestVerifyProviderChain(t *testing.T) { + testCases := []struct { + name string + // State-mutating setup specific to this test case + mockSetup func(sdk.Context, testkeeper.MockedKeepers) + connectionHops []string + expError bool + }{ + { + name: "success", + mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { + gomock.InOrder( + mocks.MockConnectionKeeper.EXPECT().GetConnection( + ctx, "connectionID", + ).Return(conntypes.ConnectionEnd{ClientId: "clientID"}, true).Times(1), + ) + }, + connectionHops: []string{"connectionID"}, + expError: false, + }, + { + name: "connection hops is not length 1", + mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { + // Expect no calls to GetConnection(), VerifyProviderChain will return from first step. + gomock.InAnyOrder( + mocks.MockConnectionKeeper.EXPECT().GetConnection(gomock.Any(), gomock.Any()).Times(0), + ) + }, + connectionHops: []string{"connectionID", "otherConnID"}, + expError: true, + }, + { + name: "connection does not exist", + mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { + gomock.InOrder( + mocks.MockConnectionKeeper.EXPECT().GetConnection( + ctx, "connectionID").Return(conntypes.ConnectionEnd{}, + false, // Found is returned as false + ).Times(1), + ) + }, + connectionHops: []string{"connectionID"}, + expError: true, + }, + { + name: "found clientID does not match expectation", + mockSetup: func(ctx sdk.Context, mocks testkeeper.MockedKeepers) { + gomock.InOrder( + mocks.MockConnectionKeeper.EXPECT().GetConnection( + ctx, "connectionID").Return( + conntypes.ConnectionEnd{ClientId: "unexpectedClientID"}, true, + ).Times(1), + ) + }, + connectionHops: []string{"connectionID"}, + expError: true, + }, + } + + for _, tc := range testCases { + + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + + // Common setup + consumerKeeper.SetProviderClientID(ctx, "clientID") // Set expected provider clientID + + // Specific mock setup + tc.mockSetup(ctx, mocks) + + err := consumerKeeper.VerifyProviderChain(ctx, tc.connectionHops) + + if tc.expError { + require.Error(t, err, "invalid case did not return error") + } else { + require.NoError(t, err, "valid case returned error") + } + ctrl.Finish() + } +} + +// TestGetAllHeightToValsetUpdateIDs tests GetAllHeightToValsetUpdateIDs behaviour correctness +func TestGetAllHeightToValsetUpdateIDs(t *testing.T) { + ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + cases := []ccv.HeightToValsetUpdateID{ + { + ValsetUpdateId: 2, + Height: 22, + }, + { + ValsetUpdateId: 1, + Height: 11, + }, + { + // normal execution should not have two HeightToValsetUpdateID + // with the same ValsetUpdateId, but let's test it anyway + ValsetUpdateId: 1, + Height: 44, + }, + { + ValsetUpdateId: 3, + Height: 33, + }, + } + expectedGetAllOrder := cases + // sorting by Height + sort.Slice(expectedGetAllOrder, func(i, j int) bool { + return expectedGetAllOrder[i].Height < expectedGetAllOrder[j].Height + }) + + for _, c := range cases { + ck.SetHeightValsetUpdateID(ctx, c.Height, c.ValsetUpdateId) + } + + // iterate and check all results are returned + result := ck.GetAllHeightToValsetUpdateIDs(ctx) + require.Len(t, result, len(cases)) + require.Equal(t, expectedGetAllOrder, result) +} + +// TestGetAllOutstandingDowntimes tests GetAllOutstandingDowntimes behaviour correctness +func TestGetAllOutstandingDowntimes(t *testing.T) { + ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + addresses := []sdk.ConsAddress{ + sdk.ConsAddress([]byte("consAddress2")), + sdk.ConsAddress([]byte("consAddress1")), + sdk.ConsAddress([]byte("consAddress4")), + sdk.ConsAddress([]byte("consAddress3")), + } + expectedGetAllOrder := []ccv.OutstandingDowntime{} + for _, addr := range addresses { + expectedGetAllOrder = append(expectedGetAllOrder, ccv.OutstandingDowntime{ValidatorConsensusAddress: addr.String()}) + } + // sorting by ConsAddress + sort.Slice(expectedGetAllOrder, func(i, j int) bool { + return bytes.Compare(addresses[i], addresses[j]) == -1 + }) + + for _, addr := range addresses { + ck.SetOutstandingDowntime(ctx, addr) + } + + // iterate and check all results are returned in the expected order + result := ck.GetAllOutstandingDowntimes(ctx) + require.Len(t, result, len(addresses)) + require.Equal(t, result, expectedGetAllOrder) +} + +func TestPrevStandaloneChainFlag(t *testing.T) { + ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Test that the default value is false + require.False(t, ck.IsPrevStandaloneChain(ctx)) + + // Test that the value can be set and retrieved + ck.MarkAsPrevStandaloneChain(ctx) + require.True(t, ck.IsPrevStandaloneChain(ctx)) +} + +func TestDeleteHeadOfPendingPackets(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // append some pending packets + consumerKeeper.AppendPendingPacket(ctx, ccv.VscMaturedPacket, &ccv.ConsumerPacketData_VscMaturedPacketData{}) + consumerKeeper.AppendPendingPacket(ctx, ccv.SlashPacket, &ccv.ConsumerPacketData_SlashPacketData{}) + consumerKeeper.AppendPendingPacket(ctx, ccv.VscMaturedPacket, &ccv.ConsumerPacketData_VscMaturedPacketData{}) + + // Check there's 3 pending packets, vsc matured at head + pp := consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pp, 3) + require.Equal(t, pp[0].Type, ccv.VscMaturedPacket) + + // Delete the head, confirm slash packet is now at head + consumerKeeper.DeleteHeadOfPendingPackets(ctx) + pp = consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pp, 2) + require.Equal(t, pp[0].Type, ccv.SlashPacket) + + // Delete the head, confirm vsc matured packet is now at head + consumerKeeper.DeleteHeadOfPendingPackets(ctx) + pp = consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pp, 1) + require.Equal(t, pp[0].Type, ccv.VscMaturedPacket) +} diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index fa7068fa29..135966ba33 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -1,503 +1,503 @@ package keeper_test -// import ( -// "fmt" -// "sort" -// "testing" -// "time" - -// clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" -// channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" -// host "github.com/cosmos/ibc-go/v8/modules/core/24-host" -// "github.com/golang/mock/gomock" -// "github.com/stretchr/testify/require" - -// cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" -// "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" -// sdk "github.com/cosmos/cosmos-sdk/types" -// stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -// capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" - -// abci "github.com/cometbft/cometbft/abci/types" - -// "github.com/cosmos/interchain-security/v3/testutil/crypto" -// testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" -// consumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" -// consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" -// "github.com/cosmos/interchain-security/v3/x/ccv/types" -// ) - -// // TestOnRecvVSCPacket tests the behavior of OnRecvVSCPacket over various packet scenarios -// func TestOnRecvVSCPacket(t *testing.T) { -// consumerCCVChannelID := "consumerCCVChannelID" -// providerCCVChannelID := "providerCCVChannelID" - -// pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) -// require.NoError(t, err) -// pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) -// require.NoError(t, err) -// pk3, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) -// require.NoError(t, err) - -// changes1 := []abci.ValidatorUpdate{ -// { -// PubKey: pk1, -// Power: 30, -// }, -// { -// PubKey: pk2, -// Power: 20, -// }, -// } - -// changes2 := []abci.ValidatorUpdate{ -// { -// PubKey: pk2, -// Power: 40, -// }, -// { -// PubKey: pk3, -// Power: 10, -// }, -// } - -// pd := types.NewValidatorSetChangePacketData( -// changes1, -// 1, -// nil, -// ) - -// pd2 := types.NewValidatorSetChangePacketData( -// changes2, -// 2, -// nil, -// ) - -// testCases := []struct { -// name string -// packet channeltypes.Packet -// newChanges types.ValidatorSetChangePacketData -// expectedPendingChanges types.ValidatorSetChangePacketData -// }{ -// { -// "success on first packet", -// channeltypes.NewPacket(pd.GetBytes(), 1, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, -// clienttypes.NewHeight(1, 0), 0), -// types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, -// types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, -// }, -// { -// "success on subsequent packet", -// channeltypes.NewPacket(pd.GetBytes(), 2, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, -// clienttypes.NewHeight(1, 0), 0), -// types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, -// types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, -// }, -// { -// "success on packet with more changes", -// channeltypes.NewPacket(pd2.GetBytes(), 3, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, -// clienttypes.NewHeight(1, 0), 0), -// types.ValidatorSetChangePacketData{ValidatorUpdates: changes2}, -// types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ -// { -// PubKey: pk1, -// Power: 30, -// }, -// { -// PubKey: pk2, -// Power: 40, -// }, -// { -// PubKey: pk3, -// Power: 10, -// }, -// }}, -// }, -// } - -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Set channel to provider, still in context of consumer chain -// consumerKeeper.SetProviderChannel(ctx, consumerCCVChannelID) - -// // Set module params with custom unbonding period -// moduleParams := types.DefaultParams() -// moduleParams.UnbondingPeriod = 100 * time.Hour -// consumerKeeper.SetParams(ctx, moduleParams) - -// for _, tc := range testCases { -// ack := consumerKeeper.OnRecvVSCPacket(ctx, tc.packet, tc.newChanges) - -// require.NotNil(t, ack, "invalid test case: %s did not return ack", tc.name) -// require.True(t, ack.Success(), "invalid test case: %s did not return a Success Acknowledgment", tc.name) -// providerChannel, ok := consumerKeeper.GetProviderChannel(ctx) -// require.True(t, ok) -// require.Equal(t, tc.packet.DestinationChannel, providerChannel, -// "provider channel is not destination channel on successful receive for valid test case: %s", tc.name) - -// // Check that pending changes are accumulated and stored correctly -// actualPendingChanges, ok := consumerKeeper.GetPendingChanges(ctx) -// require.True(t, ok) -// // Sort to avoid dumb inequalities -// sort.SliceStable(actualPendingChanges.ValidatorUpdates, func(i, j int) bool { -// return actualPendingChanges.ValidatorUpdates[i].PubKey.Compare(actualPendingChanges.ValidatorUpdates[j].PubKey) == -1 -// }) -// sort.SliceStable(tc.expectedPendingChanges.ValidatorUpdates, func(i, j int) bool { -// return tc.expectedPendingChanges.ValidatorUpdates[i].PubKey.Compare(tc.expectedPendingChanges.ValidatorUpdates[j].PubKey) == -1 -// }) -// require.Equal(t, tc.expectedPendingChanges, *actualPendingChanges, "pending changes not equal to expected changes after successful packet receive. case: %s", tc.name) - -// expectedTime := ctx.BlockTime().Add(consumerKeeper.GetUnbondingPeriod(ctx)) -// require.True( -// t, -// consumerKeeper.PacketMaturityTimeExists(ctx, tc.newChanges.ValsetUpdateId, expectedTime), -// "no packet maturity time for case: %s", tc.name, -// ) -// } -// } - -// // TestOnRecvVSCPacketDuplicateUpdates tests that the consumer can correctly handle a single VSC packet -// // with duplicate valUpdates for the same pub key. -// // -// // Note: This scenario shouldn't usually happen, ie. the provider shouldn't send duplicate val updates -// // for the same pub key. But it's useful to guard against. -// func TestOnRecvVSCPacketDuplicateUpdates(t *testing.T) { -// // Arbitrary channel IDs -// consumerCCVChannelID := "consumerCCVChannelID" -// providerCCVChannelID := "providerCCVChannelID" - -// // Keeper setup -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() -// consumerKeeper.SetProviderChannel(ctx, consumerCCVChannelID) -// consumerKeeper.SetParams(ctx, types.DefaultParams()) - -// // Construct packet/data with duplicate val updates for the same pub key -// cId := crypto.NewCryptoIdentityFromIntSeed(43278947) -// valUpdates := []abci.ValidatorUpdate{ -// { -// PubKey: cId.TMProtoCryptoPublicKey(), -// Power: 0, -// }, -// { -// PubKey: cId.TMProtoCryptoPublicKey(), -// Power: 473289, -// }, -// } -// vscData := types.NewValidatorSetChangePacketData( -// valUpdates, -// 1, -// nil, -// ) -// packet := channeltypes.NewPacket(vscData.GetBytes(), 2, types.ProviderPortID, -// providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0) - -// // Confirm no pending changes exist before OnRecvVSCPacket -// _, ok := consumerKeeper.GetPendingChanges(ctx) -// require.False(t, ok) - -// // Execute OnRecvVSCPacket -// ack := consumerKeeper.OnRecvVSCPacket(ctx, packet, vscData) -// require.NotNil(t, ack) -// require.True(t, ack.Success()) - -// // Confirm pending changes are queued by OnRecvVSCPacket -// gotPendingChanges, ok := consumerKeeper.GetPendingChanges(ctx) -// require.True(t, ok) - -// // Confirm that only the latest update is kept, duplicate update is discarded -// require.Equal(t, 1, len(gotPendingChanges.ValidatorUpdates)) -// require.Equal(t, valUpdates[1], gotPendingChanges.ValidatorUpdates[0]) // Only latest update should be kept -// } - -// // TestSendPackets tests the SendPackets method failing -// func TestSendPacketsFailure(t *testing.T) { -// // Keeper setup -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() -// consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") -// consumerKeeper.SetParams(ctx, types.DefaultParams()) - -// // Set some pending packets -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{}) -// consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{}) -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{}) - -// // Mock the channel keeper to return an error -// gomock.InOrder( -// mocks.MockChannelKeeper.EXPECT().GetChannel(ctx, types.ConsumerPortID, -// "consumerCCVChannelID").Return(channeltypes.Channel{}, false).Times(1), -// ) - -// // No panic should occur, pending packets should not be cleared -// consumerKeeper.SendPackets(ctx) -// require.Equal(t, 3, len(consumerKeeper.GetPendingPackets(ctx))) -// } - -// func TestSendPackets(t *testing.T) { -// // Keeper setup -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") -// consumerKeeper.SetParams(ctx, types.DefaultParams()) - -// // No slash record should exist -// _, found := consumerKeeper.GetSlashRecord(ctx) -// require.False(t, found) -// require.True(t, consumerKeeper.PacketSendingPermitted(ctx)) - -// // Queue up two vsc matured, followed by slash, followed by vsc matured -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: &types.VSCMaturedPacketData{ -// ValsetUpdateId: 77, -// }, -// }) -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: &types.VSCMaturedPacketData{ -// ValsetUpdateId: 90, -// }, -// }) -// consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ -// SlashPacketData: &types.SlashPacketData{ -// Validator: abci.Validator{}, -// ValsetUpdateId: 88, -// Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, -// }, -// }) -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: &types.VSCMaturedPacketData{ -// ValsetUpdateId: 99, -// }, -// }) - -// // First two vsc matured and slash should be sent, 3 total -// gomock.InAnyOrder( -// testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 3), -// ) -// consumerKeeper.SendPackets(ctx) -// ctrl.Finish() - -// // First two packets should be deleted, slash should be at head of queue -// pendingPackets := consumerKeeper.GetPendingPackets(ctx) -// require.Equal(t, 2, len(pendingPackets)) -// require.Equal(t, types.SlashPacket, pendingPackets[0].Type) -// require.Equal(t, types.VscMaturedPacket, pendingPackets[1].Type) - -// // Packet sending not permitted -// require.False(t, consumerKeeper.PacketSendingPermitted(ctx)) - -// // Now delete slash record as would be done by a recv SlashPacketHandledResult -// // then confirm last vsc matured is sent -// consumerKeeper.ClearSlashRecord(ctx) -// consumerKeeper.DeleteHeadOfPendingPackets(ctx) - -// // Packet sending permitted -// require.True(t, consumerKeeper.PacketSendingPermitted(ctx)) - -// gomock.InAnyOrder( -// testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 1), -// ) - -// consumerKeeper.SendPackets(ctx) -// ctrl.Finish() - -// // No packets should be left -// pendingPackets = consumerKeeper.GetPendingPackets(ctx) -// require.Equal(t, 0, len(pendingPackets)) -// } - -// // TestOnAcknowledgementPacketError tests application logic for ERROR acknowledgments of sent VSCMatured and Slash packets -// // in conjunction with the ibc module's execution of "acknowledgePacket", -// // according to https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#processing-acknowledgements -// func TestOnAcknowledgementPacketError(t *testing.T) { -// // Channel ID to some dest chain that's not the established provider -// channelIDToDestChain := "channelIDToDestChain" - -// // Channel ID to established provider -// channelIDToProvider := "channelIDToProvider" - -// // Channel ID on destination (counter party) chain -// channelIDOnDest := "ChannelIDOnDest" - -// // Instantiate in-mem keeper with mocks -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// keeperParams := testkeeper.NewInMemKeeperParams(t) -// mocks := testkeeper.NewMockedKeepers(ctrl) -// consumerKeeper := testkeeper.NewInMemConsumerKeeper(keeperParams, mocks) -// ctx := keeperParams.Ctx - -// // Set an established provider channel for later in test -// consumerKeeper.SetProviderChannel(ctx, channelIDToProvider) - -// slashPacketData := types.NewSlashPacketData( -// abci.Validator{Address: []byte, Power: int64(1)}, uint64(1), stakingtypes.Infraction_INFRACTION_DOWNTIME, -// ) - -// // The type that'd be JSON marshaled and sent over the wire -// consumerPacketData := types.NewConsumerPacketData( -// types.SlashPacket, -// &types.ConsumerPacketData_SlashPacketData{ -// SlashPacketData: slashPacketData, -// }, -// ) - -// // AcknowledgePacket is in reference to a packet originally sent from this (consumer) module. -// packet := channeltypes.NewPacket( -// consumerPacketData.GetBytes(), -// 1, -// types.ConsumerPortID, // Source port -// channelIDToDestChain, // Source channel -// types.ProviderPortID, // Dest (counter party) port -// channelIDOnDest, // Dest (counter party) channel -// clienttypes.Height{}, -// uint64(time.Now().Add(60*time.Second).UnixNano()), -// ) - -// // Still expect no error returned from OnAcknowledgementPacket, -// // but the input error ack will be handled with appropriate ChanCloseInit calls -// dummyCap := &capabilitytypes.Capability{} -// gomock.InOrder( - -// mocks.MockScopedKeeper.EXPECT().GetCapability( -// ctx, host.ChannelCapabilityPath(types.ConsumerPortID, channelIDToDestChain), -// ).Return(dummyCap, true).Times(1), -// // Due to input error ack, ChanCloseInit is called on channel to destination chain -// mocks.MockChannelKeeper.EXPECT().ChanCloseInit( -// ctx, types.ConsumerPortID, channelIDToDestChain, dummyCap, -// ).Return(nil).Times(1), - -// mocks.MockScopedKeeper.EXPECT().GetCapability( -// ctx, host.ChannelCapabilityPath(types.ConsumerPortID, channelIDToProvider), -// ).Return(dummyCap, true).Times(1), -// // Due to input error ack and existence of established channel to provider, -// // ChanCloseInit is called on channel to provider -// mocks.MockChannelKeeper.EXPECT().ChanCloseInit( -// ctx, types.ConsumerPortID, channelIDToProvider, dummyCap, -// ).Return(nil).Times(1), -// ) - -// ack := types.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("error")) -// err := consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) -// require.Nil(t, err) -// } - -// // TestOnAcknowledgementPacketResult tests application logic for RESULT acknowledgments of sent VSCMatured and Slash packets -// // in conjunction with the ibc module's execution of "acknowledgePacket", -// func TestOnAcknowledgementPacketResult(t *testing.T) { -// // Setup -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// setupSlashBeforeVscMatured(ctx, &consumerKeeper) - -// // Slash record found, 2 pending packets, slash is at head of queue -// _, found := consumerKeeper.GetSlashRecord(ctx) -// require.True(t, found) -// pendingPackets := consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pendingPackets, 2) -// require.Equal(t, types.SlashPacket, pendingPackets[0].Type) - -// // v1 result should delete slash record and head of pending packets. Vsc matured remains -// ack := channeltypes.NewResultAcknowledgement(types.V1Result) -// packet := channeltypes.Packet{Data: pendingPackets[0].GetBytes()} -// err := consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) -// require.Nil(t, err) -// _, found = consumerKeeper.GetSlashRecord(ctx) -// require.False(t, found) -// require.Len(t, consumerKeeper.GetPendingPackets(ctx), 1) -// require.Equal(t, types.VscMaturedPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) - -// // refresh state -// setupSlashBeforeVscMatured(ctx, &consumerKeeper) -// pendingPackets = consumerKeeper.GetPendingPackets(ctx) -// packet = channeltypes.Packet{Data: pendingPackets[0].GetBytes()} - -// // Slash packet handled result should delete slash record and head of pending packets -// ack = channeltypes.NewResultAcknowledgement(types.SlashPacketHandledResult) -// err = consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) -// require.Nil(t, err) -// _, found = consumerKeeper.GetSlashRecord(ctx) -// require.False(t, found) -// require.Len(t, consumerKeeper.GetPendingPackets(ctx), 1) -// require.Equal(t, types.VscMaturedPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) - -// // refresh state -// setupSlashBeforeVscMatured(ctx, &consumerKeeper) -// pendingPackets = consumerKeeper.GetPendingPackets(ctx) -// packet = channeltypes.Packet{Data: pendingPackets[0].GetBytes()} - -// slashRecordBefore, found := consumerKeeper.GetSlashRecord(ctx) -// require.True(t, found) -// require.True(t, slashRecordBefore.WaitingOnReply) - -// // Slash packet bounced result should update slash record -// ack = channeltypes.NewResultAcknowledgement(types.SlashPacketBouncedResult) -// err = consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) -// require.Nil(t, err) -// slashRecordAfter, found := consumerKeeper.GetSlashRecord(ctx) -// require.True(t, found) -// require.False(t, slashRecordAfter.WaitingOnReply) // waiting on reply toggled false -// require.Equal(t, slashRecordAfter.SendTime.UnixNano(), -// slashRecordBefore.SendTime.UnixNano()) // send time NOT updated. Bounce result shouldn't affect that -// } - -// func setupSlashBeforeVscMatured(ctx sdk.Context, k *consumerkeeper.Keeper) { -// // clear old state -// k.ClearSlashRecord(ctx) -// k.DeleteAllPendingDataPackets(ctx) - -// // Set some related state to test against -// k.SetSlashRecord(ctx, consumertypes.SlashRecord{WaitingOnReply: true, SendTime: time.Now()}) -// // Slash packet before VSCMatured packet -// k.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ // Slash appears first -// SlashPacketData: &types.SlashPacketData{ -// Validator: abci.Validator{}, -// ValsetUpdateId: 88, -// Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, -// }, -// }) -// k.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: &types.VSCMaturedPacketData{ -// ValsetUpdateId: 90, -// }, -// }) -// } - -// // Regression test for https://github.com/cosmos/interchain-security/issues/1145 -// func TestSendPacketsDeletion(t *testing.T) { -// // Keeper setup -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() -// consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") -// consumerKeeper.SetParams(ctx, types.DefaultParams()) - -// // Queue two pending packets, vsc matured first -// consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ -// VscMaturedPacketData: &types.VSCMaturedPacketData{ -// ValsetUpdateId: 90, -// }, -// }) -// consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ // Slash appears first -// SlashPacketData: &types.SlashPacketData{ -// Validator: abci.Validator{}, -// ValsetUpdateId: 88, -// Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, -// }, -// }) - -// // Get mocks for the (first) successful SendPacket call that does NOT return an error -// expectations := testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 1) -// // Append mocks for the (second) failed SendPacket call, which returns an error -// expectations = append(expectations, mocks.MockChannelKeeper.EXPECT().GetChannel(ctx, types.ConsumerPortID, -// "consumerCCVChannelID").Return(channeltypes.Channel{}, false).Times(1)) -// gomock.InOrder(expectations...) - -// consumerKeeper.SendPackets(ctx) - -// // Expect the first successfully sent packet to be popped from queue -// require.Equal(t, 1, len(consumerKeeper.GetPendingPackets(ctx))) - -// // Expect the slash packet to remain -// require.Equal(t, types.SlashPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) -// } +import ( + "fmt" + "sort" + "testing" + "time" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/interchain-security/v3/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" + consumerkeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + "github.com/cosmos/interchain-security/v3/x/ccv/types" +) + +// TestOnRecvVSCPacket tests the behavior of OnRecvVSCPacket over various packet scenarios +func TestOnRecvVSCPacket(t *testing.T) { + consumerCCVChannelID := "consumerCCVChannelID" + providerCCVChannelID := "providerCCVChannelID" + + pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + require.NoError(t, err) + pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + require.NoError(t, err) + pk3, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + require.NoError(t, err) + + changes1 := []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 20, + }, + } + + changes2 := []abci.ValidatorUpdate{ + { + PubKey: pk2, + Power: 40, + }, + { + PubKey: pk3, + Power: 10, + }, + } + + pd := types.NewValidatorSetChangePacketData( + changes1, + 1, + nil, + ) + + pd2 := types.NewValidatorSetChangePacketData( + changes2, + 2, + nil, + ) + + testCases := []struct { + name string + packet channeltypes.Packet + newChanges types.ValidatorSetChangePacketData + expectedPendingChanges types.ValidatorSetChangePacketData + }{ + { + "success on first packet", + channeltypes.NewPacket(pd.GetBytes(), 1, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, + clienttypes.NewHeight(1, 0), 0), + types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, + types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, + }, + { + "success on subsequent packet", + channeltypes.NewPacket(pd.GetBytes(), 2, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, + clienttypes.NewHeight(1, 0), 0), + types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, + types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, + }, + { + "success on packet with more changes", + channeltypes.NewPacket(pd2.GetBytes(), 3, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, + clienttypes.NewHeight(1, 0), 0), + types.ValidatorSetChangePacketData{ValidatorUpdates: changes2}, + types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 40, + }, + { + PubKey: pk3, + Power: 10, + }, + }}, + }, + } + + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Set channel to provider, still in context of consumer chain + consumerKeeper.SetProviderChannel(ctx, consumerCCVChannelID) + + // Set module params with custom unbonding period + moduleParams := types.DefaultParams() + moduleParams.UnbondingPeriod = 100 * time.Hour + consumerKeeper.SetParams(ctx, moduleParams) + + for _, tc := range testCases { + ack := consumerKeeper.OnRecvVSCPacket(ctx, tc.packet, tc.newChanges) + + require.NotNil(t, ack, "invalid test case: %s did not return ack", tc.name) + require.True(t, ack.Success(), "invalid test case: %s did not return a Success Acknowledgment", tc.name) + providerChannel, ok := consumerKeeper.GetProviderChannel(ctx) + require.True(t, ok) + require.Equal(t, tc.packet.DestinationChannel, providerChannel, + "provider channel is not destination channel on successful receive for valid test case: %s", tc.name) + + // Check that pending changes are accumulated and stored correctly + actualPendingChanges, ok := consumerKeeper.GetPendingChanges(ctx) + require.True(t, ok) + // Sort to avoid dumb inequalities + sort.SliceStable(actualPendingChanges.ValidatorUpdates, func(i, j int) bool { + return actualPendingChanges.ValidatorUpdates[i].PubKey.Compare(actualPendingChanges.ValidatorUpdates[j].PubKey) == -1 + }) + sort.SliceStable(tc.expectedPendingChanges.ValidatorUpdates, func(i, j int) bool { + return tc.expectedPendingChanges.ValidatorUpdates[i].PubKey.Compare(tc.expectedPendingChanges.ValidatorUpdates[j].PubKey) == -1 + }) + require.Equal(t, tc.expectedPendingChanges, *actualPendingChanges, "pending changes not equal to expected changes after successful packet receive. case: %s", tc.name) + + expectedTime := ctx.BlockTime().Add(consumerKeeper.GetUnbondingPeriod(ctx)) + require.True( + t, + consumerKeeper.PacketMaturityTimeExists(ctx, tc.newChanges.ValsetUpdateId, expectedTime), + "no packet maturity time for case: %s", tc.name, + ) + } +} + +// TestOnRecvVSCPacketDuplicateUpdates tests that the consumer can correctly handle a single VSC packet +// with duplicate valUpdates for the same pub key. +// +// Note: This scenario shouldn't usually happen, ie. the provider shouldn't send duplicate val updates +// for the same pub key. But it's useful to guard against. +func TestOnRecvVSCPacketDuplicateUpdates(t *testing.T) { + // Arbitrary channel IDs + consumerCCVChannelID := "consumerCCVChannelID" + providerCCVChannelID := "providerCCVChannelID" + + // Keeper setup + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + consumerKeeper.SetProviderChannel(ctx, consumerCCVChannelID) + consumerKeeper.SetParams(ctx, types.DefaultParams()) + + // Construct packet/data with duplicate val updates for the same pub key + cId := crypto.NewCryptoIdentityFromIntSeed(43278947) + valUpdates := []abci.ValidatorUpdate{ + { + PubKey: cId.TMProtoCryptoPublicKey(), + Power: 0, + }, + { + PubKey: cId.TMProtoCryptoPublicKey(), + Power: 473289, + }, + } + vscData := types.NewValidatorSetChangePacketData( + valUpdates, + 1, + nil, + ) + packet := channeltypes.NewPacket(vscData.GetBytes(), 2, types.ProviderPortID, + providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0) + + // Confirm no pending changes exist before OnRecvVSCPacket + _, ok := consumerKeeper.GetPendingChanges(ctx) + require.False(t, ok) + + // Execute OnRecvVSCPacket + ack := consumerKeeper.OnRecvVSCPacket(ctx, packet, vscData) + require.NotNil(t, ack) + require.True(t, ack.Success()) + + // Confirm pending changes are queued by OnRecvVSCPacket + gotPendingChanges, ok := consumerKeeper.GetPendingChanges(ctx) + require.True(t, ok) + + // Confirm that only the latest update is kept, duplicate update is discarded + require.Equal(t, 1, len(gotPendingChanges.ValidatorUpdates)) + require.Equal(t, valUpdates[1], gotPendingChanges.ValidatorUpdates[0]) // Only latest update should be kept +} + +// TestSendPackets tests the SendPackets method failing +func TestSendPacketsFailure(t *testing.T) { + // Keeper setup + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") + consumerKeeper.SetParams(ctx, types.DefaultParams()) + + // Set some pending packets + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{}) + consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{}) + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{}) + + // Mock the channel keeper to return an error + gomock.InOrder( + mocks.MockChannelKeeper.EXPECT().GetChannel(ctx, types.ConsumerPortID, + "consumerCCVChannelID").Return(channeltypes.Channel{}, false).Times(1), + ) + + // No panic should occur, pending packets should not be cleared + consumerKeeper.SendPackets(ctx) + require.Equal(t, 3, len(consumerKeeper.GetPendingPackets(ctx))) +} + +func TestSendPackets(t *testing.T) { + // Keeper setup + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") + consumerKeeper.SetParams(ctx, types.DefaultParams()) + + // No slash record should exist + _, found := consumerKeeper.GetSlashRecord(ctx) + require.False(t, found) + require.True(t, consumerKeeper.PacketSendingPermitted(ctx)) + + // Queue up two vsc matured, followed by slash, followed by vsc matured + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: &types.VSCMaturedPacketData{ + ValsetUpdateId: 77, + }, + }) + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: &types.VSCMaturedPacketData{ + ValsetUpdateId: 90, + }, + }) + consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ + SlashPacketData: &types.SlashPacketData{ + Validator: abci.Validator{}, + ValsetUpdateId: 88, + Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, + }, + }) + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: &types.VSCMaturedPacketData{ + ValsetUpdateId: 99, + }, + }) + + // First two vsc matured and slash should be sent, 3 total + gomock.InAnyOrder( + testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 3), + ) + consumerKeeper.SendPackets(ctx) + ctrl.Finish() + + // First two packets should be deleted, slash should be at head of queue + pendingPackets := consumerKeeper.GetPendingPackets(ctx) + require.Equal(t, 2, len(pendingPackets)) + require.Equal(t, types.SlashPacket, pendingPackets[0].Type) + require.Equal(t, types.VscMaturedPacket, pendingPackets[1].Type) + + // Packet sending not permitted + require.False(t, consumerKeeper.PacketSendingPermitted(ctx)) + + // Now delete slash record as would be done by a recv SlashPacketHandledResult + // then confirm last vsc matured is sent + consumerKeeper.ClearSlashRecord(ctx) + consumerKeeper.DeleteHeadOfPendingPackets(ctx) + + // Packet sending permitted + require.True(t, consumerKeeper.PacketSendingPermitted(ctx)) + + gomock.InAnyOrder( + testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 1), + ) + + consumerKeeper.SendPackets(ctx) + ctrl.Finish() + + // No packets should be left + pendingPackets = consumerKeeper.GetPendingPackets(ctx) + require.Equal(t, 0, len(pendingPackets)) +} + +// TestOnAcknowledgementPacketError tests application logic for ERROR acknowledgments of sent VSCMatured and Slash packets +// in conjunction with the ibc module's execution of "acknowledgePacket", +// according to https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#processing-acknowledgements +func TestOnAcknowledgementPacketError(t *testing.T) { + // Channel ID to some dest chain that's not the established provider + channelIDToDestChain := "channelIDToDestChain" + + // Channel ID to established provider + channelIDToProvider := "channelIDToProvider" + + // Channel ID on destination (counter party) chain + channelIDOnDest := "ChannelIDOnDest" + + // Instantiate in-mem keeper with mocks + ctrl := gomock.NewController(t) + defer ctrl.Finish() + keeperParams := testkeeper.NewInMemKeeperParams(t) + mocks := testkeeper.NewMockedKeepers(ctrl) + consumerKeeper := testkeeper.NewInMemConsumerKeeper(keeperParams, mocks) + ctx := keeperParams.Ctx + + // Set an established provider channel for later in test + consumerKeeper.SetProviderChannel(ctx, channelIDToProvider) + + slashPacketData := types.NewSlashPacketData( + abci.Validator{Address: []byte{}, Power: int64(1)}, uint64(1), stakingtypes.Infraction_INFRACTION_DOWNTIME, + ) + + // The type that'd be JSON marshaled and sent over the wire + consumerPacketData := types.NewConsumerPacketData( + types.SlashPacket, + &types.ConsumerPacketData_SlashPacketData{ + SlashPacketData: slashPacketData, + }, + ) + + // AcknowledgePacket is in reference to a packet originally sent from this (consumer) module. + packet := channeltypes.NewPacket( + consumerPacketData.GetBytes(), + 1, + types.ConsumerPortID, // Source port + channelIDToDestChain, // Source channel + types.ProviderPortID, // Dest (counter party) port + channelIDOnDest, // Dest (counter party) channel + clienttypes.Height{}, + uint64(time.Now().Add(60*time.Second).UnixNano()), + ) + + // Still expect no error returned from OnAcknowledgementPacket, + // but the input error ack will be handled with appropriate ChanCloseInit calls + dummyCap := &capabilitytypes.Capability{} + gomock.InOrder( + + mocks.MockScopedKeeper.EXPECT().GetCapability( + ctx, host.ChannelCapabilityPath(types.ConsumerPortID, channelIDToDestChain), + ).Return(dummyCap, true).Times(1), + // Due to input error ack, ChanCloseInit is called on channel to destination chain + mocks.MockChannelKeeper.EXPECT().ChanCloseInit( + ctx, types.ConsumerPortID, channelIDToDestChain, dummyCap, + ).Return(nil).Times(1), + + mocks.MockScopedKeeper.EXPECT().GetCapability( + ctx, host.ChannelCapabilityPath(types.ConsumerPortID, channelIDToProvider), + ).Return(dummyCap, true).Times(1), + // Due to input error ack and existence of established channel to provider, + // ChanCloseInit is called on channel to provider + mocks.MockChannelKeeper.EXPECT().ChanCloseInit( + ctx, types.ConsumerPortID, channelIDToProvider, dummyCap, + ).Return(nil).Times(1), + ) + + ack := types.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("error")) + err := consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) + require.Nil(t, err) +} + +// TestOnAcknowledgementPacketResult tests application logic for RESULT acknowledgments of sent VSCMatured and Slash packets +// in conjunction with the ibc module's execution of "acknowledgePacket", +func TestOnAcknowledgementPacketResult(t *testing.T) { + // Setup + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + setupSlashBeforeVscMatured(ctx, &consumerKeeper) + + // Slash record found, 2 pending packets, slash is at head of queue + _, found := consumerKeeper.GetSlashRecord(ctx) + require.True(t, found) + pendingPackets := consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pendingPackets, 2) + require.Equal(t, types.SlashPacket, pendingPackets[0].Type) + + // v1 result should delete slash record and head of pending packets. Vsc matured remains + ack := channeltypes.NewResultAcknowledgement(types.V1Result) + packet := channeltypes.Packet{Data: pendingPackets[0].GetBytes()} + err := consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) + require.Nil(t, err) + _, found = consumerKeeper.GetSlashRecord(ctx) + require.False(t, found) + require.Len(t, consumerKeeper.GetPendingPackets(ctx), 1) + require.Equal(t, types.VscMaturedPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) + + // refresh state + setupSlashBeforeVscMatured(ctx, &consumerKeeper) + pendingPackets = consumerKeeper.GetPendingPackets(ctx) + packet = channeltypes.Packet{Data: pendingPackets[0].GetBytes()} + + // Slash packet handled result should delete slash record and head of pending packets + ack = channeltypes.NewResultAcknowledgement(types.SlashPacketHandledResult) + err = consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) + require.Nil(t, err) + _, found = consumerKeeper.GetSlashRecord(ctx) + require.False(t, found) + require.Len(t, consumerKeeper.GetPendingPackets(ctx), 1) + require.Equal(t, types.VscMaturedPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) + + // refresh state + setupSlashBeforeVscMatured(ctx, &consumerKeeper) + pendingPackets = consumerKeeper.GetPendingPackets(ctx) + packet = channeltypes.Packet{Data: pendingPackets[0].GetBytes()} + + slashRecordBefore, found := consumerKeeper.GetSlashRecord(ctx) + require.True(t, found) + require.True(t, slashRecordBefore.WaitingOnReply) + + // Slash packet bounced result should update slash record + ack = channeltypes.NewResultAcknowledgement(types.SlashPacketBouncedResult) + err = consumerKeeper.OnAcknowledgementPacket(ctx, packet, ack) + require.Nil(t, err) + slashRecordAfter, found := consumerKeeper.GetSlashRecord(ctx) + require.True(t, found) + require.False(t, slashRecordAfter.WaitingOnReply) // waiting on reply toggled false + require.Equal(t, slashRecordAfter.SendTime.UnixNano(), + slashRecordBefore.SendTime.UnixNano()) // send time NOT updated. Bounce result shouldn't affect that +} + +func setupSlashBeforeVscMatured(ctx sdk.Context, k *consumerkeeper.Keeper) { + // clear old state + k.ClearSlashRecord(ctx) + k.DeleteAllPendingDataPackets(ctx) + + // Set some related state to test against + k.SetSlashRecord(ctx, consumertypes.SlashRecord{WaitingOnReply: true, SendTime: time.Now()}) + // Slash packet before VSCMatured packet + k.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ // Slash appears first + SlashPacketData: &types.SlashPacketData{ + Validator: abci.Validator{}, + ValsetUpdateId: 88, + Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, + }, + }) + k.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: &types.VSCMaturedPacketData{ + ValsetUpdateId: 90, + }, + }) +} + +// Regression test for https://github.com/cosmos/interchain-security/issues/1145 +func TestSendPacketsDeletion(t *testing.T) { + // Keeper setup + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + consumerKeeper.SetProviderChannel(ctx, "consumerCCVChannelID") + consumerKeeper.SetParams(ctx, types.DefaultParams()) + + // Queue two pending packets, vsc matured first + consumerKeeper.AppendPendingPacket(ctx, types.VscMaturedPacket, &types.ConsumerPacketData_VscMaturedPacketData{ + VscMaturedPacketData: &types.VSCMaturedPacketData{ + ValsetUpdateId: 90, + }, + }) + consumerKeeper.AppendPendingPacket(ctx, types.SlashPacket, &types.ConsumerPacketData_SlashPacketData{ // Slash appears first + SlashPacketData: &types.SlashPacketData{ + Validator: abci.Validator{}, + ValsetUpdateId: 88, + Infraction: stakingtypes.Infraction_INFRACTION_DOWNTIME, + }, + }) + + // Get mocks for the (first) successful SendPacket call that does NOT return an error + expectations := testkeeper.GetMocksForSendIBCPacket(ctx, mocks, "consumerCCVChannelID", 1) + // Append mocks for the (second) failed SendPacket call, which returns an error + expectations = append(expectations, mocks.MockChannelKeeper.EXPECT().GetChannel(ctx, types.ConsumerPortID, + "consumerCCVChannelID").Return(channeltypes.Channel{}, false).Times(1)) + gomock.InOrder(expectations...) + + consumerKeeper.SendPackets(ctx) + + // Expect the first successfully sent packet to be popped from queue + require.Equal(t, 1, len(consumerKeeper.GetPendingPackets(ctx))) + + // Expect the slash packet to remain + require.Equal(t, types.SlashPacket, consumerKeeper.GetPendingPackets(ctx)[0].Type) +} diff --git a/x/ccv/consumer/keeper/soft_opt_out_test.go b/x/ccv/consumer/keeper/soft_opt_out_test.go index ce5aa62fb0..252bec0154 100644 --- a/x/ccv/consumer/keeper/soft_opt_out_test.go +++ b/x/ccv/consumer/keeper/soft_opt_out_test.go @@ -1,108 +1,118 @@ package keeper_test -// // Tests that UpdateSmallestNonOptOutPower updates the smallest validator power that cannot soft opt out. -// // Soft opt out allows the bottom [SoftOptOutThreshold] portion of validators in the set to opt out. -// // UpdateSmallestNonOptOutPower should update the smallest validator power that cannot opt out. -// func TestUpdateSmallestNonOptOutPower(t *testing.T) { -// cIds := crypto.GenMultipleCryptoIds(7, 682934679238) +import ( + "testing" -// testCases := []struct { -// name string -// // soft opt out threshold set as param -// optOutThresh string -// // validators to set in store -// validators []*tmtypes.Validator -// // expected smallest power of validator which cannot opt out -// expSmallestNonOptOutValPower int64 -// }{ -// { -// name: "One", -// optOutThresh: "0.05", -// validators: []*tmtypes.Validator{ -// tmtypes.NewValidator(cIds[0].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[1].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[3].TMCryptoPubKey(), 3), -// tmtypes.NewValidator(cIds[4].TMCryptoPubKey(), 49), -// tmtypes.NewValidator(cIds[5].TMCryptoPubKey(), 51), -// }, -// // 107 total power, validator with 3 power passes 0.05 threshold (6 / 107 = 0.056) and cannot opt out -// expSmallestNonOptOutValPower: 3, -// }, -// { -// name: "One in different order", -// optOutThresh: "0.05", -// validators: []*tmtypes.Validator{ -// tmtypes.NewValidator(cIds[0].TMCryptoPubKey(), 3), -// tmtypes.NewValidator(cIds[1].TMCryptoPubKey(), 51), -// tmtypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[3].TMCryptoPubKey(), 49), -// tmtypes.NewValidator(cIds[4].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), -// }, -// // Same result as first test case, just confirms order of validators doesn't matter -// expSmallestNonOptOutValPower: 3, -// }, -// { -// name: "Two", -// optOutThresh: "0.05", -// validators: []*tmtypes.Validator{ -// tmtypes.NewValidator(cIds[0].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[1].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[3].TMCryptoPubKey(), 3), -// tmtypes.NewValidator(cIds[4].TMCryptoPubKey(), 500), -// }, -// // 506 total power, validator with 500 passes 0.05 threshold and cannot opt out -// expSmallestNonOptOutValPower: 500, -// }, -// { -// name: "Three", -// optOutThresh: "0.199999", -// validators: []*tmtypes.Validator{ -// tmtypes.NewValidator(cIds[0].TMCryptoPubKey(), 54), -// tmtypes.NewValidator(cIds[1].TMCryptoPubKey(), 53), -// tmtypes.NewValidator(cIds[2].TMCryptoPubKey(), 52), -// tmtypes.NewValidator(cIds[3].TMCryptoPubKey(), 51), -// tmtypes.NewValidator(cIds[4].TMCryptoPubKey(), 50), -// tmtypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[6].TMCryptoPubKey(), 1), -// }, -// // 262 total power, (50 + 1 + 1) / 262 ~= 0.19, validator with 51 passes 0.199999 threshold and cannot opt out -// expSmallestNonOptOutValPower: 51, -// }, -// { -// name: "soft opt-out disabled", -// optOutThresh: "0", -// validators: []*tmtypes.Validator{ -// tmtypes.NewValidator(cIds[0].TMCryptoPubKey(), 54), -// tmtypes.NewValidator(cIds[1].TMCryptoPubKey(), 53), -// tmtypes.NewValidator(cIds[2].TMCryptoPubKey(), 52), -// tmtypes.NewValidator(cIds[3].TMCryptoPubKey(), 51), -// tmtypes.NewValidator(cIds[4].TMCryptoPubKey(), 50), -// tmtypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), -// tmtypes.NewValidator(cIds[6].TMCryptoPubKey(), 1), -// }, -// expSmallestNonOptOutValPower: 0, -// }, -// } + cmttypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/interchain-security/v3/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" + "github.com/stretchr/testify/require" +) -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// moduleParams := ccvtypes.DefaultParams() -// moduleParams.SoftOptOutThreshold = tc.optOutThresh -// consumerKeeper.SetParams(ctx, moduleParams) -// defer ctrl.Finish() +// Tests that UpdateSmallestNonOptOutPower updates the smallest validator power that cannot soft opt out. +// Soft opt out allows the bottom [SoftOptOutThreshold] portion of validators in the set to opt out. +// UpdateSmallestNonOptOutPower should update the smallest validator power that cannot opt out. +func TestUpdateSmallestNonOptOutPower(t *testing.T) { + cIds := crypto.GenMultipleCryptoIds(7, 682934679238) -// // set validators in store -// SetCCValidators(t, consumerKeeper, ctx, tc.validators) + testCases := []struct { + name string + // soft opt out threshold set as param + optOutThresh string + // validators to set in store + validators []*cmttypes.Validator + // expected smallest power of validator which cannot opt out + expSmallestNonOptOutValPower int64 + }{ + { + name: "One", + optOutThresh: "0.05", + validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cIds[0].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[1].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[3].TMCryptoPubKey(), 3), + cmttypes.NewValidator(cIds[4].TMCryptoPubKey(), 49), + cmttypes.NewValidator(cIds[5].TMCryptoPubKey(), 51), + }, + // 107 total power, validator with 3 power passes 0.05 threshold (6 / 107 = 0.056) and cannot opt out + expSmallestNonOptOutValPower: 3, + }, + { + name: "One in different order", + optOutThresh: "0.05", + validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cIds[0].TMCryptoPubKey(), 3), + cmttypes.NewValidator(cIds[1].TMCryptoPubKey(), 51), + cmttypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[3].TMCryptoPubKey(), 49), + cmttypes.NewValidator(cIds[4].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), + }, + // Same result as first test case, just confirms order of validators doesn't matter + expSmallestNonOptOutValPower: 3, + }, + { + name: "Two", + optOutThresh: "0.05", + validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cIds[0].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[1].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[2].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[3].TMCryptoPubKey(), 3), + cmttypes.NewValidator(cIds[4].TMCryptoPubKey(), 500), + }, + // 506 total power, validator with 500 passes 0.05 threshold and cannot opt out + expSmallestNonOptOutValPower: 500, + }, + { + name: "Three", + optOutThresh: "0.199999", + validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cIds[0].TMCryptoPubKey(), 54), + cmttypes.NewValidator(cIds[1].TMCryptoPubKey(), 53), + cmttypes.NewValidator(cIds[2].TMCryptoPubKey(), 52), + cmttypes.NewValidator(cIds[3].TMCryptoPubKey(), 51), + cmttypes.NewValidator(cIds[4].TMCryptoPubKey(), 50), + cmttypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[6].TMCryptoPubKey(), 1), + }, + // 262 total power, (50 + 1 + 1) / 262 ~= 0.19, validator with 51 passes 0.199999 threshold and cannot opt out + expSmallestNonOptOutValPower: 51, + }, + { + name: "soft opt-out disabled", + optOutThresh: "0", + validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cIds[0].TMCryptoPubKey(), 54), + cmttypes.NewValidator(cIds[1].TMCryptoPubKey(), 53), + cmttypes.NewValidator(cIds[2].TMCryptoPubKey(), 52), + cmttypes.NewValidator(cIds[3].TMCryptoPubKey(), 51), + cmttypes.NewValidator(cIds[4].TMCryptoPubKey(), 50), + cmttypes.NewValidator(cIds[5].TMCryptoPubKey(), 1), + cmttypes.NewValidator(cIds[6].TMCryptoPubKey(), 1), + }, + expSmallestNonOptOutValPower: 0, + }, + } -// // update smallest power of validator which cannot opt out -// consumerKeeper.UpdateSmallestNonOptOutPower(ctx) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + moduleParams := ccvtypes.DefaultParams() + moduleParams.SoftOptOutThreshold = tc.optOutThresh + consumerKeeper.SetParams(ctx, moduleParams) + defer ctrl.Finish() -// // expect smallest power of validator which cannot opt out to be updated -// require.Equal(t, tc.expSmallestNonOptOutValPower, consumerKeeper.GetSmallestNonOptOutPower(ctx)) -// }) -// } -// } + // set validators in store + SetCCValidators(t, consumerKeeper, ctx, tc.validators) + + // update smallest power of validator which cannot opt out + consumerKeeper.UpdateSmallestNonOptOutPower(ctx) + + // expect smallest power of validator which cannot opt out to be updated + require.Equal(t, tc.expSmallestNonOptOutValPower, consumerKeeper.GetSmallestNonOptOutPower(ctx)) + }) + } +} diff --git a/x/ccv/consumer/keeper/validators_test.go b/x/ccv/consumer/keeper/validators_test.go index 508f639ec5..91009457a4 100644 --- a/x/ccv/consumer/keeper/validators_test.go +++ b/x/ccv/consumer/keeper/validators_test.go @@ -1,279 +1,299 @@ package keeper_test -// import ( -// "testing" - -// "github.com/stretchr/testify/require" - -// "cosmossdk.io/math" - -// cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" -// sdk "github.com/cosmos/cosmos-sdk/types" -// stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - -// abci "github.com/cometbft/cometbft/abci/types" -// tmtypes "github.com/cometbft/cometbft/types" - -// "github.com/cosmos/interchain-security/v3/testutil/crypto" -// testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" -// "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" -// "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" -// ) - -// // TestApplyCCValidatorChanges tests the ApplyCCValidatorChanges method for a consumer keeper -// func TestApplyCCValidatorChanges(t *testing.T) { -// keeperParams := testkeeper.NewInMemKeeperParams(t) -// consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) -// defer ctrl.Finish() - -// // utility functions -// getCCVals := func() (vals []types.CrossChainValidator) { -// vals = consumerKeeper.GetAllCCValidator(ctx) -// return -// } - -// clearCCVals := func() { -// ccVals := consumerKeeper.GetAllCCValidator(ctx) -// for _, v := range ccVals { -// consumerKeeper.DeleteCCValidator(ctx, v.Address) -// } -// } - -// sumCCValsPow := func(vals []types.CrossChainValidator) (power int64) { -// for _, v := range vals { -// power += v.Power -// } -// return -// } - -// // prepare the testing setup by clearing the current cross-chain validators in states -// clearCCVals() - -// tcValidators := GenerateValidators(t) - -// changes := []abci.ValidatorUpdate{} -// changesPower := int64(0) - -// for _, v := range tcValidators { -// changes = append(changes, tmtypes.TM2PB.ValidatorUpdate(v)) -// changesPower += v.VotingPower -// } - -// // finish setup by storing 3 out 4 testing validators as cross-chain validator records -// SetCCValidators(t, consumerKeeper, ctx, tcValidators[:len(tcValidators)-1]) - -// // verify setup -// ccVals := getCCVals() -// require.Len(t, ccVals, len(tcValidators)-1) - -// // test behaviors -// testCases := []struct { -// changes []abci.ValidatorUpdate -// expTotalPower int64 -// expValsNum int -// }{ -// { // add new bonded validator -// changes: changes[len(changes)-1:], -// expTotalPower: changesPower, -// expValsNum: len(ccVals) + 1, -// }, -// { // update a validator voting power -// changes: []abci.ValidatorUpdate{{PubKey: changes[0].PubKey, Power: changes[0].Power + 3}}, -// expTotalPower: changesPower + 3, -// expValsNum: len(ccVals) + 1, -// }, -// { // unbond a validator -// changes: []abci.ValidatorUpdate{{PubKey: changes[0].PubKey, Power: 0}}, -// expTotalPower: changesPower - changes[0].Power, -// expValsNum: len(ccVals), -// }, -// { // update all validators voting power -// changes: []abci.ValidatorUpdate{ -// {PubKey: changes[0].PubKey, Power: changes[0].Power + 1}, -// {PubKey: changes[1].PubKey, Power: changes[1].Power + 2}, -// {PubKey: changes[2].PubKey, Power: changes[2].Power + 3}, -// {PubKey: changes[3].PubKey, Power: changes[3].Power + 4}, -// }, -// expTotalPower: changesPower + 10, -// expValsNum: len(ccVals) + 1, -// }, -// } - -// for _, tc := range testCases { - -// consumerKeeper.ApplyCCValidatorChanges(ctx, tc.changes) -// gotVals := getCCVals() - -// require.Len(t, gotVals, tc.expValsNum) -// require.Equal(t, tc.expTotalPower, sumCCValsPow(gotVals)) -// } -// } - -// // TestIsValidatorJailed tests the IsValidatorJailed method for a consumer keeper -// func TestIsValidatorJailed(t *testing.T) { -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // Consumer keeper from test setup should return false for IsPrevStandaloneChain() -// require.False(t, consumerKeeper.IsPrevStandaloneChain(ctx)) - -// // IsValidatorJailed should return false for an arbitrary consensus address -// consAddr := []byte{0x01, 0x02, 0x03} -// require.False(t, consumerKeeper.IsValidatorJailed(ctx, consAddr)) - -// // Set outstanding downtime for that addr -// consumerKeeper.SetOutstandingDowntime(ctx, consAddr) - -// // Now confirm IsValidatorJailed returns true -// require.True(t, consumerKeeper.IsValidatorJailed(ctx, consAddr)) - -// // Next, we set a value for the standalone staking keeper, -// // and mark the consumer keeper as being from a previous standalone chain -// consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) -// consumerKeeper.MarkAsPrevStandaloneChain(ctx) -// require.True(t, consumerKeeper.IsPrevStandaloneChain(ctx)) - -// // Set init genesis height to current block height so that ChangeoverIsComplete() is false -// consumerKeeper.SetInitGenesisHeight(ctx, ctx.BlockHeight()) -// require.False(t, consumerKeeper.ChangeoverIsComplete(ctx)) - -// // At this point, the state of the consumer keeper is s.t. IsValidatorJailed() queries the standalone staking keeper - -// // Now mock that a validator is jailed from the standalone staking keeper -// mocks.MockStakingKeeper.EXPECT().IsValidatorJailed(ctx, consAddr).Return(true).Times(1) - -// // Confirm IsValidatorJailed returns true -// require.True(t, consumerKeeper.IsValidatorJailed(ctx, consAddr)) -// } - -// func TestSlash(t *testing.T) { -// consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) -// defer ctrl.Finish() - -// // If we call slash with infraction type empty, no slash packet will be queued -// consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, sdk.NewDec(9.0), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) -// pendingPackets := consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pendingPackets, 0) - -// // Consumer keeper from test setup should return false for IsPrevStandaloneChain() -// require.False(t, consumerKeeper.IsPrevStandaloneChain(ctx)) - -// // Now setup a value for vscID mapped to infraction height -// consumerKeeper.SetHeightValsetUpdateID(ctx, 5, 6) - -// // Call slash with valid infraction type and confirm 1 slash packet is queued -// consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, sdk.NewDec(9.0), stakingtypes.Infraction_INFRACTION_DOWNTIME) -// pendingPackets = consumerKeeper.GetPendingPackets(ctx) -// require.Len(t, pendingPackets, 1) - -// // Next, we set a value for the standalone staking keeper, -// // and mark the consumer keeper as being from a previous standalone chain -// consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) -// consumerKeeper.MarkAsPrevStandaloneChain(ctx) -// require.True(t, consumerKeeper.IsPrevStandaloneChain(ctx)) - -// // At this point, the state of the consumer keeper is s.t. -// // Slash() calls the standalone staking keeper's Slash() - -// // If we call slash with infraction type empty, standalone staking keeper's slash will not be called -// // (if it was called, test would panic without mocking the call) -// consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, math.LegacyNewDec(9.0), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) - -// // Now setup a mock for Slash, and confirm that it is called against -// // standalone staking keeper with valid infraction type -// infractionHeight := int64(5) -// mocks.MockStakingKeeper.EXPECT().SlashWithInfractionReason( -// ctx, []byte{0x01, 0x02, 0x03}, infractionHeight, int64(6), -// math.LegacyMustNewDecFromStr("0.05"), stakingtypes.Infraction_INFRACTION_UNSPECIFIED).Times(1) // We pass empty infraction to standalone staking keeper since it's not used - -// // Also setup init genesis height s.t. infraction height is before first consumer height -// consumerKeeper.SetInitGenesisHeight(ctx, 4) -// require.Equal(t, consumerKeeper.FirstConsumerHeight(ctx), int64(6)) - -// consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, infractionHeight, 6, -// math.LegacyMustNewDecFromStr("0.05"), stakingtypes.Infraction_INFRACTION_DOWNTIME) -// } - -// // Tests the getter and setter behavior for historical info -// // func TestHistoricalInfo(t *testing.T) { -// // keeperParams := testkeeper.NewInMemKeeperParams(t) -// // consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) -// // defer ctrl.Finish() -// // ctx = ctx.WithBlockHeight(15) - -// // // Generate test validators, save them to store, and retrieve stored records -// // validators := GenerateValidators(t) -// // SetCCValidators(t, consumerKeeper, ctx, validators) -// // ccValidators := consumerKeeper.GetAllCCValidator(ctx) -// // require.Len(t, ccValidators, len(validators)) - -// // // iterate over validators and convert them to staking type -// // sVals := []stakingtypes.Validator{} -// // for _, v := range ccValidators { -// // pk, err := v.ConsPubKey() -// // require.NoError(t, err) - -// // val, err := stakingtypes.NewValidator(nil, pk, stakingtypes.Description{}) -// // require.NoError(t, err) - -// // // set voting power to random value -// // val.Tokens = sdk.TokensFromConsensusPower(tmrand.NewRand().Int64(), sdk.DefaultPowerReduction) -// // sVals = append(sVals, val) -// // } - -// // currentHeight := ctx.BlockHeight() - -// // // create and store historical info -// // hi := stakingtypes.NewHistoricalInfo(ctx.BlockHeader(), sVals, sdk.DefaultPowerReduction) -// // consumerKeeper.SetHistoricalInfo(ctx, currentHeight, &hi) - -// // // expect to get historical info -// // recv, found := consumerKeeper.GetHistoricalInfo(ctx, currentHeight) -// // require.True(t, found, "HistoricalInfo not found after set") -// // require.Equal(t, hi, recv, "HistoricalInfo not equal") - -// // // verify that historical info valset has validators sorted in order -// // require.True(t, IsValSetSorted(recv.Valset, sdk.DefaultPowerReduction), "HistoricalInfo validators is not sorted") -// // } - -// // IsValSetSorted reports whether valset is sorted. -// func IsValSetSorted(data []stakingtypes.Validator, powerReduction math.Int) bool { -// n := len(data) -// for i := n - 1; i > 0; i-- { -// if stakingtypes.ValidatorsByVotingPower(data).Less(i, i-1, powerReduction) { -// return false -// } -// } -// return true -// } - -// // Generates 4 test validators with non zero voting power -// func GenerateValidators(tb testing.TB) []*tmtypes.Validator { -// tb.Helper() -// numValidators := 4 -// validators := []*tmtypes.Validator{} -// for i := 0; i < numValidators; i++ { -// cId := crypto.NewCryptoIdentityFromIntSeed(234 + i) -// pubKey := cId.TMCryptoPubKey() - -// votingPower := int64(i + 1) -// validator := tmtypes.NewValidator(pubKey, votingPower) -// validators = append(validators, validator) -// } -// return validators -// } - -// // Sets each input tmtypes.Validator as a types.CrossChainValidator in the consumer keeper store -// func SetCCValidators(tb testing.TB, consumerKeeper keeper.Keeper, -// ctx sdk.Context, validators []*tmtypes.Validator, -// ) { -// tb.Helper() -// for _, v := range validators { -// publicKey, err := cryptocodec.FromTmPubKeyInterface(v.PubKey) -// require.NoError(tb, err) - -// ccv, err := types.NewCCValidator(v.Address, v.VotingPower, publicKey) -// require.NoError(tb, err) -// consumerKeeper.SetCCValidator(ctx, ccv) -// } -// } +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmtrand "github.com/cometbft/cometbft/libs/rand" + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/interchain-security/v3/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" + "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" +) + +// TestApplyCCValidatorChanges tests the ApplyCCValidatorChanges method for a consumer keeper +func TestApplyCCValidatorChanges(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + + // utility functions + getCCVals := func() (vals []types.CrossChainValidator) { + vals = consumerKeeper.GetAllCCValidator(ctx) + return + } + + clearCCVals := func() { + ccVals := consumerKeeper.GetAllCCValidator(ctx) + for _, v := range ccVals { + consumerKeeper.DeleteCCValidator(ctx, v.Address) + } + } + + sumCCValsPow := func(vals []types.CrossChainValidator) (power int64) { + for _, v := range vals { + power += v.Power + } + return + } + + // prepare the testing setup by clearing the current cross-chain validators in states + clearCCVals() + + tcValidators := GenerateValidators(t) + + changes := []abci.ValidatorUpdate{} + changesPower := int64(0) + + for _, v := range tcValidators { + changes = append(changes, cmttypes.TM2PB.ValidatorUpdate(v)) + changesPower += v.VotingPower + } + + // finish setup by storing 3 out 4 testing validators as cross-chain validator records + SetCCValidators(t, consumerKeeper, ctx, tcValidators[:len(tcValidators)-1]) + + // verify setup + ccVals := getCCVals() + require.Len(t, ccVals, len(tcValidators)-1) + + // test behaviors + testCases := []struct { + changes []abci.ValidatorUpdate + expTotalPower int64 + expValsNum int + }{ + { // add new bonded validator + changes: changes[len(changes)-1:], + expTotalPower: changesPower, + expValsNum: len(ccVals) + 1, + }, + { // update a validator voting power + changes: []abci.ValidatorUpdate{{PubKey: changes[0].PubKey, Power: changes[0].Power + 3}}, + expTotalPower: changesPower + 3, + expValsNum: len(ccVals) + 1, + }, + { // unbond a validator + changes: []abci.ValidatorUpdate{{PubKey: changes[0].PubKey, Power: 0}}, + expTotalPower: changesPower - changes[0].Power, + expValsNum: len(ccVals), + }, + { // update all validators voting power + changes: []abci.ValidatorUpdate{ + {PubKey: changes[0].PubKey, Power: changes[0].Power + 1}, + {PubKey: changes[1].PubKey, Power: changes[1].Power + 2}, + {PubKey: changes[2].PubKey, Power: changes[2].Power + 3}, + {PubKey: changes[3].PubKey, Power: changes[3].Power + 4}, + }, + expTotalPower: changesPower + 10, + expValsNum: len(ccVals) + 1, + }, + } + + for _, tc := range testCases { + + consumerKeeper.ApplyCCValidatorChanges(ctx, tc.changes) + gotVals := getCCVals() + + require.Len(t, gotVals, tc.expValsNum) + require.Equal(t, tc.expTotalPower, sumCCValsPow(gotVals)) + } +} + +// TestIsValidatorJailed tests the IsValidatorJailed method for a consumer keeper +func TestIsValidatorJailed(t *testing.T) { + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Consumer keeper from test setup should return false for IsPrevStandaloneChain() + require.False(t, consumerKeeper.IsPrevStandaloneChain(ctx)) + + // IsValidatorJailed should return false for an arbitrary consensus address + consAddr := []byte{0x01, 0x02, 0x03} + isJailed1, err := consumerKeeper.IsValidatorJailed(ctx, consAddr) + require.NoError(t, err) + require.False(t, isJailed1) + + // Set outstanding downtime for that addr + consumerKeeper.SetOutstandingDowntime(ctx, consAddr) + + // Now confirm IsValidatorJailed returns true + isJailed2, err := consumerKeeper.IsValidatorJailed(ctx, consAddr) + require.NoError(t, err) + require.True(t, isJailed2) + + // Next, we set a value for the standalone staking keeper, + // and mark the consumer keeper as being from a previous standalone chain + consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) + consumerKeeper.MarkAsPrevStandaloneChain(ctx) + require.True(t, consumerKeeper.IsPrevStandaloneChain(ctx)) + + // Set init genesis height to current block height so that ChangeoverIsComplete() is false + consumerKeeper.SetInitGenesisHeight(ctx, ctx.BlockHeight()) + require.False(t, consumerKeeper.ChangeoverIsComplete(ctx)) + + // At this point, the state of the consumer keeper is s.t. IsValidatorJailed() queries the standalone staking keeper + + // Now mock that a validator is jailed from the standalone staking keeper + mocks.MockStakingKeeper.EXPECT().IsValidatorJailed(ctx, consAddr).Return(true, nil).Times(1) + + // Confirm IsValidatorJailed returns true + isJailed3, err := consumerKeeper.IsValidatorJailed(ctx, consAddr) + require.NoError(t, err) + require.True(t, isJailed3) +} + +func TestSlash(t *testing.T) { + consumerKeeper, ctx, ctrl, mocks := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // If we call slash with infraction type empty, no slash packet will be queued + consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, math.LegacyNewDec(9.0), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) + pendingPackets := consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pendingPackets, 0) + + // Consumer keeper from test setup should return false for IsPrevStandaloneChain() + require.False(t, consumerKeeper.IsPrevStandaloneChain(ctx)) + + // Now setup a value for vscID mapped to infraction height + consumerKeeper.SetHeightValsetUpdateID(ctx, 5, 6) + + // Call slash with valid infraction type and confirm 1 slash packet is queued + consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, math.LegacyNewDec(9.0), stakingtypes.Infraction_INFRACTION_DOWNTIME) + pendingPackets = consumerKeeper.GetPendingPackets(ctx) + require.Len(t, pendingPackets, 1) + + // Next, we set a value for the standalone staking keeper, + // and mark the consumer keeper as being from a previous standalone chain + consumerKeeper.SetStandaloneStakingKeeper(mocks.MockStakingKeeper) + consumerKeeper.MarkAsPrevStandaloneChain(ctx) + require.True(t, consumerKeeper.IsPrevStandaloneChain(ctx)) + + // At this point, the state of the consumer keeper is s.t. + // Slash() calls the standalone staking keeper's Slash() + + // If we call slash with infraction type empty, standalone staking keeper's slash will not be called + // (if it was called, test would panic without mocking the call) + consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, 5, 6, math.LegacyNewDec(9.0), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) + + // Now setup a mock for Slash, and confirm that it is called against + // standalone staking keeper with valid infraction type + infractionHeight := int64(5) + mocks.MockStakingKeeper.EXPECT().SlashWithInfractionReason( + ctx, []byte{0x01, 0x02, 0x03}, infractionHeight, int64(6), + math.LegacyMustNewDecFromStr("0.05"), stakingtypes.Infraction_INFRACTION_UNSPECIFIED).Times(1) // We pass empty infraction to standalone staking keeper since it's not used + + // Also setup init genesis height s.t. infraction height is before first consumer height + consumerKeeper.SetInitGenesisHeight(ctx, 4) + require.Equal(t, consumerKeeper.FirstConsumerHeight(ctx), int64(6)) + + consumerKeeper.SlashWithInfractionReason(ctx, []byte{0x01, 0x02, 0x03}, infractionHeight, 6, + math.LegacyMustNewDecFromStr("0.05"), stakingtypes.Infraction_INFRACTION_DOWNTIME) +} + +// Tests the getter and setter behavior for historical info +// @MSalopek -> in v50 this requries using stakingtypes.Validators +// which embeds an address codec +// +// Validators is a collection of Validator +// +// type Validators struct { +// Validators []Validator +// ValidatorCodec address.Codec +// } +func TestHistoricalInfo(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) + consumerKeeper, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + ctx = ctx.WithBlockHeight(15) + + // Generate test validators, save them to store, and retrieve stored records + validators := GenerateValidators(t) + SetCCValidators(t, consumerKeeper, ctx, validators) + ccValidators := consumerKeeper.GetAllCCValidator(ctx) + require.Len(t, ccValidators, len(validators)) + + // iterate over validators and convert them to staking type + sVals := []stakingtypes.Validator{} + for _, v := range ccValidators { + pk, err := v.ConsPubKey() + require.NoError(t, err) + + val, err := stakingtypes.NewValidator("", pk, stakingtypes.Description{}) + require.NoError(t, err) + + // set voting power to random value + val.Tokens = sdk.TokensFromConsensusPower(cmtrand.NewRand().Int64(), sdk.DefaultPowerReduction) + sVals = append(sVals, val) + } + + currentHeight := ctx.BlockHeight() + + validatorsWithCodec := stakingtypes.Validators{ + Validators: sVals, + ValidatorCodec: consumerKeeper.ValidatorAddressCodec(), + } + // create and store historical info + hi := stakingtypes.NewHistoricalInfo(ctx.BlockHeader(), validatorsWithCodec, sdk.DefaultPowerReduction) + consumerKeeper.SetHistoricalInfo(ctx, currentHeight, &hi) + + // expect to get historical info + recv, err := consumerKeeper.GetHistoricalInfo(ctx, currentHeight) + require.NoError(t, err, "HistoricalInfo not found after set") + require.Equal(t, hi, recv, "HistoricalInfo not equal") + + // verify that historical info valset has validators sorted in order + require.True(t, IsValSetSorted(recv.Valset, sdk.DefaultPowerReduction), "HistoricalInfo validators is not sorted") +} + +// IsValSetSorted reports whether valset is sorted. +func IsValSetSorted(data []stakingtypes.Validator, powerReduction math.Int) bool { + n := len(data) + for i := n - 1; i > 0; i-- { + if stakingtypes.ValidatorsByVotingPower(data).Less(i, i-1, powerReduction) { + return false + } + } + return true +} + +// Generates 4 test validators with non zero voting power +func GenerateValidators(tb testing.TB) []*cmttypes.Validator { + tb.Helper() + numValidators := 4 + validators := []*cmttypes.Validator{} + for i := 0; i < numValidators; i++ { + cId := crypto.NewCryptoIdentityFromIntSeed(234 + i) + pubKey := cId.TMCryptoPubKey() + + votingPower := int64(i + 1) + validator := cmttypes.NewValidator(pubKey, votingPower) + validators = append(validators, validator) + } + return validators +} + +// Sets each input cmttypes.Validator as a types.CrossChainValidator in the consumer keeper store +func SetCCValidators(tb testing.TB, consumerKeeper keeper.Keeper, + ctx sdk.Context, validators []*cmttypes.Validator, +) { + tb.Helper() + for _, v := range validators { + publicKey, err := cryptocodec.FromTmPubKeyInterface(v.PubKey) + require.NoError(tb, err) + + ccv, err := types.NewCCValidator(v.Address, v.VotingPower, publicKey) + require.NoError(tb, err) + consumerKeeper.SetCCValidator(ctx, ccv) + } +} diff --git a/x/ccv/consumer/module.go b/x/ccv/consumer/module.go index a3092345f0..1283ae18c6 100644 --- a/x/ccv/consumer/module.go +++ b/x/ccv/consumer/module.go @@ -5,7 +5,8 @@ import ( "encoding/json" "fmt" - porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + "cosmossdk.io/core/appmodule" + abci "github.com/cometbft/cometbft/abci/types" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -17,8 +18,6 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/interchain-security/v3/x/ccv/consumer/client/cli" "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" @@ -26,9 +25,17 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ porttypes.IBCModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ module.AppModuleSimulation = (*AppModule)(nil) + _ module.HasABCIGenesis = (*AppModule)(nil) + _ module.HasABCIEndBlock = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasInvariants = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + _ appmodule.HasBeginBlocker = (*AppModule)(nil) ) // AppModuleBasic is the IBC Consumer AppModuleBasic @@ -143,7 +150,7 @@ func (AppModule) ConsensusVersion() uint64 { // BeginBlock implements the AppModule interface // Set the VSC ID for the subsequent block to the same value as the current block // Panic if the provider's channel was established and then closed -func (am AppModule) BeginBlock(ctx context.Context) { +func (am AppModule) BeginBlock(ctx context.Context) error { sdkCtx := sdk.UnwrapSDKContext(ctx) // Update smallest validator power that cannot opt out. @@ -164,20 +171,21 @@ func (am AppModule) BeginBlock(ctx context.Context) { am.keeper.Logger(sdkCtx).Debug("block height was mapped to vscID", "height", blockHeight+1, "vscID", vID) am.keeper.TrackHistoricalInfo(sdkCtx) + return nil } // EndBlock implements the AppModule interface // Flush PendingChanges to ABCI, send pending packets, write acknowledgements for packets that have finished unbonding. // // TODO: e2e tests confirming behavior with and without standalone -> consumer changeover -func (am AppModule) EndBlock(ctx context.Context) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) // If PreCCV state is active, consumer is a previously standalone chain // that was just upgraded to include the consumer ccv module, execute changeover logic. if am.keeper.IsPreCCV(sdkCtx) { initialValUpdates := am.keeper.ChangeoverToConsumer(sdkCtx) - return initialValUpdates + return initialValUpdates, nil } // Execute EndBlock logic for the Reward Distribution sub-protocol @@ -192,7 +200,7 @@ func (am AppModule) EndBlock(ctx context.Context) []abci.ValidatorUpdate { data, ok := am.keeper.GetPendingChanges(sdkCtx) if !ok { - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } // apply changes to cross-chain validator set tendermintUpdates := am.keeper.ApplyCCValidatorChanges(sdkCtx, data.ValidatorUpdates) @@ -200,7 +208,7 @@ func (am AppModule) EndBlock(ctx context.Context) []abci.ValidatorUpdate { am.keeper.Logger(sdkCtx).Debug("sending validator updates to consensus engine", "len updates", len(tendermintUpdates)) - return tendermintUpdates + return tendermintUpdates, nil } // AppModuleSimulation functions