diff --git a/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md new file mode 100644 index 0000000000..421967ddab --- /dev/null +++ b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md @@ -0,0 +1,4 @@ +- `[x/consumer]` Populate the memo on the IBC transfer packets used to send ICS rewards. +with the required consumer chain Id to identify the consumer to the provider. +- `[x/provider]` Identify the source of ICS rewards from the IBC transfer packet memo. + ([\#2290](https://github.com/cosmos/interchain-security/pull/2290)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md b/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md new file mode 100644 index 0000000000..421967ddab --- /dev/null +++ b/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md @@ -0,0 +1,4 @@ +- `[x/consumer]` Populate the memo on the IBC transfer packets used to send ICS rewards. +with the required consumer chain Id to identify the consumer to the provider. +- `[x/provider]` Identify the source of ICS rewards from the IBC transfer packet memo. + ([\#2290](https://github.com/cosmos/interchain-security/pull/2290)) \ No newline at end of file diff --git a/UPGRADING.md b/UPGRADING.md index e56e283fe2..d487f8e26f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,26 @@ ## Unreleased +### Consumer + +Upgrading a consumer from v4.4.x to v4.5.x and from v5.x or v6.1.x to v6.2.x requires state migrations. The following migrators should be added to the upgrade handler of the consumer chain: + + +```go +// InitializeConsumerId sets the consumer Id parameter in the consumer module, +// to the consumer id for which the consumer is registered on the provider chain. +// The consumer id can be obtained in by querying the provider, e.g. by using the +// QueryConsumerIdFromClientId query. +func InitializeConsumerId(ctx sdk.Context, consumerKeeper consumerkeeper.Keeper) error { + params, err := consumerKeeper.GetParams(ctx) + if err != nil { + return err + } + params.ConsumerId = ConsumerId + return consumerKeeper.SetParams(ctx, params) +} +``` + ### Provider Upgrading a provider from v5.1.x requires state migrations. The following migrators should be added to the upgrade handler of the provider chain: diff --git a/proto/interchain_security/ccv/v1/shared_consumer.proto b/proto/interchain_security/ccv/v1/shared_consumer.proto index a89c8f3cd6..d8dad14df7 100644 --- a/proto/interchain_security/ccv/v1/shared_consumer.proto +++ b/proto/interchain_security/ccv/v1/shared_consumer.proto @@ -76,6 +76,10 @@ message ConsumerParams { // The period after which a consumer can retry sending a throttled packet. google.protobuf.Duration retry_delay_period = 13 [ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ]; + + // The consumer ID of this consumer chain. Used by the consumer module to send + // ICS rewards. + string consumer_id = 14; } // ConsumerGenesisState defines shared genesis information between provider and diff --git a/tests/e2e/action_rapid_test.go b/tests/e2e/action_rapid_test.go index 205e794058..dffa0286a8 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -102,7 +102,7 @@ func CreateSubmitChangeRewardDenomsProposalActionGen() *rapid.Generator[SubmitCh Chain: GetChainIDGen().Draw(t, "Chain"), From: GetValidatorIDGen().Draw(t, "From"), Deposit: rapid.Uint().Draw(t, "Deposit"), - Denom: rapid.String().Draw(t, "Denom"), + Denoms: rapid.SliceOf(rapid.String()).Draw(t, "Denoms"), } }) } diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 240d9f98a4..872d19ce6c 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -495,7 +495,7 @@ func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, upd bz, err = cmd.CombinedOutput() if err != nil { fmt.Println("command failed: ", cmd) - log.Fatal("update consumer failed error: %w, output: %s", err, string(bz)) + log.Fatalf("update consumer failed error: %s, output: %s", err, string(bz)) } // Check transaction @@ -2536,14 +2536,14 @@ func (tr Chain) registerRepresentative( type SubmitChangeRewardDenomsProposalAction struct { Chain ChainID - Denom string + Denoms []string Deposit uint From ValidatorID } func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { changeRewMsg := types.MsgChangeRewardDenoms{ - DenomsToAdd: []string{action.Denom}, + DenomsToAdd: action.Denoms, DenomsToRemove: []string{"stake"}, Authority: "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", } @@ -2604,7 +2604,7 @@ func (tr Chain) submitChangeRewardDenomsLegacyProposal(action SubmitChangeReward ChangeRewardDenomsProposal: types.ChangeRewardDenomsProposal{ Title: "Change reward denoms", Description: "Change reward denoms", - DenomsToAdd: []string{action.Denom}, + DenomsToAdd: action.Denoms, DenomsToRemove: []string{"stake"}, }, Deposit: fmt.Sprint(action.Deposit) + `stake`, @@ -3309,3 +3309,95 @@ func (tr Commands) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, g return cmd.CombinedOutput() } + +type CreateIbcClientAction struct { + ChainA ChainID + ChainB ChainID +} + +func (tr Chain) createIbcClientHermes( + action CreateIbcClientAction, + verbose bool, +) { + cmd := tr.target.ExecCommand("hermes", + "create", "client", + "--host-chain", string(tr.testConfig.chainConfigs[action.ChainA].ChainId), + "--reference-chain", string(tr.testConfig.chainConfigs[action.ChainB].ChainId), + "--trusting-period", "1200000s", + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("createIbcClientHermes: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + +type TransferIbcTokenAction struct { + Chain ChainID + DstAddr string + From ValidatorID + Amount uint + Channel uint + Memo string +} + +func (tr Chain) transferIbcToken( + action TransferIbcTokenAction, + verbose bool, +) { + // Note: to get error response reported back from this command '--gas auto' needs to be set. + gas := "auto" + + transferCmd := fmt.Sprintf( + `%s tx ibc-transfer transfer transfer \ +%s %s %s --memo %q --from validator%s --chain-id %s \ +--home %s --node %s --gas %s --keyring-backend test -y -o json`, + tr.testConfig.chainConfigs[action.Chain].BinaryName, + "channel-"+fmt.Sprint(action.Channel), + action.DstAddr, + fmt.Sprint(action.Amount)+`stake`, + action.Memo, + action.From, + string(tr.testConfig.chainConfigs[action.Chain].ChainId), + tr.getValidatorHome(action.Chain, action.From), + tr.getValidatorNode(action.Chain, action.From), + gas, + ) + + cmd := tr.target.ExecCommand( + "/bin/bash", "-c", + transferCmd, + ) + + if verbose { + fmt.Println("transferIbcToken cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("unexpected error during IBC token transfer: %s: %s", string(bz), err) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(action.Chain, 2, 30*time.Second) +} diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 336d4af39c..55db1789d7 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -9,6 +9,7 @@ import ( "regexp" "sort" "strconv" + "strings" "time" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -154,10 +155,10 @@ func (tr Chain) GetRewards(chain ChainID, modelState Rewards) Rewards { currentBlock = 1 } for k := range modelState.IsRewarded { - receivedRewards[k] = tr.target.GetReward(chain, k, nextBlock, modelState.IsNativeDenom) > tr.target.GetReward(chain, k, currentBlock, modelState.IsNativeDenom) + receivedRewards[k] = tr.target.GetReward(chain, k, nextBlock, modelState.Denom) > tr.target.GetReward(chain, k, currentBlock, modelState.Denom) } - return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, IsNativeDenom: modelState.IsNativeDenom} + return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, Denom: modelState.Denom} } func (tr Chain) GetConsumerAddresses(chain ChainID, modelState map[ValidatorID]string) map[ValidatorID]string { @@ -252,7 +253,7 @@ func (tr Commands) GetBlockHeight(chain ChainID) uint { return uint(blockHeight) } -func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { +func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { valCfg := tr.validatorConfigs[validator] delAddresss := valCfg.DelAddress if chain != ChainID("provi") { @@ -285,12 +286,23 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u log.Fatal("failed getting rewards: ", err, "\n", string(bz)) } - denomCondition := `total.#(denom!="stake").amount` - if isNativeDenom { - denomCondition = `total.#(denom=="stake").amount` + denomCondition := fmt.Sprintf(`total.#(%%"*%s*")`, denom) + amount := strings.Split(gjson.Get(string(bz), denomCondition).String(), denom)[0] + + fmt.Println("denomCondition:", denomCondition) + fmt.Println("json:", gjson.Parse(string(bz))) + + res := float64(0) + if amount != "" { + res, err = strconv.ParseFloat(amount, 64) + if err != nil { + log.Fatal("failed parsing consumer reward:", err) + } } - return gjson.Get(string(bz), denomCondition).Float() + fmt.Println("res", res) + + return res } // interchain-securityd query gov proposals diff --git a/tests/e2e/state_rapid_test.go b/tests/e2e/state_rapid_test.go index 2962e89571..da4bbed877 100644 --- a/tests/e2e/state_rapid_test.go +++ b/tests/e2e/state_rapid_test.go @@ -104,8 +104,8 @@ func GetRewardsGen() *rapid.Generator[Rewards] { return rapid.Custom(func(t *rapid.T) Rewards { return Rewards{ IsIncrementalReward: rapid.Bool().Draw(t, "IsIncrementalReward"), - IsNativeDenom: rapid.Bool().Draw(t, "IsNativeDenom"), - IsRewarded: rapid.MapOf(GetValidatorIDGen(), rapid.Bool()).Draw(t, "IsRewarded"), + // Denom: rapid.Str, + IsRewarded: rapid.MapOf(GetValidatorIDGen(), rapid.Bool()).Draw(t, "IsRewarded"), } }) } diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index df506639e4..604f724ad8 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -1,10 +1,11 @@ package main -import ( - gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" -) +import gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" -const consumerRewardDenom = "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" +var consumerRewardDenoms = []string{ + "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9", // transfer channel-1 + "ibc/D549749C93524DA1831A4B3C850DFC1BA9060261BEDFB224B3B0B4744CD77A70", // transfer channel-2 +} func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool) []Step { return []Step{ @@ -27,7 +28,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): false, }, IsIncrementalReward: true, - IsNativeDenom: true, + Denom: "stake", }, // Check that delegating on gov-consumer does not change validator powers ValPowers: &map[ValidatorID]uint{ @@ -66,7 +67,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): true, }, IsIncrementalReward: true, - IsNativeDenom: true, + Denom: "stake", }, }, }, @@ -141,7 +142,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): false, }, IsIncrementalReward: false, - IsNativeDenom: false, + Denom: consumerRewardDenoms[0], }, // Check that the denom is not registered on provider chain RegisteredConsumerRewardDenoms: &[]string{}, @@ -151,7 +152,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool { Action: SubmitChangeRewardDenomsProposalAction{ Chain: ChainID("provi"), - Denom: consumerRewardDenom, + Denoms: consumerRewardDenoms, Deposit: 10000001, From: ValidatorID("bob"), }, @@ -172,10 +173,11 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool State: State{ ChainID("provi"): ChainState{ // Check that the denom is registered on provider chain - RegisteredConsumerRewardDenoms: &[]string{consumerRewardDenom}, + RegisteredConsumerRewardDenoms: &consumerRewardDenoms, }, }, }, + // Relay pending consumer rewards sent via the transfer channel-1 { Action: RelayRewardPacketsToProviderAction{ ConsumerChain: ChainID(consumerName), @@ -185,8 +187,115 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool }, State: State{ ChainID("provi"): ChainState{ - // Check that ARE NOT minted and sent to provider chain and distributed to validators and their delegators on provider chain - // the tokens are not sent because the test configuration does not allow sending tokens + Rewards: &Rewards{ + // expectRegisteredRewardDistribution == true + // expect rewards to be distributed since IBC denoms are registered + // and transfer channel-1 is associated to the consumer id + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): expectRegisteredRewardDistribution, + ValidatorID("bob"): expectRegisteredRewardDistribution, + ValidatorID("carol"): expectRegisteredRewardDistribution, + }, + IsIncrementalReward: false, + Denom: consumerRewardDenoms[0], + }, + }, + }, + }, + // Create a second consumer client on the provider + { + Action: CreateIbcClientAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + }, + State: State{}, + }, + // Create a new IBC connection between the 2nd consumer client + // and the existing provider client on the consumer + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + ClientA: 1, + ClientB: 0, // already created during the CCV handshake + }, + State: State{}, + }, + // Create IBC transfer channel-2 + { + Action: AddIbcChannelAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + ConnectionA: 1, + PortA: "transfer", + PortB: "transfer", + Order: "unordered", + Version: "ics20-1", + }, + State: State{}, + }, + // Transfer tokens from the consumer to the consumer reward pool + // of the provider via the transfer channel-2 + { + Action: TransferIbcTokenAction{ + Chain: ChainID(consumerName), + From: ValidatorID("carol"), + DstAddr: "cosmos1ap0mh6xzfn8943urr84q6ae7zfnar48am2erhd", // consumer reward pool address + Amount: 1000000, + Channel: 2, + Memo: "consumer chain rewards distribution", // no consumer Id in memo + }, + State: State{}, + }, + // Relay the transfer packets from channel-2 + // and check that tokens are not distributed + // since the packet isn't associated to a consumer id + { + Action: RelayRewardPacketsToProviderAction{ + ConsumerChain: ChainID(consumerName), + ProviderChain: ChainID("provi"), + Port: "transfer", + Channel: 2, + }, + State: State{ + ChainID("provi"): ChainState{ + Rewards: &Rewards{ + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): false, + ValidatorID("carol"): false, + }, + IsIncrementalReward: true, + Denom: "stake", + }, + }, + }, + }, + // Transfer tokens from the consumer to the consumer reward pool + // of the provider via the transfer channel-2 using the correct memo + // to identify the consumer + { + Action: TransferIbcTokenAction{ + Chain: ChainID(consumerName), + From: ValidatorID("carol"), + DstAddr: "cosmos1ap0mh6xzfn8943urr84q6ae7zfnar48am2erhd", // consumer reward pool address + Amount: 1000000, + Channel: 2, + Memo: `{"provider":{"consumerId":"0","chainId":"democ","memo":"ICS rewards"}}`, + }, + State: State{}, + }, + // Relay the transfer packets from channel-2 + // and check that tokens are distributed + { + Action: RelayRewardPacketsToProviderAction{ + ConsumerChain: ChainID(consumerName), + ProviderChain: ChainID("provi"), + Port: "transfer", + Channel: 2, + }, + State: State{ + ChainID("provi"): ChainState{ Rewards: &Rewards{ IsRewarded: map[ValidatorID]bool{ ValidatorID("alice"): expectRegisteredRewardDistribution, @@ -194,7 +303,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): expectRegisteredRewardDistribution, }, IsIncrementalReward: false, - IsNativeDenom: false, + Denom: consumerRewardDenoms[1], }, }, }, diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go index 9cc397a362..dab57b4eba 100644 --- a/tests/e2e/steps_inactive_vals.go +++ b/tests/e2e/steps_inactive_vals.go @@ -40,8 +40,8 @@ func stepsInactiveProviderValidators() []Step { ValidatorID("carol"): 300000000, }, Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // we need to get incremental rewards + Denom: "stake", // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards // if we would look at total rewards, alice would trivially also get rewards, // because she gets rewards in the first block due to being in the genesis IsRewarded: map[ValidatorID]bool{ @@ -100,8 +100,8 @@ func stepsInactiveProviderValidators() []Step { }, // check that bob and carol get rewards, but alice does not Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // check rewards since block 1 + Denom: "stake", // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards since block 1 IsRewarded: map[ValidatorID]bool{ ValidatorID("alice"): false, ValidatorID("bob"): true, @@ -143,8 +143,8 @@ func stepsInactiveProviderValidators() []Step { State: State{ ChainID("provi"): ChainState{ Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // check rewards for currently produced blocks only + Denom: "stake", // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only IsRewarded: map[ValidatorID]bool{ ValidatorID("alice"): true, // alice is participating right now, so gets rewards ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus @@ -176,8 +176,8 @@ func stepsInactiveProviderValidators() []Step { }, // check that between two blocks now, alice does not get rewarded with the native denom Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // check rewards for currently produced blocks only + Denom: "stake", // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only IsRewarded: map[ValidatorID]bool{ ValidatorID("alice"): false, ValidatorID("bob"): true, @@ -787,8 +787,8 @@ func stepsInactiveValsWithTopN() []Step { ValidatorID("carol"): 300000000, }, Rewards: &Rewards{ - IsNativeDenom: true, // check for rewards in the provider denom - IsIncrementalReward: true, // we need to get incremental rewards + Denom: "stake", // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards // if we would look at total rewards, alice would trivially also get rewards, // because she gets rewards in the first block due to being in the genesis IsRewarded: map[ValidatorID]bool{ diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index 2c3f9c1ac2..b20c194072 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -413,6 +413,13 @@ func (td *DefaultDriver) runAction(action interface{}) error { case SubmitConsumerMisbehaviourAction: target := td.getTargetDriver("provider") target.submitConsumerMisbehaviour(action, td.verbose) + case CreateIbcClientAction: + // use default for hermes actions + target := td.getTargetDriver("") + target.createIbcClientHermes(action, td.verbose) + case TransferIbcTokenAction: + target := td.getTargetDriver(action.Chain) + target.transferIbcToken(action, td.verbose) default: log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.name, action) } diff --git a/tests/e2e/testlib/types.go b/tests/e2e/testlib/types.go index 844611b796..0fd7ada259 100644 --- a/tests/e2e/testlib/types.go +++ b/tests/e2e/testlib/types.go @@ -38,7 +38,7 @@ type ChainCommands interface { GetProposal(chain ChainID, proposal uint) Proposal GetParam(chain ChainID, param Param) string GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string - GetReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 + GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 GetRegisteredConsumerRewardDenoms(chain ChainID) []string GetSlashMeter() int64 GetPendingPacketQueueSize(chain ChainID) uint @@ -339,9 +339,9 @@ type Rewards struct { // if true it will calculate if the validator/delegator is rewarded between 2 successive blocks, // otherwise it will calculate if it received any rewards since the 1st block IsIncrementalReward bool - // if true checks rewards for "stake" token, otherwise checks rewards from - // other chains (e.g. false is used to check if provider received rewards from a consumer chain) - IsNativeDenom bool + // The reward denom to be checked. This can be either the native "stake" denom or + // a denom from other chains (e.g. if provider received rewards from a consumer chain) + Denom string } type ParamsProposal struct { diff --git a/tests/e2e/v4/state.go b/tests/e2e/v4/state.go index 6343747d5d..f0ba1bca4b 100644 --- a/tests/e2e/v4/state.go +++ b/tests/e2e/v4/state.go @@ -7,6 +7,7 @@ import ( "os/exec" "regexp" "strconv" + "strings" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" "github.com/kylelemons/godebug/pretty" @@ -153,7 +154,7 @@ func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { return 0 } -func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { +func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { valCfg := tr.ValidatorConfigs[validator] delAddresss := valCfg.DelAddress if chain != ChainID("provi") { @@ -167,25 +168,32 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u } binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - + cmd := tr.Target.ExecCommand(binaryName, "query", "distribution", "rewards", delAddresss, - `--height`, fmt.Sprint(blockHeight), `--node`, tr.GetQueryNode(chain), `-o`, `json`, - ).CombinedOutput() + ) + + bz, err := cmd.CombinedOutput() if err != nil { - log.Fatal(err, "\n", string(bz)) + log.Println("running cmd: ", cmd) + log.Fatal("failed getting rewards: ", err, "\n", string(bz)) } - denomCondition := `total.#(denom!="stake").amount` - if isNativeDenom { - denomCondition = `total.#(denom=="stake").amount` + denomCondition := fmt.Sprintf(`total.#(%%"*%s*")`, denom) + amount := strings.Split(gjson.Get(string(bz), denomCondition).String(), denom)[0] + + res := float64(0) + if amount != "" { + res, err = strconv.ParseFloat(amount, 64) + if err != nil { + log.Fatal("failed parsing consumer reward:", err) + } } - return gjson.Get(string(bz), denomCondition).Float() + return res } func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 0db5370578..1fe0cf0234 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -505,6 +505,7 @@ func createConsumerGenesis(modelParams ModelParams, providerChain *ibctesting.Te []string{}, []string{}, ccvtypes.DefaultRetryDelayPeriod, + "", ) return consumertypes.NewInitialGenesisState(consumerClientState, providerConsState, valUpdates, params) diff --git a/x/ccv/consumer/keeper/distribution.go b/x/ccv/consumer/keeper/distribution.go index eb2175926a..c50902463f 100644 --- a/x/ccv/consumer/keeper/distribution.go +++ b/x/ccv/consumer/keeper/distribution.go @@ -118,6 +118,11 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error { sentCoins := sdk.NewCoins() var allBalances sdk.Coins + rewardMemo, err := ccv.CreateTransferMemo(k.GetConsumerId(ctx), ctx.ChainID()) + if err != nil { + return err + } + // iterate over all whitelisted reward denoms for _, denom := range k.AllowedRewardDenoms(ctx) { // get the balance of the denom in the toSendToProviderTokens address @@ -134,7 +139,7 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error { Receiver: providerAddr, // provider fee pool address to send to TimeoutHeight: timeoutHeight, // timeout height disabled TimeoutTimestamp: timeoutTimestamp, - Memo: "consumer chain rewards distribution", + Memo: rewardMemo, } // validate MsgTransfer before calling Transfer() diff --git a/x/ccv/consumer/keeper/params.go b/x/ccv/consumer/keeper/params.go index bdd020ed70..0b6a308263 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -127,3 +127,8 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration { params := k.GetConsumerParams(ctx) return params.RetryDelayPeriod } + +func (k Keeper) GetConsumerId(ctx sdk.Context) string { + params := k.GetConsumerParams(ctx) + return params.ConsumerId +} diff --git a/x/ccv/consumer/keeper/params_test.go b/x/ccv/consumer/keeper/params_test.go index b090feac43..02ac3ef1e9 100644 --- a/x/ccv/consumer/keeper/params_test.go +++ b/x/ccv/consumer/keeper/params_test.go @@ -31,6 +31,7 @@ func TestParams(t *testing.T) { rewardDenoms, provideRewardDenoms, ccv.DefaultRetryDelayPeriod, + "0", ) // these are the default params, IBC suite independently sets enabled=true params := consumerKeeper.GetConsumerParams(ctx) @@ -38,7 +39,7 @@ func TestParams(t *testing.T) { newParams := ccv.NewParams(false, 1000, "channel-2", "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - 7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour) + 7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, "1") consumerKeeper.SetParams(ctx, newParams) params = consumerKeeper.GetConsumerParams(ctx) require.Equal(t, newParams, params) diff --git a/x/ccv/consumer/migrations/v3/legacy_params.go b/x/ccv/consumer/migrations/v3/legacy_params.go index b67bf9db0e..debfe832f6 100644 --- a/x/ccv/consumer/migrations/v3/legacy_params.go +++ b/x/ccv/consumer/migrations/v3/legacy_params.go @@ -24,6 +24,7 @@ func GetConsumerParamsLegacy(ctx sdk.Context, paramSpace ccvtypes.LegacyParamSub getRewardDenoms(ctx, paramSpace), getProviderRewardDenoms(ctx, paramSpace), getRetryDelayPeriod(ctx, paramSpace), + "0", ) } diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index 2803c2686a..b77ee85f1a 100644 --- a/x/ccv/consumer/types/genesis_test.go +++ b/x/ccv/consumer/types/genesis_test.go @@ -233,6 +233,7 @@ func TestValidateInitialGenesisState(t *testing.T) { []string{}, []string{}, ccv.DefaultRetryDelayPeriod, + "1", )), true, }, @@ -252,6 +253,7 @@ func TestValidateInitialGenesisState(t *testing.T) { []string{}, []string{}, ccv.DefaultRetryDelayPeriod, + "1", )), true, }, @@ -457,6 +459,7 @@ func TestValidateRestartConsumerGenesisState(t *testing.T) { []string{}, []string{}, ccv.DefaultRetryDelayPeriod, + "1", )), true, }, diff --git a/x/ccv/consumer/types/params_test.go b/x/ccv/consumer/types/params_test.go index 1e2a8e8cfe..267d9728e6 100644 --- a/x/ccv/consumer/types/params_test.go +++ b/x/ccv/consumer/types/params_test.go @@ -11,6 +11,8 @@ import ( // Tests the validation of consumer params that happens at genesis func TestValidateParams(t *testing.T) { + consumerId := "13" + testCases := []struct { name string params ccvtypes.ConsumerParams @@ -19,59 +21,67 @@ func TestValidateParams(t *testing.T) { {"default params", ccvtypes.DefaultParams(), true}, { "custom valid params", - ccvtypes.NewParams(true, 5, "", "", 1004, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), true, + ccvtypes.NewParams(true, 5, "", "", 1004, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), true, }, { "custom invalid params, block per dist transmission", - ccvtypes.NewParams(true, -5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, -5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, dist transmission channel", - ccvtypes.NewParams(true, 5, "badchannel/", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "badchannel/", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, ccv timeout", - ccvtypes.NewParams(true, 5, "", "", -5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", -5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, transfer timeout", - ccvtypes.NewParams(true, 5, "", "", 1004, -7, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 1004, -7, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, consumer redist fraction is negative", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "-0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "-0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, consumer redist fraction is over 1", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "1.2", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "1.2", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, bad consumer redist fraction ", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "notFrac", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "notFrac", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, negative num historical entries", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", -100, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", -100, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, negative unbonding period", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, -24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, -24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, invalid reward denom", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"u"}, []string{}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"u"}, []string{}, 2*time.Hour, consumerId), false, }, { "custom invalid params, invalid provider reward denom", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{"a"}, 2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{"a"}, 2*time.Hour, consumerId), false, }, { "custom invalid params, retry delay period is negative", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, -2*time.Hour), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, -2*time.Hour, consumerId), false, }, { "custom invalid params, retry delay period is zero", - ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, 0), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, 0, consumerId), false, + }, + { + "custom invalid params, consumer ID is blank", + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, time.Hour, ""), false, + }, + { + "custom invalid params, consumer ID is not a uint64", + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, time.Hour, "consumerId"), false, }, } diff --git a/x/ccv/provider/ibc_middleware.go b/x/ccv/provider/ibc_middleware.go index d5db2fd50e..46d158e5e0 100644 --- a/x/ccv/provider/ibc_middleware.go +++ b/x/ccv/provider/ibc_middleware.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/interchain-security/v6/x/ccv/provider/keeper" "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" ) var _ porttypes.Middleware = &IBCMiddleware{} @@ -123,34 +124,6 @@ func (im IBCMiddleware) OnRecvPacket( // that the packet data is valid and can be safely // deserialized without checking errors. if ack.Success() { - // execute the middleware logic only if the sender is a consumer chain - consumerId, err := im.keeper.IdentifyConsumerIdFromIBCPacket(ctx, packet) - if err != nil { - // Check if the packet is received on a canonical transfer channels - // of one of the known consumer chains. - // Note: this is a patch for the Cosmos Hub for consumers such as Stride - // TODO: remove once the known consumer chains upgrade to send ICS rewards - // with the consumer ID added to the memo field - if ctx.ChainID() == "cosmoshub-4" && // this patch is only for the Cosmos Hub - packet.DestinationChannel == "channel-391" { // canonical transfer channel Stride <> Cosmos Hub - // check source chain ID - srcChainId, err := im.keeper.GetSourceChainIdFromIBCPacket(ctx, packet) - if err != nil || srcChainId != "stride-1" { - // ignore packet if it's not from Stride - return ack - } - // accept the packet as a potential ICS reward - consumerId = "1" // consumer ID of Stride - // sanity check: make sure this is the consumer ID for Stride - chainId, err := im.keeper.GetConsumerChainId(ctx, consumerId) - if err != nil || srcChainId != chainId { - return ack - } - } else { - return ack - } - } - // extract the coin info received from the packet data var data ibctransfertypes.FungibleTokenPacketData _ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data) @@ -161,6 +134,32 @@ func (im IBCMiddleware) OnRecvPacket( return ack } + consumerId := "" + // check if the transfer has the reward memo + if rewardMemo, err := ccvtypes.GetRewardMemoFromTransferMemo(data.Memo); err != nil { + // check if the transfer is on a channel with the same underlying + // client as the CCV channel + consumerId, err = im.keeper.IdentifyConsumerIdFromIBCPacket(ctx, packet) + if err != nil { + if data.Memo == "consumer chain rewards distribution" { + // log error message + logger.Error( + "received token transfer with ICS reward from unknown consumer", + "packet", packet.String(), + "fungibleTokenPacketData", data.String(), + "error", err.Error(), + ) + } + + return ack + } + } else { + logger.Info("transfer memo:%#+v", rewardMemo) + consumerId = rewardMemo.ConsumerId + } + + coinAmt, _ := math.NewIntFromString(data.Amount) + coinDenom := GetProviderDenom(data.Denom, packet) chainId, err := im.keeper.GetConsumerChainId(ctx, consumerId) if err != nil { logger.Error( @@ -173,9 +172,6 @@ func (im IBCMiddleware) OnRecvPacket( return ack } - coinAmt, _ := math.NewIntFromString(data.Amount) - coinDenom := GetProviderDenom(data.Denom, packet) - logger.Info( "received ICS rewards from consumer chain", "consumerId", consumerId, diff --git a/x/ccv/provider/keeper/consumer_lifecycle.go b/x/ccv/provider/keeper/consumer_lifecycle.go index c51db4392a..c578df4ff2 100644 --- a/x/ccv/provider/keeper/consumer_lifecycle.go +++ b/x/ccv/provider/keeper/consumer_lifecycle.go @@ -346,6 +346,7 @@ func (k Keeper) MakeConsumerGenesis( []string{}, []string{}, ccv.DefaultRetryDelayPeriod, + consumerId, ) // create provider client state and consensus state for the consumer to be able diff --git a/x/ccv/provider/keeper/consumer_lifecycle_test.go b/x/ccv/provider/keeper/consumer_lifecycle_test.go index 3561cb8c46..6b258ae003 100644 --- a/x/ccv/provider/keeper/consumer_lifecycle_test.go +++ b/x/ccv/provider/keeper/consumer_lifecycle_test.go @@ -649,7 +649,8 @@ func TestMakeConsumerGenesis(t *testing.T) { "soft_opt_out_threshold": "0", "reward_denoms": [], "provider_reward_denoms": [], - "retry_delay_period": %d + "retry_delay_period": %d, + "consumer_id": "%s" }, "new_chain": true, "provider" : { @@ -720,6 +721,7 @@ func TestMakeConsumerGenesis(t *testing.T) { initializationParameters.HistoricalEntries, consumerUnbondingPeriod.Nanoseconds(), ccvtypes.DefaultRetryDelayPeriod.Nanoseconds(), + CONSUMER_ID, providerChainId, trustingPeriod.Nanoseconds(), providerUnbondingPeriod.Nanoseconds(), diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index 08c9019782..545a51cade 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -30,8 +30,8 @@ func (k Keeper) QueryConsumerGenesis(c context.Context, req *types.QueryConsumer } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } gen, ok := k.GetConsumerGenesis(ctx, consumerId) @@ -141,8 +141,8 @@ func (k Keeper) QueryValidatorConsumerAddr(goCtx context.Context, req *types.Que ctx := sdk.UnwrapSDKContext(goCtx) consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } providerAddrTmp, err := sdk.ConsAddressFromBech32(req.ProviderAddress) @@ -230,8 +230,8 @@ func (k Keeper) QueryAllPairsValConsAddrByConsumer( } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } // list of pairs valconsensus addr @@ -275,8 +275,8 @@ func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req * } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } optedInVals := []string{} @@ -302,8 +302,8 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } ctx := sdk.UnwrapSDKContext(goCtx) @@ -507,8 +507,8 @@ func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress) @@ -569,8 +569,8 @@ func (k Keeper) QueryConsumerChain(goCtx context.Context, req *types.QueryConsum } consumerId := req.ConsumerId - if err := types.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error()) + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) } ctx := sdk.UnwrapSDKContext(goCtx) diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index dd2a6ea4c6..11096a0acb 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -8,7 +8,6 @@ import ( var ( ErrUnknownConsumerId = errorsmod.Register(ModuleName, 3, "no consumer chain with this consumer id") ErrUnknownConsumerChannelId = errorsmod.Register(ModuleName, 4, "no consumer chain with this channel id") - ErrInvalidConsumerId = errorsmod.Register(ModuleName, 6, "invalid consumer id") ErrConsumerKeyInUse = errorsmod.Register(ModuleName, 10, "consumer key is already in use by a validator") ErrCannotAssignDefaultKeyAssignment = errorsmod.Register(ModuleName, 11, "cannot re-assign default key assignment") ErrInvalidConsumerRewardDenom = errorsmod.Register(ModuleName, 14, "invalid consumer reward denom") diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 63a868ad8a..3f3899acac 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -3,7 +3,6 @@ package types import ( "encoding/json" "fmt" - "strconv" "strings" ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" @@ -75,7 +74,7 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ChainId: %s", err.Error()) } - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ConsumerId: %s", err.Error()) } @@ -139,7 +138,7 @@ func NewMsgSubmitConsumerMisbehaviour( // ValidateBasic implements the sdk.HasValidateBasic interface. func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerMisbehaviour, "ConsumerId: %s", err.Error()) } @@ -177,7 +176,7 @@ func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ValidateTendermintHeader: %s", err.Error()) } - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ConsumerId: %s", err.Error()) } @@ -200,7 +199,7 @@ func (msg MsgOptIn) ValidateBasic() error { return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ChainId: %s", err.Error()) } - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ConsumerId: %s", err.Error()) } @@ -231,7 +230,7 @@ func (msg MsgOptOut) ValidateBasic() error { return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ChainId: %s", err.Error()) } - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ConsumerId: %s", err.Error()) } @@ -263,7 +262,7 @@ func (msg MsgSetConsumerCommissionRate) ValidateBasic() error { return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ChainId: %s", err.Error()) } - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ConsumerId: %s", err.Error()) } @@ -346,7 +345,7 @@ func NewMsgUpdateConsumer(owner, consumerId, ownerAddress string, metadata *Cons // ValidateBasic implements the sdk.HasValidateBasic interface. func (msg MsgUpdateConsumer) ValidateBasic() error { - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "ConsumerId: %s", err.Error()) } @@ -383,7 +382,7 @@ func NewMsgRemoveConsumer(owner, consumerId string) (*MsgRemoveConsumer, error) // ValidateBasic implements the sdk.HasValidateBasic interface. func (msg MsgRemoveConsumer) ValidateBasic() error { - if err := ValidateConsumerId(msg.ConsumerId); err != nil { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { return err } return nil @@ -432,21 +431,6 @@ func ValidateHeaderForConsumerDoubleVoting(header *ibctmtypes.Header) error { return nil } -// ValidateConsumerId validates the provided consumer id and returns an error if it is not valid -func ValidateConsumerId(consumerId string) error { - if strings.TrimSpace(consumerId) == "" { - return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id cannot be blank") - } - - // check that `consumerId` corresponds to a `uint64` - _, err := strconv.ParseUint(consumerId, 10, 64) - if err != nil { - return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id (%s) cannot be parsed: %s", consumerId, err.Error()) - } - - return nil -} - // ValidateStringField validates that a string `field` satisfies the following properties: // - is not empty // - has at most `maxLength` characters diff --git a/x/ccv/provider/types/msg_test.go b/x/ccv/provider/types/msg_test.go index b2a522fba6..6867343b65 100644 --- a/x/ccv/provider/types/msg_test.go +++ b/x/ccv/provider/types/msg_test.go @@ -13,20 +13,6 @@ import ( "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" ) -func TestValidateConsumerId(t *testing.T) { - // empty consumer id - require.Error(t, types.ValidateConsumerId("")) - - // not a `uint64` where `uint64` is in the range [0, 2^64) - require.Error(t, types.ValidateConsumerId("a")) - require.Error(t, types.ValidateConsumerId("-2545")) - require.Error(t, types.ValidateConsumerId("18446744073709551616")) // 2^64 - - // valid consumer id - require.NoError(t, types.ValidateConsumerId("0")) - require.NoError(t, types.ValidateConsumerId("18446744073709551615")) // 2^64 - 1 -} - func TestValidateStringField(t *testing.T) { testCases := []struct { name string diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index 995d9905e8..2cc65e484c 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -24,4 +24,5 @@ var ( ErrInvalidDoubleVotingEvidence = errorsmod.Register(ModuleName, 16, "invalid consumer double voting evidence") ErrStoreKeyNotFound = errorsmod.Register(ModuleName, 17, "store key not found") ErrStoreUnmarshal = errorsmod.Register(ModuleName, 18, "cannot unmarshal value from store") + ErrInvalidConsumerId = errorsmod.Register(ModuleName, 19, "invalid consumer id") ) diff --git a/x/ccv/types/params.go b/x/ccv/types/params.go index 99c58bd4df..2a29eeb086 100644 --- a/x/ccv/types/params.go +++ b/x/ccv/types/params.go @@ -78,6 +78,7 @@ func NewParams(enabled bool, blocksPerDistributionTransmission int64, consumerRedistributionFraction string, historicalEntries int64, consumerUnbondingPeriod time.Duration, rewardDenoms, providerRewardDenoms []string, retryDelayPeriod time.Duration, + consumerId string, ) ConsumerParams { return ConsumerParams{ Enabled: enabled, @@ -94,6 +95,7 @@ func NewParams(enabled bool, blocksPerDistributionTransmission int64, RewardDenoms: rewardDenoms, ProviderRewardDenoms: providerRewardDenoms, RetryDelayPeriod: retryDelayPeriod, + ConsumerId: consumerId, } } @@ -114,6 +116,7 @@ func DefaultParams() ConsumerParams { rewardDenoms, provideRewardDenoms, DefaultRetryDelayPeriod, + "0", ) } @@ -155,6 +158,9 @@ func (p ConsumerParams) Validate() error { if err := ValidateDuration(p.RetryDelayPeriod); err != nil { return err } + if err := ValidateConsumerId(p.ConsumerId); err != nil { + return err + } return nil } diff --git a/x/ccv/types/shared_consumer.pb.go b/x/ccv/types/shared_consumer.pb.go index 5ab076dbf2..2c859e2b45 100644 --- a/x/ccv/types/shared_consumer.pb.go +++ b/x/ccv/types/shared_consumer.pb.go @@ -75,6 +75,8 @@ type ConsumerParams struct { ProviderRewardDenoms []string `protobuf:"bytes,12,rep,name=provider_reward_denoms,json=providerRewardDenoms,proto3" json:"provider_reward_denoms,omitempty"` // The period after which a consumer can retry sending a throttled packet. RetryDelayPeriod time.Duration `protobuf:"bytes,13,opt,name=retry_delay_period,json=retryDelayPeriod,proto3,stdduration" json:"retry_delay_period"` + // The consumer ID of this consumer chain + ConsumerId string `protobuf:"bytes,14,opt,name=consumer_id,json=consumerId,proto3" json:"consumer_id,omitempty"` } func (m *ConsumerParams) Reset() { *m = ConsumerParams{} } @@ -202,6 +204,13 @@ func (m *ConsumerParams) GetRetryDelayPeriod() time.Duration { return 0 } +func (m *ConsumerParams) GetConsumerId() string { + if m != nil { + return m.ConsumerId + } + return "" +} + // ConsumerGenesisState defines shared genesis information between provider and // consumer type ConsumerGenesisState struct { @@ -341,58 +350,59 @@ func init() { } var fileDescriptor_d0a8be0efc64dfbc = []byte{ - // 817 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0x41, 0x73, 0xdc, 0x34, - 0x14, 0x8e, 0xb3, 0x25, 0xdd, 0x68, 0x93, 0xa6, 0x88, 0x50, 0x4c, 0x3a, 0xb3, 0x71, 0x03, 0x87, - 0x1d, 0x98, 0xda, 0x24, 0x74, 0x60, 0x86, 0x1b, 0x49, 0x28, 0xa5, 0x87, 0x64, 0xeb, 0x84, 0x32, - 0x03, 0x07, 0x8d, 0x2c, 0xbd, 0x5d, 0x6b, 0xb0, 0x25, 0x8f, 0x24, 0x3b, 0xe4, 0x17, 0x70, 0xe5, - 0xc8, 0x4f, 0x2a, 0xb7, 0x1e, 0x39, 0x01, 0x93, 0xfc, 0x11, 0xc6, 0xb2, 0x9d, 0x78, 0x19, 0x02, - 0xe9, 0x4d, 0x4f, 0xef, 0xfb, 0x3e, 0xfb, 0x7b, 0xd2, 0x7b, 0x42, 0x9f, 0x08, 0x69, 0x41, 0xb3, - 0x94, 0x0a, 0x49, 0x0c, 0xb0, 0x52, 0x0b, 0x7b, 0x1e, 0x31, 0x56, 0x45, 0xd5, 0x6e, 0x64, 0x52, - 0xaa, 0x81, 0x13, 0xa6, 0xa4, 0x29, 0x73, 0xd0, 0x61, 0xa1, 0x95, 0x55, 0x78, 0xeb, 0x5f, 0x18, - 0x21, 0x63, 0x55, 0x58, 0xed, 0x6e, 0x3d, 0xb4, 0x20, 0x39, 0xe8, 0x5c, 0x48, 0x1b, 0xd1, 0x84, - 0x89, 0xc8, 0x9e, 0x17, 0x60, 0x1a, 0xe2, 0x56, 0x24, 0x12, 0x16, 0x65, 0x62, 0x9e, 0x5a, 0x96, - 0x09, 0x90, 0xd6, 0x44, 0x3d, 0x74, 0xb5, 0xdb, 0x8b, 0x5a, 0xc2, 0x78, 0xae, 0xd4, 0x3c, 0x83, - 0xc8, 0x45, 0x49, 0x39, 0x8b, 0x78, 0xa9, 0xa9, 0x15, 0x4a, 0xb6, 0xf9, 0xcd, 0xb9, 0x9a, 0x2b, - 0xb7, 0x8c, 0xea, 0x55, 0xb3, 0xbb, 0x73, 0xb9, 0x82, 0xee, 0x1d, 0xb4, 0xbf, 0x3c, 0xa5, 0x9a, - 0xe6, 0x06, 0xfb, 0xe8, 0x2e, 0x48, 0x9a, 0x64, 0xc0, 0x7d, 0x2f, 0xf0, 0x26, 0xc3, 0xb8, 0x0b, - 0xf1, 0x31, 0xfa, 0x30, 0xc9, 0x14, 0xfb, 0xd1, 0x90, 0x02, 0x34, 0xe1, 0xc2, 0x58, 0x2d, 0x92, - 0xb2, 0xfe, 0x06, 0xb1, 0x9a, 0x4a, 0x93, 0x0b, 0x63, 0x84, 0x92, 0xfe, 0x72, 0xe0, 0x4d, 0x06, - 0xf1, 0xa3, 0x06, 0x3b, 0x05, 0x7d, 0xd8, 0x43, 0x9e, 0xf6, 0x80, 0xf8, 0x39, 0x7a, 0x74, 0xa3, - 0x0a, 0x61, 0x29, 0x95, 0x12, 0x32, 0x7f, 0x10, 0x78, 0x93, 0xd5, 0x78, 0x9b, 0xdf, 0x20, 0x72, - 0xd0, 0xc0, 0xf0, 0x17, 0x68, 0xab, 0xd0, 0xaa, 0x12, 0x1c, 0x34, 0x99, 0x01, 0x90, 0x42, 0xa9, - 0x8c, 0x50, 0xce, 0x35, 0x31, 0x56, 0xfb, 0x77, 0x9c, 0xc8, 0x83, 0x0e, 0xf1, 0x14, 0x60, 0xaa, - 0x54, 0xf6, 0x25, 0xe7, 0xfa, 0xc4, 0x6a, 0xfc, 0x02, 0x61, 0xc6, 0x2a, 0x62, 0x45, 0x0e, 0xaa, - 0xb4, 0xb5, 0x3b, 0xa1, 0xb8, 0xff, 0x56, 0xe0, 0x4d, 0x46, 0x7b, 0xef, 0x87, 0x4d, 0x61, 0xc3, - 0xae, 0xb0, 0xe1, 0x61, 0x5b, 0xd8, 0xfd, 0xe1, 0xab, 0x3f, 0xb6, 0x97, 0x7e, 0xfd, 0x73, 0xdb, - 0x8b, 0xef, 0x33, 0x56, 0x9d, 0x36, 0xec, 0xa9, 0x23, 0xe3, 0x1f, 0xd0, 0x7b, 0xce, 0xcd, 0x0c, - 0xf4, 0x3f, 0x75, 0x57, 0x6e, 0xaf, 0xfb, 0x6e, 0xa7, 0xb1, 0x28, 0xfe, 0x0c, 0x05, 0xdd, 0x3d, - 0x23, 0x1a, 0x16, 0x4a, 0x38, 0xd3, 0x94, 0xd5, 0x0b, 0xff, 0xae, 0x73, 0x3c, 0xee, 0x70, 0xf1, - 0x02, 0xec, 0x69, 0x8b, 0xc2, 0x8f, 0x11, 0x4e, 0x85, 0xb1, 0x4a, 0x0b, 0x46, 0x33, 0x02, 0xd2, - 0x6a, 0x01, 0xc6, 0x1f, 0xba, 0x03, 0x7c, 0xfb, 0x3a, 0xf3, 0x55, 0x93, 0xc0, 0x47, 0xe8, 0x7e, - 0x29, 0x13, 0x25, 0xb9, 0x90, 0xf3, 0xce, 0xce, 0xea, 0xed, 0xed, 0x6c, 0x5c, 0x91, 0x5b, 0x23, - 0x9f, 0xa3, 0x07, 0x46, 0xcd, 0x2c, 0x51, 0x85, 0x25, 0x75, 0x85, 0x6c, 0xaa, 0xc1, 0xa4, 0x2a, - 0xe3, 0x3e, 0xaa, 0x7f, 0x7f, 0x7f, 0xd9, 0xf7, 0xe2, 0x77, 0x6a, 0xc4, 0x71, 0x61, 0x8f, 0x4b, - 0x7b, 0xda, 0xa5, 0xf1, 0x07, 0x68, 0x5d, 0xc3, 0x19, 0xd5, 0x9c, 0x70, 0x90, 0x2a, 0x37, 0xfe, - 0x28, 0x18, 0x4c, 0x56, 0xe3, 0xb5, 0x66, 0xf3, 0xd0, 0xed, 0xe1, 0x27, 0xe8, 0xea, 0xc0, 0xc9, - 0x22, 0x7a, 0xcd, 0xa1, 0x37, 0xbb, 0x6c, 0xdc, 0x67, 0xbd, 0x40, 0x58, 0x83, 0xd5, 0xe7, 0x84, - 0x43, 0x46, 0xcf, 0x3b, 0x97, 0xeb, 0x6f, 0x70, 0x19, 0x1c, 0xfd, 0xb0, 0x66, 0x37, 0x36, 0x77, - 0x7e, 0xf3, 0xd0, 0x66, 0xd7, 0x65, 0x5f, 0x83, 0x04, 0x23, 0xcc, 0x89, 0xa5, 0x16, 0xf0, 0x33, - 0xb4, 0x52, 0xb8, 0xae, 0x73, 0xad, 0x36, 0xda, 0xfb, 0x28, 0xbc, 0x79, 0x5e, 0x84, 0x8b, 0x7d, - 0xba, 0x7f, 0xa7, 0xfe, 0x60, 0xdc, 0xf2, 0xf1, 0x73, 0x34, 0xec, 0xdc, 0xb8, 0xfe, 0x1b, 0xed, - 0x4d, 0xfe, 0x4b, 0x6b, 0xda, 0x62, 0xbf, 0x91, 0x33, 0xd5, 0x2a, 0x5d, 0xf1, 0xf1, 0x43, 0xb4, - 0x2a, 0xe1, 0x8c, 0x38, 0xa6, 0x6b, 0xbf, 0x61, 0x3c, 0x94, 0x70, 0x76, 0x50, 0xc7, 0x3b, 0x3f, - 0x2f, 0xa3, 0xb5, 0x3e, 0x1b, 0x1f, 0xa1, 0xb5, 0x66, 0x44, 0x11, 0x53, 0x7b, 0x6a, 0x9d, 0x7c, - 0x1c, 0x8a, 0x84, 0x85, 0xfd, 0x01, 0x16, 0xf6, 0x46, 0x56, 0xed, 0xc6, 0xed, 0xba, 0x32, 0xc4, - 0x23, 0x76, 0x1d, 0xe0, 0xef, 0xd0, 0x46, 0x7d, 0x69, 0x41, 0x9a, 0xd2, 0xb4, 0x92, 0x8d, 0xa1, - 0xf0, 0x7f, 0x25, 0x3b, 0x5a, 0xa3, 0x7a, 0x8f, 0x2d, 0xc4, 0xf8, 0x08, 0x6d, 0x08, 0x29, 0xac, - 0xa0, 0x19, 0xa9, 0x68, 0x46, 0x0c, 0x58, 0x7f, 0x10, 0x0c, 0x26, 0xa3, 0xbd, 0xa0, 0xaf, 0x53, - 0x4f, 0xe2, 0xf0, 0x25, 0xcd, 0x04, 0xa7, 0x56, 0xe9, 0x6f, 0x0b, 0x4e, 0x2d, 0xb4, 0x15, 0x5a, - 0x6f, 0xe9, 0x2f, 0x69, 0x76, 0x02, 0x76, 0xff, 0xe8, 0xd5, 0xc5, 0xd8, 0x7b, 0x7d, 0x31, 0xf6, - 0xfe, 0xba, 0x18, 0x7b, 0xbf, 0x5c, 0x8e, 0x97, 0x5e, 0x5f, 0x8e, 0x97, 0x7e, 0xbf, 0x1c, 0x2f, - 0x7d, 0xff, 0x64, 0x2e, 0x6c, 0x5a, 0x26, 0x21, 0x53, 0x79, 0xc4, 0x94, 0xc9, 0x95, 0x89, 0xae, - 0xcf, 0xe2, 0xf1, 0xd5, 0xcb, 0x51, 0x7d, 0x16, 0xfd, 0xe4, 0x9e, 0x0f, 0x37, 0xf8, 0x93, 0x15, - 0x77, 0xa9, 0x3e, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x66, 0xae, 0x12, 0x66, 0x06, 0x00, + // 833 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xcf, 0x73, 0xdc, 0x34, + 0x14, 0x8e, 0xb3, 0x25, 0xd9, 0x68, 0xf3, 0xa3, 0x88, 0x50, 0x4c, 0x3a, 0xb3, 0xd9, 0x06, 0x0e, + 0x3b, 0x30, 0xb5, 0x49, 0xe8, 0xc0, 0x0c, 0x37, 0x92, 0x50, 0xda, 0x1e, 0x92, 0xad, 0x13, 0xca, + 0x0c, 0x1c, 0x34, 0xb2, 0xf4, 0x76, 0xad, 0xc1, 0x96, 0x3c, 0x92, 0xec, 0x90, 0x3b, 0x33, 0x5c, + 0x39, 0xf2, 0x27, 0x95, 0x5b, 0x8f, 0x9c, 0x80, 0x49, 0xfe, 0x11, 0xc6, 0xb2, 0xbd, 0xf1, 0x32, + 0x04, 0xda, 0x9b, 0x9f, 0xf4, 0x7d, 0x9f, 0xf5, 0xbd, 0xa7, 0xf7, 0x84, 0x3e, 0x11, 0xd2, 0x82, + 0x66, 0x09, 0x15, 0x92, 0x18, 0x60, 0x85, 0x16, 0xf6, 0x32, 0x64, 0xac, 0x0c, 0xcb, 0xfd, 0xd0, + 0x24, 0x54, 0x03, 0x27, 0x4c, 0x49, 0x53, 0x64, 0xa0, 0x83, 0x5c, 0x2b, 0xab, 0xf0, 0xce, 0xbf, + 0x30, 0x02, 0xc6, 0xca, 0xa0, 0xdc, 0xdf, 0xb9, 0x6f, 0x41, 0x72, 0xd0, 0x99, 0x90, 0x36, 0xa4, + 0x31, 0x13, 0xa1, 0xbd, 0xcc, 0xc1, 0xd4, 0xc4, 0x9d, 0x50, 0xc4, 0x2c, 0x4c, 0xc5, 0x2c, 0xb1, + 0x2c, 0x15, 0x20, 0xad, 0x09, 0x3b, 0xe8, 0x72, 0xbf, 0x13, 0x35, 0x84, 0xe1, 0x4c, 0xa9, 0x59, + 0x0a, 0xa1, 0x8b, 0xe2, 0x62, 0x1a, 0xf2, 0x42, 0x53, 0x2b, 0x94, 0x6c, 0xf6, 0xb7, 0x67, 0x6a, + 0xa6, 0xdc, 0x67, 0x58, 0x7d, 0xd5, 0xab, 0x7b, 0x3f, 0xad, 0xa2, 0xcd, 0xa3, 0xe6, 0xc8, 0x13, + 0xaa, 0x69, 0x66, 0xb0, 0x8f, 0x56, 0x41, 0xd2, 0x38, 0x05, 0xee, 0x7b, 0x23, 0x6f, 0xdc, 0x8f, + 0xda, 0x10, 0x9f, 0xa2, 0x0f, 0xe3, 0x54, 0xb1, 0x1f, 0x0c, 0xc9, 0x41, 0x13, 0x2e, 0x8c, 0xd5, + 0x22, 0x2e, 0xaa, 0x7f, 0x10, 0xab, 0xa9, 0x34, 0x99, 0x30, 0x46, 0x28, 0xe9, 0x2f, 0x8f, 0xbc, + 0x71, 0x2f, 0x7a, 0x50, 0x63, 0x27, 0xa0, 0x8f, 0x3b, 0xc8, 0xf3, 0x0e, 0x10, 0x3f, 0x43, 0x0f, + 0x6e, 0x55, 0x21, 0x2c, 0xa1, 0x52, 0x42, 0xea, 0xf7, 0x46, 0xde, 0x78, 0x2d, 0xda, 0xe5, 0xb7, + 0x88, 0x1c, 0xd5, 0x30, 0xfc, 0x05, 0xda, 0xc9, 0xb5, 0x2a, 0x05, 0x07, 0x4d, 0xa6, 0x00, 0x24, + 0x57, 0x2a, 0x25, 0x94, 0x73, 0x4d, 0x8c, 0xd5, 0xfe, 0x1d, 0x27, 0x72, 0xaf, 0x45, 0x3c, 0x06, + 0x98, 0x28, 0x95, 0x7e, 0xc9, 0xb9, 0x3e, 0xb3, 0x1a, 0x3f, 0x47, 0x98, 0xb1, 0x92, 0x58, 0x91, + 0x81, 0x2a, 0x6c, 0xe5, 0x4e, 0x28, 0xee, 0xbf, 0x35, 0xf2, 0xc6, 0x83, 0x83, 0xf7, 0x83, 0x3a, + 0xb1, 0x41, 0x9b, 0xd8, 0xe0, 0xb8, 0x49, 0xec, 0x61, 0xff, 0xe5, 0x1f, 0xbb, 0x4b, 0xbf, 0xfe, + 0xb9, 0xeb, 0x45, 0x77, 0x19, 0x2b, 0xcf, 0x6b, 0xf6, 0xc4, 0x91, 0xf1, 0xf7, 0xe8, 0x3d, 0xe7, + 0x66, 0x0a, 0xfa, 0x9f, 0xba, 0x2b, 0xaf, 0xaf, 0xfb, 0x6e, 0xab, 0xb1, 0x28, 0xfe, 0x04, 0x8d, + 0xda, 0x7b, 0x46, 0x34, 0x2c, 0xa4, 0x70, 0xaa, 0x29, 0xab, 0x3e, 0xfc, 0x55, 0xe7, 0x78, 0xd8, + 0xe2, 0xa2, 0x05, 0xd8, 0xe3, 0x06, 0x85, 0x1f, 0x22, 0x9c, 0x08, 0x63, 0x95, 0x16, 0x8c, 0xa6, + 0x04, 0xa4, 0xd5, 0x02, 0x8c, 0xdf, 0x77, 0x05, 0x7c, 0xfb, 0x66, 0xe7, 0xab, 0x7a, 0x03, 0x9f, + 0xa0, 0xbb, 0x85, 0x8c, 0x95, 0xe4, 0x42, 0xce, 0x5a, 0x3b, 0x6b, 0xaf, 0x6f, 0x67, 0x6b, 0x4e, + 0x6e, 0x8c, 0x7c, 0x8e, 0xee, 0x19, 0x35, 0xb5, 0x44, 0xe5, 0x96, 0x54, 0x19, 0xb2, 0x89, 0x06, + 0x93, 0xa8, 0x94, 0xfb, 0xa8, 0x3a, 0xfe, 0xe1, 0xb2, 0xef, 0x45, 0xef, 0x54, 0x88, 0xd3, 0xdc, + 0x9e, 0x16, 0xf6, 0xbc, 0xdd, 0xc6, 0x1f, 0xa0, 0x0d, 0x0d, 0x17, 0x54, 0x73, 0xc2, 0x41, 0xaa, + 0xcc, 0xf8, 0x83, 0x51, 0x6f, 0xbc, 0x16, 0xad, 0xd7, 0x8b, 0xc7, 0x6e, 0x0d, 0x3f, 0x42, 0xf3, + 0x82, 0x93, 0x45, 0xf4, 0xba, 0x43, 0x6f, 0xb7, 0xbb, 0x51, 0x97, 0xf5, 0x1c, 0x61, 0x0d, 0x56, + 0x5f, 0x12, 0x0e, 0x29, 0xbd, 0x6c, 0x5d, 0x6e, 0xbc, 0xc1, 0x65, 0x70, 0xf4, 0xe3, 0x8a, 0xdd, + 0xd8, 0xdc, 0x45, 0x83, 0x79, 0xbd, 0x04, 0xf7, 0x37, 0x5d, 0x69, 0x50, 0xbb, 0xf4, 0x94, 0xef, + 0xfd, 0xe6, 0xa1, 0xed, 0xb6, 0x0d, 0xbf, 0x06, 0x09, 0x46, 0x98, 0x33, 0x4b, 0x2d, 0xe0, 0x27, + 0x68, 0x25, 0x77, 0x6d, 0xe9, 0x7a, 0x71, 0x70, 0xf0, 0x51, 0x70, 0xfb, 0x40, 0x09, 0x16, 0x1b, + 0xf9, 0xf0, 0x4e, 0x75, 0xa2, 0xa8, 0xe1, 0xe3, 0x67, 0xa8, 0xdf, 0xda, 0x75, 0x0d, 0x3a, 0x38, + 0x18, 0xff, 0x97, 0xd6, 0xa4, 0xc1, 0x3e, 0x95, 0x53, 0xd5, 0x28, 0xcd, 0xf9, 0xf8, 0x3e, 0x5a, + 0x93, 0x70, 0x41, 0x1c, 0xd3, 0xf5, 0x67, 0x3f, 0xea, 0x4b, 0xb8, 0x38, 0xaa, 0xe2, 0xbd, 0x9f, + 0x97, 0xd1, 0x7a, 0x97, 0x8d, 0x4f, 0xd0, 0x7a, 0x3d, 0xc3, 0x88, 0xa9, 0x3c, 0x35, 0x4e, 0x3e, + 0x0e, 0x44, 0xcc, 0x82, 0xee, 0x84, 0x0b, 0x3a, 0x33, 0xad, 0x72, 0xe3, 0x56, 0x5d, 0x1a, 0xa2, + 0x01, 0xbb, 0x09, 0xf0, 0xb7, 0x68, 0xab, 0x4a, 0x1d, 0x48, 0x53, 0x98, 0x46, 0xb2, 0x36, 0x14, + 0xfc, 0xaf, 0x64, 0x4b, 0xab, 0x55, 0x37, 0xd9, 0x42, 0x8c, 0x4f, 0xd0, 0x96, 0x90, 0xc2, 0x0a, + 0x9a, 0x92, 0x92, 0xa6, 0xc4, 0x80, 0xf5, 0x7b, 0xa3, 0xde, 0x78, 0x70, 0x30, 0xea, 0xea, 0x54, + 0xa3, 0x3a, 0x78, 0x41, 0x53, 0xc1, 0xa9, 0x55, 0xfa, 0x9b, 0x9c, 0x53, 0x0b, 0x4d, 0x86, 0x36, + 0x1a, 0xfa, 0x0b, 0x9a, 0x9e, 0x81, 0x3d, 0x3c, 0x79, 0x79, 0x35, 0xf4, 0x5e, 0x5d, 0x0d, 0xbd, + 0xbf, 0xae, 0x86, 0xde, 0x2f, 0xd7, 0xc3, 0xa5, 0x57, 0xd7, 0xc3, 0xa5, 0xdf, 0xaf, 0x87, 0x4b, + 0xdf, 0x3d, 0x9a, 0x09, 0x9b, 0x14, 0x71, 0xc0, 0x54, 0x16, 0x32, 0x65, 0x32, 0x65, 0xc2, 0x9b, + 0x5a, 0x3c, 0x9c, 0x3f, 0x2d, 0xe5, 0x67, 0xe1, 0x8f, 0xee, 0x7d, 0x71, 0x2f, 0x43, 0xbc, 0xe2, + 0x6e, 0xdd, 0xa7, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xbc, 0xf0, 0xde, 0x5e, 0x87, 0x06, 0x00, 0x00, } @@ -416,6 +426,13 @@ func (m *ConsumerParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ConsumerId) > 0 { + i -= len(m.ConsumerId) + copy(dAtA[i:], m.ConsumerId) + i = encodeVarintSharedConsumer(dAtA, i, uint64(len(m.ConsumerId))) + i-- + dAtA[i] = 0x72 + } n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.RetryDelayPeriod, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.RetryDelayPeriod):]) if err1 != nil { return 0, err1 @@ -693,6 +710,10 @@ func (m *ConsumerParams) Size() (n int) { } l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.RetryDelayPeriod) n += 1 + l + sovSharedConsumer(uint64(l)) + l = len(m.ConsumerId) + if l > 0 { + n += 1 + l + sovSharedConsumer(uint64(l)) + } return n } @@ -1152,6 +1173,38 @@ func (m *ConsumerParams) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsumerId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSharedConsumer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSharedConsumer + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSharedConsumer + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsumerId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSharedConsumer(dAtA[iNdEx:]) diff --git a/x/ccv/types/shared_params.go b/x/ccv/types/shared_params.go index 118ef4d5c6..aa90134f3f 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -2,10 +2,13 @@ package types import ( fmt "fmt" + "strconv" + "strings" "time" ibchost "github.com/cosmos/ibc-go/v8/modules/core/24-host" + errorsmod "cosmossdk.io/errors" "cosmossdk.io/math" sdktypes "github.com/cosmos/cosmos-sdk/types" @@ -114,3 +117,18 @@ func CalculateTrustPeriod(unbondingPeriod time.Duration, defaultTrustPeriodFract return trustPeriod, nil } + +// ValidateConsumerId validates the provided consumer id and returns an error if it is not valid +func ValidateConsumerId(consumerId string) error { + if strings.TrimSpace(consumerId) == "" { + return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id cannot be blank") + } + + // check that `consumerId` corresponds to a `uint64` + _, err := strconv.ParseUint(consumerId, 10, 64) + if err != nil { + return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id (%s) cannot be parsed: %s", consumerId, err.Error()) + } + + return nil +} diff --git a/x/ccv/types/shared_params_test.go b/x/ccv/types/shared_params_test.go new file mode 100644 index 0000000000..9d2bbfce00 --- /dev/null +++ b/x/ccv/types/shared_params_test.go @@ -0,0 +1,23 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/interchain-security/v6/x/ccv/types" +) + +func TestValidateConsumerId(t *testing.T) { + // empty consumer id + require.Error(t, types.ValidateConsumerId("")) + + // not a `uint64` where `uint64` is in the range [0, 2^64) + require.Error(t, types.ValidateConsumerId("a")) + require.Error(t, types.ValidateConsumerId("-2545")) + require.Error(t, types.ValidateConsumerId("18446744073709551616")) // 2^64 + + // valid consumer id + require.NoError(t, types.ValidateConsumerId("0")) + require.NoError(t, types.ValidateConsumerId("18446744073709551615")) // 2^64 - 1 +} diff --git a/x/ccv/types/wire.go b/x/ccv/types/wire.go index 9c22522b74..ec04399380 100644 --- a/x/ccv/types/wire.go +++ b/x/ccv/types/wire.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" errorsmod "cosmossdk.io/errors" @@ -194,3 +195,54 @@ func NewConsumerPacketData(cpdType ConsumerPacketDataType, data isConsumerPacket Data: data, } } + +type RewardMemo struct { + ConsumerId string `json:"consumerId"` + ChainId string `json:"chainId"` + Memo string `json:"memo"` +} + +func NewRewardMemo(consumerId, chainId, memo string) RewardMemo { + return RewardMemo{ + ConsumerId: consumerId, + ChainId: chainId, + Memo: memo, + } +} + +// CreateTransferMemo creates a memo for the IBC transfer of ICS rewards. +// Note that the memo follows the Fungible Token Transfer v2 standard +// https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md#using-the-memo-field +func CreateTransferMemo(consumerId, chainId string) (string, error) { + memo := NewRewardMemo(consumerId, chainId, "ICS rewards") + memoBytes, err := json.Marshal(memo) + if err != nil { + return "", err + } + return fmt.Sprintf(`{ + "provider": %s + }`, + string(memoBytes), + ), nil +} + +func GetRewardMemoFromTransferMemo(memo string) (RewardMemo, error) { + memoData := map[string]json.RawMessage{} + err := json.Unmarshal([]byte(memo), &memoData) + if err != nil { + return RewardMemo{}, err + } + + providerMemo, ok := memoData["provider"] + if !ok { + return RewardMemo{}, err + } + + rewardMemo := RewardMemo{} + err = json.Unmarshal([]byte(providerMemo), &rewardMemo) + if err != nil { + return RewardMemo{}, err + } + + return rewardMemo, nil +} diff --git a/x/ccv/types/wire_test.go b/x/ccv/types/wire_test.go index 408221bc0e..227e776c29 100644 --- a/x/ccv/types/wire_test.go +++ b/x/ccv/types/wire_test.go @@ -223,3 +223,17 @@ func TestVSCMaturedPacketDataWireBytes(t *testing.T) { require.Equal(t, expectedStr, str) } + +func TestCreateTransferMemo(t *testing.T) { + consumerId := "13" + chainId := "chain-13" + + transferMemo, err := types.CreateTransferMemo(consumerId, chainId) + require.NoError(t, err) + + rewardMemo, err := types.GetRewardMemoFromTransferMemo(transferMemo) + require.NoError(t, err) + require.Equal(t, consumerId, rewardMemo.ConsumerId) + require.Equal(t, chainId, rewardMemo.ChainId) + require.Equal(t, "ICS rewards", rewardMemo.Memo) +}