Skip to content

Commit 47cd04e

Browse files
authored
fix!: enable the submission of allowlisted reward denoms (#2326)
* init commit * fix docs * add validation
1 parent 9d224a3 commit 47cd04e

File tree

14 files changed

+507
-352
lines changed

14 files changed

+507
-352
lines changed

docs/docs/build/modules/02-provider.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ message MsgChangeRewardDenoms {
476476
`MsgCreateConsumer` enables a user to create a consumer chain.
477477

478478
Both the `chain_id` and `metadata` fields are mandatory.
479-
Both the `initialization_parameters` and `power_shaping_parameters` fields are optional.
479+
The `initialization_parameters`, `power_shaping_parameters`, and `allowlisted_reward_denoms` fields are optional.
480480
The parameters not provided are set to their zero value.
481481

482482
The owner of the created consumer chain is the submitter of the message.
@@ -507,6 +507,9 @@ message MsgCreateConsumer {
507507
ConsumerInitializationParameters initialization_parameters = 4;
508508
509509
PowerShapingParameters power_shaping_parameters = 5;
510+
511+
// allowlisted reward denoms by the consumer chain
512+
AllowlistedRewardDenoms allowlisted_reward_denoms = 6;
510513
}
511514
```
512515

@@ -516,7 +519,7 @@ message MsgCreateConsumer {
516519

517520
Note that only the `owner` (i.e., signer) and `consumer_id` fields are mandatory.
518521
The others field are optional. Not providing one of them will leave the existing values unchanged.
519-
Providing one of `metadata`, `initialization_parameters` or `power_shaping_parameters`,
522+
Providing one of `metadata`, `initialization_parameters`, `power_shaping_parameters`, or `allowlisted_reward_denoms`
520523
will update all the containing fields.
521524
If one of the containing fields is missing, it will be set to its zero value.
522525
For example, updating the `initialization_parameters` without specifying the `spawn_time`, will set the `spawn_time` to zero.
@@ -525,7 +528,7 @@ If the `initialization_parameters` field is set and `initialization_parameters.s
525528
Updating the `spawn_time` from a positive value to zero will remove the consumer chain from the list of scheduled to launch chains.
526529
If the consumer chain is already launched, updating the `initialization_parameters` is no longer possible.
527530

528-
If the `power_shaping_parameters` field is set and `power_shaping_parameters.top_N` is possitive, then the owner needs to be the gov module account address.
531+
If the `power_shaping_parameters` field is set and `power_shaping_parameters.top_N` is positive, then the owner needs to be the gov module account address.
529532

530533
If the `new_owner_address` field is set to a value different than the gov module account address, then `top_N` needs to be zero.
531534

@@ -550,6 +553,9 @@ message MsgUpdateConsumer {
550553
551554
// the power-shaping parameters of the consumer when updated
552555
PowerShapingParameters power_shaping_parameters = 6;
556+
557+
// allowlisted reward denoms by the consumer chain
558+
AllowlistedRewardDenoms allowlisted_reward_denoms = 7;
553559
}
554560
```
555561

@@ -1675,6 +1681,9 @@ where `update-consumer-msg.json` contains:
16751681
"denylist":[],
16761682
"min_stake": "1000",
16771683
"allow_inactive_vals":true
1684+
},
1685+
"allowlisted_reward_denoms": {
1686+
"denoms": ["ibc/0025F8A87464A471E66B234C4F93AEC5B4DA3D42D7986451A059273426290DD5"]
16781687
}
16791688
}
16801689
```

docs/docs/consumer-development/onboarding.md

+8
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ Example of power-shaping parameters:
130130
}
131131
```
132132

133+
Example of allowlisted reward denoms:
134+
```js
135+
// AllowlistedRewardDenoms provided in MsgCreateConsumer or MsgUpdateConsumer
136+
{
137+
"denoms": ["ibc/0025F8A87464A471E66B234C4F93AEC5B4DA3D42D7986451A059273426290DD5", "ibc/054892D6BB43AF8B93AAC28AA5FD7019D2C59A15DAFD6F45C1FA2BF9BDA22454"]
138+
}
139+
```
140+
133141
## 4. Launch
134142

135143
The consumer chain starts after at least 66.67% of its voting power comes online.

proto/interchain_security/ccv/provider/v1/query.proto

+2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ message Chain {
194194
// Corresponds to whether inactive validators are allowed to validate the consumer chain.
195195
bool allow_inactive_vals = 12;
196196
string consumer_id = 13;
197+
// the reward denoms allowlisted by this consumer chain
198+
AllowlistedRewardDenoms allowlisted_reward_denoms = 14;
197199
}
198200

199201
message QueryValidatorConsumerAddrRequest {

proto/interchain_security/ccv/provider/v1/tx.proto

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ message MsgCreateConsumer {
362362
PowerShapingParameters power_shaping_parameters = 5;
363363

364364
// allowlisted reward denoms of the consumer
365-
AllowlistedRewardDenoms allowlisted_reward_denoms = 7;
365+
AllowlistedRewardDenoms allowlisted_reward_denoms = 6;
366366
}
367367

368368
// MsgCreateConsumerResponse defines response type for MsgCreateConsumer

x/ccv/provider/client/cli/tx.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,14 @@ where create_consumer.json has the following structure:
255255
"denylist": [],
256256
"min_stake": "0",
257257
"allow_inactive_vals": false
258+
},
259+
"allowlisted_reward_denoms": {
260+
"denoms": ["ibc/...", "ibc/..."]
258261
}
259262
}
260263
261264
Note that both 'chain_id' and 'metadata' are mandatory;
262-
and both 'initialization_parameters' and 'power_shaping_parameters' are optional.
265+
and 'initialization_parameters', 'power_shaping_parameters' and 'allowlisted_reward_denoms' are optional.
263266
The parameters not provided are set to their zero value.
264267
`, version.AppName)),
265268
Args: cobra.ExactArgs(1),
@@ -286,7 +289,8 @@ The parameters not provided are set to their zero value.
286289
return fmt.Errorf("consumer data unmarshalling failed: %w", err)
287290
}
288291

289-
msg, err := types.NewMsgCreateConsumer(submitter, consCreate.ChainId, consCreate.Metadata, consCreate.InitializationParameters, consCreate.PowerShapingParameters)
292+
msg, err := types.NewMsgCreateConsumer(submitter, consCreate.ChainId, consCreate.Metadata, consCreate.InitializationParameters,
293+
consCreate.PowerShapingParameters, consCreate.AllowlistedRewardDenoms)
290294
if err != nil {
291295
return err
292296
}
@@ -349,12 +353,15 @@ where update_consumer.json has the following structure:
349353
"denylist": [],
350354
"min_stake": "0",
351355
"allow_inactive_vals": false
352-
}
356+
},
357+
"allowlisted_reward_denoms": {
358+
"denoms": ["ibc/...", "ibc/..."]
359+
}
353360
}
354361
355362
Note that only 'consumer_id' is mandatory. The others are optional.
356363
Not providing one of them will leave the existing values unchanged.
357-
Providing one of 'metadata', 'initialization_parameters' or 'power_shaping_parameters',
364+
Providing one of 'metadata', 'initialization_parameters', 'power_shaping_parameters', or 'allowlisted_reward_denoms'
358365
will update all the containing fields.
359366
If one of the fields is missing, it will be set to its zero value.
360367
`, version.AppName)),
@@ -387,7 +394,8 @@ If one of the fields is missing, it will be set to its zero value.
387394
return fmt.Errorf("consumer_id can't be empty")
388395
}
389396

390-
msg, err := types.NewMsgUpdateConsumer(owner, consUpdate.ConsumerId, consUpdate.NewOwnerAddress, consUpdate.Metadata, consUpdate.InitializationParameters, consUpdate.PowerShapingParameters)
397+
msg, err := types.NewMsgUpdateConsumer(owner, consUpdate.ConsumerId, consUpdate.NewOwnerAddress, consUpdate.Metadata,
398+
consUpdate.InitializationParameters, consUpdate.PowerShapingParameters, consUpdate.AllowlistedRewardDenoms)
391399
if err != nil {
392400
return err
393401
}

x/ccv/provider/keeper/grpc_query.go

+23-14
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,31 @@ func (k Keeper) GetConsumerChain(ctx sdk.Context, consumerId string) (types.Chai
114114
strDenylist[i] = addr.String()
115115
}
116116

117-
metadata, _ := k.GetConsumerMetadata(ctx, consumerId)
117+
metadata, err := k.GetConsumerMetadata(ctx, consumerId)
118+
if err != nil {
119+
return types.Chain{}, fmt.Errorf("cannot find metadata (%s): %s", consumerId, err.Error())
120+
}
121+
122+
allowlistedRewardDenoms, err := k.GetAllowlistedRewardDenoms(ctx, consumerId)
123+
if err != nil {
124+
return types.Chain{}, fmt.Errorf("cannot find allowlisted reward denoms (%s): %s", consumerId, err.Error())
125+
}
118126

119127
return types.Chain{
120-
ChainId: chainID,
121-
ClientId: clientID,
122-
Top_N: powerShapingParameters.Top_N,
123-
MinPowerInTop_N: minPowerInTopN,
124-
ValidatorSetCap: powerShapingParameters.ValidatorSetCap,
125-
ValidatorsPowerCap: powerShapingParameters.ValidatorsPowerCap,
126-
Allowlist: strAllowlist,
127-
Denylist: strDenylist,
128-
Phase: k.GetConsumerPhase(ctx, consumerId).String(),
129-
Metadata: metadata,
130-
AllowInactiveVals: powerShapingParameters.AllowInactiveVals,
131-
MinStake: powerShapingParameters.MinStake,
132-
ConsumerId: consumerId,
128+
ChainId: chainID,
129+
ClientId: clientID,
130+
Top_N: powerShapingParameters.Top_N,
131+
MinPowerInTop_N: minPowerInTopN,
132+
ValidatorSetCap: powerShapingParameters.ValidatorSetCap,
133+
ValidatorsPowerCap: powerShapingParameters.ValidatorsPowerCap,
134+
Allowlist: strAllowlist,
135+
Denylist: strDenylist,
136+
Phase: k.GetConsumerPhase(ctx, consumerId).String(),
137+
Metadata: metadata,
138+
AllowInactiveVals: powerShapingParameters.AllowInactiveVals,
139+
MinStake: powerShapingParameters.MinStake,
140+
ConsumerId: consumerId,
141+
AllowlistedRewardDenoms: &types.AllowlistedRewardDenoms{Denoms: allowlistedRewardDenoms},
133142
}, nil
134143
}
135144

x/ccv/provider/keeper/grpc_query_test.go

+32-22
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,12 @@ func TestGetConsumerChain(t *testing.T) {
459459

460460
metadataLists := []types.ConsumerMetadata{}
461461

462+
allowlistedRewardDenoms := []*types.AllowlistedRewardDenoms{
463+
{}, // no denoms
464+
{Denoms: []string{"ibc/1", "ibc/2"}},
465+
{Denoms: []string{"ibc/3", "ibc/4", "ibc/5"}},
466+
{Denoms: []string{"ibc/6"}}}
467+
462468
expectedGetAllOrder := []types.Chain{}
463469
for i, consumerID := range consumerIDs {
464470
pk.SetConsumerChainId(ctx, consumerID, chainIDs[i])
@@ -493,24 +499,27 @@ func TestGetConsumerChain(t *testing.T) {
493499
metadataLists = append(metadataLists, types.ConsumerMetadata{Name: chainIDs[i]})
494500
pk.SetConsumerMetadata(ctx, consumerID, metadataLists[i])
495501

502+
pk.SetAllowlistedRewardDenoms(ctx, consumerID, allowlistedRewardDenoms[i].Denoms)
503+
496504
phase := types.ConsumerPhase(int32(i + 1))
497505
pk.SetConsumerPhase(ctx, consumerID, phase)
498506

499507
expectedGetAllOrder = append(expectedGetAllOrder,
500508
types.Chain{
501-
ChainId: chainIDs[i],
502-
ClientId: clientID,
503-
Top_N: topN,
504-
MinPowerInTop_N: expectedMinPowerInTopNs[i],
505-
ValidatorSetCap: validatorSetCaps[i],
506-
ValidatorsPowerCap: validatorPowerCaps[i],
507-
Allowlist: strAllowlist,
508-
Denylist: strDenylist,
509-
Phase: phase.String(),
510-
Metadata: metadataLists[i],
511-
AllowInactiveVals: allowInactiveVals[i],
512-
MinStake: minStakes[i].Uint64(),
513-
ConsumerId: consumerIDs[i],
509+
ChainId: chainIDs[i],
510+
ClientId: clientID,
511+
Top_N: topN,
512+
MinPowerInTop_N: expectedMinPowerInTopNs[i],
513+
ValidatorSetCap: validatorSetCaps[i],
514+
ValidatorsPowerCap: validatorPowerCaps[i],
515+
Allowlist: strAllowlist,
516+
Denylist: strDenylist,
517+
Phase: phase.String(),
518+
Metadata: metadataLists[i],
519+
AllowInactiveVals: allowInactiveVals[i],
520+
MinStake: minStakes[i].Uint64(),
521+
ConsumerId: consumerIDs[i],
522+
AllowlistedRewardDenoms: allowlistedRewardDenoms[i],
514523
})
515524
}
516525

@@ -647,15 +656,16 @@ func TestQueryConsumerChains(t *testing.T) {
647656

648657
pk.SetConsumerPhase(ctx, consumerId, phases[i])
649658
c := types.Chain{
650-
ChainId: chainID,
651-
MinPowerInTop_N: -1,
652-
ValidatorsPowerCap: 0,
653-
ValidatorSetCap: 0,
654-
Allowlist: []string{},
655-
Denylist: []string{},
656-
Phase: phases[i].String(),
657-
Metadata: metadata,
658-
ConsumerId: consumerId,
659+
ChainId: chainID,
660+
MinPowerInTop_N: -1,
661+
ValidatorsPowerCap: 0,
662+
ValidatorSetCap: 0,
663+
Allowlist: []string{},
664+
Denylist: []string{},
665+
Phase: phases[i].String(),
666+
Metadata: metadata,
667+
ConsumerId: consumerId,
668+
AllowlistedRewardDenoms: &types.AllowlistedRewardDenoms{Denoms: []string{}},
659669
}
660670
consumerIds[i] = consumerId
661671
consumers[i] = &c

x/ccv/provider/keeper/msg_server.go

-10
Original file line numberDiff line numberDiff line change
@@ -415,11 +415,6 @@ func (k msgServer) CreateConsumer(goCtx context.Context, msg *types.MsgCreateCon
415415
}
416416

417417
if msg.AllowlistedRewardDenoms != nil {
418-
if len(msg.AllowlistedRewardDenoms.Denoms) > types.MaxAllowlistedRewardDenomsPerChain {
419-
return &resp, errorsmod.Wrapf(types.ErrInvalidAllowlistedRewardDenoms,
420-
fmt.Sprintf("a consumer chain cannot allowlist more than %d reward denoms", types.MaxAllowlistedRewardDenomsPerChain))
421-
}
422-
423418
err := k.UpdateAllowlistedRewardDenoms(ctx, consumerId, msg.AllowlistedRewardDenoms.Denoms)
424419
if err != nil {
425420
return &resp, errorsmod.Wrapf(types.ErrInvalidAllowlistedRewardDenoms,
@@ -603,11 +598,6 @@ func (k msgServer) UpdateConsumer(goCtx context.Context, msg *types.MsgUpdateCon
603598
}
604599

605600
if msg.AllowlistedRewardDenoms != nil {
606-
if len(msg.AllowlistedRewardDenoms.Denoms) > types.MaxAllowlistedRewardDenomsPerChain {
607-
return &resp, errorsmod.Wrapf(types.ErrInvalidAllowlistedRewardDenoms,
608-
fmt.Sprintf("a consumer chain cannot allowlist more than %d reward denoms", types.MaxAllowlistedRewardDenomsPerChain))
609-
}
610-
611601
if err := k.UpdateAllowlistedRewardDenoms(ctx, consumerId, msg.AllowlistedRewardDenoms.Denoms); err != nil {
612602
return &resp, errorsmod.Wrapf(types.ErrInvalidAllowlistedRewardDenoms,
613603
"cannot update allowlisted reward denoms: %s", err.Error())

x/ccv/provider/types/msg.go

+31
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package types
33
import (
44
"encoding/json"
55
"fmt"
6+
"github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
67
"strings"
78

89
ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
@@ -280,13 +281,15 @@ func (msg MsgSetConsumerCommissionRate) ValidateBasic() error {
280281
// NewMsgCreateConsumer creates a new MsgCreateConsumer instance
281282
func NewMsgCreateConsumer(submitter, chainId string, metadata ConsumerMetadata,
282283
initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters,
284+
allowlistedRewardDenoms *AllowlistedRewardDenoms,
283285
) (*MsgCreateConsumer, error) {
284286
return &MsgCreateConsumer{
285287
Submitter: submitter,
286288
ChainId: chainId,
287289
Metadata: metadata,
288290
InitializationParameters: initializationParameters,
289291
PowerShapingParameters: powerShapingParameters,
292+
AllowlistedRewardDenoms: allowlistedRewardDenoms,
290293
}, nil
291294
}
292295

@@ -326,12 +329,19 @@ func (msg MsgCreateConsumer) ValidateBasic() error {
326329
}
327330
}
328331

332+
if msg.AllowlistedRewardDenoms != nil {
333+
if err := ValidateAllowlistedRewardDenoms(*msg.AllowlistedRewardDenoms); err != nil {
334+
return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "AllowlistedRewardDenoms: %s", err.Error())
335+
}
336+
}
337+
329338
return nil
330339
}
331340

332341
// NewMsgUpdateConsumer creates a new MsgUpdateConsumer instance
333342
func NewMsgUpdateConsumer(owner, consumerId, ownerAddress string, metadata *ConsumerMetadata,
334343
initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters,
344+
allowlistedRewardDenoms *AllowlistedRewardDenoms,
335345
) (*MsgUpdateConsumer, error) {
336346
return &MsgUpdateConsumer{
337347
Owner: owner,
@@ -340,6 +350,7 @@ func NewMsgUpdateConsumer(owner, consumerId, ownerAddress string, metadata *Cons
340350
Metadata: metadata,
341351
InitializationParameters: initializationParameters,
342352
PowerShapingParameters: powerShapingParameters,
353+
AllowlistedRewardDenoms: allowlistedRewardDenoms,
343354
}, nil
344355
}
345356

@@ -369,6 +380,12 @@ func (msg MsgUpdateConsumer) ValidateBasic() error {
369380
}
370381
}
371382

383+
if msg.AllowlistedRewardDenoms != nil {
384+
if err := ValidateAllowlistedRewardDenoms(*msg.AllowlistedRewardDenoms); err != nil {
385+
return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "AllowlistedRewardDenoms: %s", err.Error())
386+
}
387+
}
388+
372389
return nil
373390
}
374391

@@ -514,6 +531,20 @@ func ValidatePowerShapingParameters(powerShapingParameters PowerShapingParameter
514531
return nil
515532
}
516533

534+
// ValidateAllowlistedRewardDenoms validates the provided allowlisted reward denoms
535+
func ValidateAllowlistedRewardDenoms(allowlistedRewardDenoms AllowlistedRewardDenoms) error {
536+
if len(allowlistedRewardDenoms.Denoms) > MaxAllowlistedRewardDenomsPerChain {
537+
return errorsmod.Wrapf(ErrInvalidAllowlistedRewardDenoms, "More than %d denoms", MaxAllowlistedRewardDenomsPerChain)
538+
}
539+
540+
for _, denom := range allowlistedRewardDenoms.Denoms {
541+
if err := types.ValidateIBCDenom(denom); err != nil {
542+
return errorsmod.Wrapf(ErrInvalidAllowlistedRewardDenoms, "Invalid denom (%s): %s", denom, err.Error())
543+
}
544+
}
545+
return nil
546+
}
547+
517548
// ValidateInitializationParameters validates that all the provided parameters are in the expected range
518549
func ValidateInitializationParameters(initializationParameters ConsumerInitializationParameters) error {
519550
if initializationParameters.InitialHeight.IsZero() {

x/ccv/provider/types/msg_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ func TestMsgCreateConsumerValidateBasic(t *testing.T) {
471471

472472
for _, tc := range testCases {
473473
validConsumerMetadata := types.ConsumerMetadata{Name: "name", Description: "description", Metadata: "metadata"}
474-
msg, err := types.NewMsgCreateConsumer("submitter", tc.chainId, validConsumerMetadata, nil, tc.powerShapingParameters)
474+
msg, err := types.NewMsgCreateConsumer("submitter", tc.chainId, validConsumerMetadata, nil, tc.powerShapingParameters, nil)
475475
require.NoError(t, err)
476476
err = msg.ValidateBasic()
477477
if tc.expPass {
@@ -546,7 +546,7 @@ func TestMsgUpdateConsumerValidateBasic(t *testing.T) {
546546

547547
for _, tc := range testCases {
548548
// TODO (PERMISSIONLESS) add more tests
549-
msg, _ := types.NewMsgUpdateConsumer("", "0", "cosmos1p3ucd3ptpw902fluyjzhq3ffgq4ntddac9sa3s", nil, nil, &tc.powerShapingParameters)
549+
msg, _ := types.NewMsgUpdateConsumer("", "0", "cosmos1p3ucd3ptpw902fluyjzhq3ffgq4ntddac9sa3s", nil, nil, &tc.powerShapingParameters, nil)
550550
err := msg.ValidateBasic()
551551
if tc.expPass {
552552
require.NoError(t, err, "valid case: %s should not return error. got %w", tc.name, err)

0 commit comments

Comments
 (0)