From c4ec8243a4f0e614a7470c4efdc4e6380313321b Mon Sep 17 00:00:00 2001 From: mpoke Date: Thu, 19 Sep 2024 13:10:30 +0200 Subject: [PATCH 1/9] add consumer ID to consumer genesis --- .../ccv/v1/shared_consumer.proto | 4 + tests/mbt/driver/setup.go | 1 + x/ccv/consumer/keeper/params.go | 8 + x/ccv/consumer/keeper/params_test.go | 3 +- x/ccv/consumer/types/genesis_test.go | 3 + x/ccv/consumer/types/params_test.go | 38 +++-- x/ccv/provider/keeper/proposal.go | 1 + x/ccv/types/errors.go | 3 + x/ccv/types/params.go | 6 + x/ccv/types/shared_consumer.pb.go | 159 ++++++++++++------ x/ccv/types/shared_params.go | 19 +++ x/ccv/types/shared_params_test.go | 23 +++ 12 files changed, 200 insertions(+), 68 deletions(-) create mode 100644 x/ccv/types/shared_params_test.go diff --git a/proto/interchain_security/ccv/v1/shared_consumer.proto b/proto/interchain_security/ccv/v1/shared_consumer.proto index 6c8cccfe86..36e189e1c0 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/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 9bf6459bf9..5cd4bfa121 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -491,6 +491,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/params.go b/x/ccv/consumer/keeper/params.go index 8d74205b8a..dec0a098e8 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -25,6 +25,7 @@ func (k Keeper) GetConsumerParams(ctx sdk.Context) ccvtypes.ConsumerParams { k.GetRewardDenoms(ctx), k.GetProviderRewardDenoms(ctx), k.GetRetryDelayPeriod(ctx), + k.GetConsumerId(ctx), ) } @@ -136,3 +137,10 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration { k.paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &period) return period } + +func (k Keeper) GetConsumerId(ctx sdk.Context) string { + // var cId string + // k.paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &cId) + // return cId + return "" +} diff --git a/x/ccv/consumer/keeper/params_test.go b/x/ccv/consumer/keeper/params_test.go index 536ae98236..801f96478d 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/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index c8c61776ec..7e62574b3b 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 8e81e2301c..e76422266d 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/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index d403af536e..29df3f3d57 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -358,6 +358,7 @@ func (k Keeper) MakeConsumerGenesis( []string{}, []string{}, ccv.DefaultRetryDelayPeriod, + "0", ) gen = *ccv.NewInitialConsumerGenesisState( diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index e492984e48..2cc65e484c 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -22,4 +22,7 @@ var ( ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 14, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 15, "consumer chain not found") 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 5759ce28f9..ee2446845f 100644 --- a/x/ccv/types/params.go +++ b/x/ccv/types/params.go @@ -68,6 +68,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, @@ -84,6 +85,7 @@ func NewParams(enabled bool, blocksPerDistributionTransmission int64, RewardDenoms: rewardDenoms, ProviderRewardDenoms: providerRewardDenoms, RetryDelayPeriod: retryDelayPeriod, + ConsumerId: consumerId, } } @@ -104,6 +106,7 @@ func DefaultParams() ConsumerParams { rewardDenoms, provideRewardDenoms, DefaultRetryDelayPeriod, + "0", ) } @@ -145,6 +148,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 92d7105d41..b41a3287b6 100644 --- a/x/ccv/types/shared_consumer.pb.go +++ b/x/ccv/types/shared_consumer.pb.go @@ -75,6 +75,9 @@ 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. Used by the consumer module to send + // ICS rewards. + ConsumerId string `protobuf:"bytes,14,opt,name=consumer_id,json=consumerId,proto3" json:"consumer_id,omitempty"` } func (m *ConsumerParams) Reset() { *m = ConsumerParams{} } @@ -202,6 +205,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,59 +351,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, 0x6f, 0xe4, 0x34, - 0x14, 0x6e, 0x3a, 0x4b, 0x77, 0xea, 0x69, 0xb7, 0x8b, 0x29, 0x4b, 0xe8, 0x4a, 0xd3, 0x6c, 0xe1, - 0x30, 0x02, 0x6d, 0x42, 0x4b, 0x25, 0x24, 0x6e, 0xb4, 0x65, 0x59, 0xf6, 0xd0, 0xce, 0xa6, 0x65, - 0x91, 0xe0, 0x60, 0x39, 0xf6, 0x9b, 0x89, 0x45, 0x62, 0x47, 0xb6, 0x93, 0xd2, 0x5f, 0xc0, 0x95, - 0x23, 0x3f, 0x69, 0xb9, 0xed, 0x91, 0x13, 0xa0, 0xf6, 0x8f, 0xa0, 0x38, 0x49, 0x9b, 0x41, 0x14, - 0xca, 0xcd, 0xcf, 0xef, 0xfb, 0xbe, 0xe4, 0x7b, 0xf6, 0x7b, 0x46, 0x9f, 0x08, 0x69, 0x41, 0xb3, - 0x94, 0x0a, 0x49, 0x0c, 0xb0, 0x52, 0x0b, 0x7b, 0x11, 0x31, 0x56, 0x45, 0xd5, 0x6e, 0x64, 0x52, - 0xaa, 0x81, 0x13, 0xa6, 0xa4, 0x29, 0x73, 0xd0, 0x61, 0xa1, 0x95, 0x55, 0x78, 0xeb, 0x1f, 0x18, - 0x21, 0x63, 0x55, 0x58, 0xed, 0x6e, 0x3d, 0xb6, 0x20, 0x39, 0xe8, 0x5c, 0x48, 0x1b, 0xd1, 0x84, - 0x89, 0xc8, 0x5e, 0x14, 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, 0xb5, 0x82, 0x1e, 0x1c, 0xb6, 0xbf, 0x3c, 0xa5, 0x9a, - 0xe6, 0x06, 0xfb, 0xe8, 0x3e, 0x48, 0x9a, 0x64, 0xc0, 0x7d, 0x2f, 0xf0, 0x26, 0xc3, 0xb8, 0x0b, - 0xf1, 0x09, 0xfa, 0x30, 0xc9, 0x14, 0xfb, 0xc1, 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, 0x93, 0x06, 0x3b, 0x05, 0x7d, 0xd4, 0x43, 0x9e, 0xf5, 0x80, 0xf8, 0x05, 0x7a, 0x72, 0xab, - 0x0a, 0x61, 0x29, 0x95, 0x12, 0x32, 0x7f, 0x10, 0x78, 0x93, 0xd5, 0x78, 0x9b, 0xdf, 0x22, 0x72, - 0xd8, 0xc0, 0xf0, 0xe7, 0x68, 0xab, 0xd0, 0xaa, 0x12, 0x1c, 0x34, 0x99, 0x01, 0x90, 0x42, 0xa9, - 0x8c, 0x50, 0xce, 0x35, 0x31, 0x56, 0xfb, 0xf7, 0x9c, 0xc8, 0xa3, 0x0e, 0xf1, 0x0c, 0x60, 0xaa, - 0x54, 0xf6, 0x05, 0xe7, 0xfa, 0xd4, 0x6a, 0xfc, 0x12, 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, 0x51, 0x5b, 0xd8, 0x83, 0xe1, 0xeb, 0xdf, 0xb7, 0x97, 0x7e, 0xf9, 0x63, 0xdb, - 0x8b, 0x1f, 0x32, 0x56, 0x9d, 0x35, 0xec, 0xa9, 0x23, 0xe3, 0xef, 0xd1, 0x7b, 0xce, 0xcd, 0x0c, - 0xf4, 0xdf, 0x75, 0x57, 0xee, 0xae, 0xfb, 0x6e, 0xa7, 0xb1, 0x28, 0xfe, 0x1c, 0x05, 0xdd, 0x3d, - 0x23, 0x1a, 0x16, 0x4a, 0x38, 0xd3, 0x94, 0xd5, 0x0b, 0xff, 0xbe, 0x73, 0x3c, 0xee, 0x70, 0xf1, - 0x02, 0xec, 0x59, 0x8b, 0xc2, 0x4f, 0x11, 0x4e, 0x85, 0xb1, 0x4a, 0x0b, 0x46, 0x33, 0x02, 0xd2, - 0x6a, 0x01, 0xc6, 0x1f, 0xba, 0x03, 0x7c, 0xfb, 0x26, 0xf3, 0x65, 0x93, 0xc0, 0xc7, 0xe8, 0x61, - 0x29, 0x13, 0x25, 0xb9, 0x90, 0xf3, 0xce, 0xce, 0xea, 0xdd, 0xed, 0x6c, 0x5c, 0x93, 0x5b, 0x23, - 0x9f, 0xa1, 0x47, 0x46, 0xcd, 0x2c, 0x51, 0x85, 0x25, 0x75, 0x85, 0x6c, 0xaa, 0xc1, 0xa4, 0x2a, - 0xe3, 0x3e, 0xaa, 0x7f, 0xff, 0x60, 0xd9, 0xf7, 0xe2, 0x77, 0x6a, 0xc4, 0x49, 0x61, 0x4f, 0x4a, - 0x7b, 0xd6, 0xa5, 0xf1, 0x07, 0x68, 0x5d, 0xc3, 0x39, 0xd5, 0x9c, 0x70, 0x90, 0x2a, 0x37, 0xfe, - 0x28, 0x18, 0x4c, 0x56, 0xe3, 0xb5, 0x66, 0xf3, 0xc8, 0xed, 0xe1, 0x7d, 0x74, 0x7d, 0xe0, 0x64, - 0x11, 0xbd, 0xe6, 0xd0, 0x9b, 0x5d, 0x36, 0xee, 0xb3, 0x5e, 0x22, 0xac, 0xc1, 0xea, 0x0b, 0xc2, - 0x21, 0xa3, 0x17, 0x9d, 0xcb, 0xf5, 0xff, 0x71, 0x19, 0x1c, 0xfd, 0xa8, 0x66, 0x37, 0x36, 0x77, - 0x7e, 0xf5, 0xd0, 0x66, 0xd7, 0x65, 0x5f, 0x81, 0x04, 0x23, 0xcc, 0xa9, 0xa5, 0x16, 0xf0, 0x73, - 0xb4, 0x52, 0xb8, 0xae, 0x73, 0xad, 0x36, 0xda, 0xfb, 0x28, 0xbc, 0x7d, 0x5e, 0x84, 0x8b, 0x7d, - 0x7a, 0x70, 0xaf, 0xfe, 0x60, 0xdc, 0xf2, 0xf1, 0x0b, 0x34, 0xec, 0xdc, 0xb8, 0xfe, 0x1b, 0xed, - 0x4d, 0xfe, 0x4d, 0x6b, 0xda, 0x62, 0xbf, 0x96, 0x33, 0xd5, 0x2a, 0x5d, 0xf3, 0xf1, 0x63, 0xb4, - 0x2a, 0xe1, 0x9c, 0x38, 0xa6, 0x6b, 0xbf, 0x61, 0x3c, 0x94, 0x70, 0x7e, 0x58, 0xc7, 0x3b, 0x3f, - 0x2d, 0xa3, 0xb5, 0x3e, 0x1b, 0x1f, 0xa3, 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, 0x13, 0xe0, 0x6f, 0xd1, 0x46, 0x7d, 0x69, 0x41, 0x9a, 0xd2, 0xb4, 0x92, 0x8d, 0xa1, - 0xf0, 0x3f, 0x25, 0x3b, 0x5a, 0xa3, 0xfa, 0x80, 0x2d, 0xc4, 0xf8, 0x18, 0x6d, 0x08, 0x29, 0xac, - 0xa0, 0x19, 0xa9, 0x68, 0x46, 0x0c, 0x58, 0x7f, 0x10, 0x0c, 0x26, 0xa3, 0xbd, 0xa0, 0xaf, 0x53, - 0x4f, 0xe2, 0xf0, 0x15, 0xcd, 0x04, 0xa7, 0x56, 0xe9, 0x6f, 0x0a, 0x4e, 0x2d, 0xb4, 0x15, 0x5a, - 0x6f, 0xe9, 0xaf, 0x68, 0x76, 0x0a, 0xf6, 0xe0, 0xf8, 0xf5, 0xe5, 0xd8, 0x7b, 0x73, 0x39, 0xf6, - 0xfe, 0xbc, 0x1c, 0x7b, 0x3f, 0x5f, 0x8d, 0x97, 0xde, 0x5c, 0x8d, 0x97, 0x7e, 0xbb, 0x1a, 0x2f, - 0x7d, 0xb7, 0x3f, 0x17, 0x36, 0x2d, 0x93, 0x90, 0xa9, 0x3c, 0x62, 0xca, 0xe4, 0xca, 0x44, 0x37, - 0x67, 0xf1, 0xf4, 0xfa, 0xe5, 0xa8, 0xf6, 0xa3, 0x1f, 0xdd, 0xf3, 0xe1, 0x06, 0x7f, 0xb2, 0xe2, - 0x2e, 0xd5, 0xa7, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x67, 0x6a, 0xf6, 0x66, 0x06, 0x00, - 0x00, + // 832 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xcf, 0x6f, 0xe4, 0x34, + 0x14, 0x6e, 0x3a, 0x4b, 0x3b, 0xf5, 0xf4, 0xc7, 0x62, 0xca, 0x12, 0xba, 0xd2, 0x74, 0xb6, 0x70, + 0x18, 0x81, 0x36, 0xa1, 0xa5, 0x12, 0x12, 0x37, 0xda, 0xb2, 0xec, 0xee, 0xa1, 0x9d, 0x4d, 0xcb, + 0x22, 0xc1, 0xc1, 0x72, 0xec, 0x37, 0x13, 0x8b, 0xc4, 0x8e, 0x6c, 0x27, 0xa5, 0x77, 0x24, 0xae, + 0x1c, 0xf9, 0x93, 0x96, 0xdb, 0x1e, 0x39, 0x01, 0x6a, 0xff, 0x11, 0x14, 0x27, 0x99, 0x66, 0x10, + 0x85, 0xe5, 0x96, 0x67, 0x7f, 0xdf, 0x17, 0x7f, 0xef, 0xf9, 0x3d, 0xa3, 0x4f, 0x84, 0xb4, 0xa0, + 0x59, 0x42, 0x85, 0x24, 0x06, 0x58, 0xa1, 0x85, 0xbd, 0x0a, 0x19, 0x2b, 0xc3, 0x72, 0x3f, 0x34, + 0x09, 0xd5, 0xc0, 0x09, 0x53, 0xd2, 0x14, 0x19, 0xe8, 0x20, 0xd7, 0xca, 0x2a, 0xbc, 0xf3, 0x0f, + 0x8c, 0x80, 0xb1, 0x32, 0x28, 0xf7, 0x77, 0x1e, 0x5a, 0x90, 0x1c, 0x74, 0x26, 0xa4, 0x0d, 0x69, + 0xcc, 0x44, 0x68, 0xaf, 0x72, 0x30, 0x35, 0x71, 0x27, 0x14, 0x31, 0x0b, 0x53, 0x31, 0x4b, 0x2c, + 0x4b, 0x05, 0x48, 0x6b, 0xc2, 0x0e, 0xba, 0xdc, 0xef, 0x44, 0x0d, 0x61, 0x38, 0x53, 0x6a, 0x96, + 0x42, 0xe8, 0xa2, 0xb8, 0x98, 0x86, 0xbc, 0xd0, 0xd4, 0x0a, 0x25, 0x9b, 0xfd, 0xed, 0x99, 0x9a, + 0x29, 0xf7, 0x19, 0x56, 0x5f, 0xf5, 0xea, 0xde, 0x8f, 0xab, 0x68, 0xf3, 0xb8, 0x39, 0xf2, 0x84, + 0x6a, 0x9a, 0x19, 0xec, 0xa3, 0x55, 0x90, 0x34, 0x4e, 0x81, 0xfb, 0xde, 0xc8, 0x1b, 0xf7, 0xa3, + 0x36, 0xc4, 0x67, 0xe8, 0xc3, 0x38, 0x55, 0xec, 0x7b, 0x43, 0x72, 0xd0, 0x84, 0x0b, 0x63, 0xb5, + 0x88, 0x8b, 0xea, 0x1f, 0xc4, 0x6a, 0x2a, 0x4d, 0x26, 0x8c, 0x11, 0x4a, 0xfa, 0xcb, 0x23, 0x6f, + 0xdc, 0x8b, 0x1e, 0xd5, 0xd8, 0x09, 0xe8, 0x93, 0x0e, 0xf2, 0xa2, 0x03, 0xc4, 0xcf, 0xd1, 0xa3, + 0x3b, 0x55, 0x08, 0x4b, 0xa8, 0x94, 0x90, 0xfa, 0xbd, 0x91, 0x37, 0x5e, 0x8b, 0x76, 0xf9, 0x1d, + 0x22, 0xc7, 0x35, 0x0c, 0x7f, 0x8e, 0x76, 0x72, 0xad, 0x4a, 0xc1, 0x41, 0x93, 0x29, 0x00, 0xc9, + 0x95, 0x4a, 0x09, 0xe5, 0x5c, 0x13, 0x63, 0xb5, 0x7f, 0xcf, 0x89, 0x3c, 0x68, 0x11, 0x4f, 0x00, + 0x26, 0x4a, 0xa5, 0x5f, 0x70, 0xae, 0xcf, 0xad, 0xc6, 0x2f, 0x10, 0x66, 0xac, 0x24, 0x56, 0x64, + 0xa0, 0x0a, 0x5b, 0xb9, 0x13, 0x8a, 0xfb, 0x6f, 0x8d, 0xbc, 0xf1, 0xe0, 0xe0, 0xfd, 0xa0, 0x4e, + 0x6c, 0xd0, 0x26, 0x36, 0x38, 0x69, 0x12, 0x7b, 0xd4, 0x7f, 0xf5, 0xfb, 0xee, 0xd2, 0x2f, 0x7f, + 0xec, 0x7a, 0xd1, 0x7d, 0xc6, 0xca, 0x8b, 0x9a, 0x3d, 0x71, 0x64, 0xfc, 0x1d, 0x7a, 0xcf, 0xb9, + 0x99, 0x82, 0xfe, 0xbb, 0xee, 0xca, 0x9b, 0xeb, 0xbe, 0xdb, 0x6a, 0x2c, 0x8a, 0x3f, 0x45, 0xa3, + 0xf6, 0x9e, 0x11, 0x0d, 0x0b, 0x29, 0x9c, 0x6a, 0xca, 0xaa, 0x0f, 0x7f, 0xd5, 0x39, 0x1e, 0xb6, + 0xb8, 0x68, 0x01, 0xf6, 0xa4, 0x41, 0xe1, 0xc7, 0x08, 0x27, 0xc2, 0x58, 0xa5, 0x05, 0xa3, 0x29, + 0x01, 0x69, 0xb5, 0x00, 0xe3, 0xf7, 0x5d, 0x01, 0xdf, 0xbe, 0xdd, 0xf9, 0xb2, 0xde, 0xc0, 0xa7, + 0xe8, 0x7e, 0x21, 0x63, 0x25, 0xb9, 0x90, 0xb3, 0xd6, 0xce, 0xda, 0x9b, 0xdb, 0xd9, 0x9a, 0x93, + 0x1b, 0x23, 0x9f, 0xa1, 0x07, 0x46, 0x4d, 0x2d, 0x51, 0xb9, 0x25, 0x55, 0x86, 0x6c, 0xa2, 0xc1, + 0x24, 0x2a, 0xe5, 0x3e, 0xaa, 0x8e, 0x7f, 0xb4, 0xec, 0x7b, 0xd1, 0x3b, 0x15, 0xe2, 0x2c, 0xb7, + 0x67, 0x85, 0xbd, 0x68, 0xb7, 0xf1, 0x07, 0x68, 0x43, 0xc3, 0x25, 0xd5, 0x9c, 0x70, 0x90, 0x2a, + 0x33, 0xfe, 0x60, 0xd4, 0x1b, 0xaf, 0x45, 0xeb, 0xf5, 0xe2, 0x89, 0x5b, 0xc3, 0x87, 0x68, 0x5e, + 0x70, 0xb2, 0x88, 0x5e, 0x77, 0xe8, 0xed, 0x76, 0x37, 0xea, 0xb2, 0x5e, 0x20, 0xac, 0xc1, 0xea, + 0x2b, 0xc2, 0x21, 0xa5, 0x57, 0xad, 0xcb, 0x8d, 0xff, 0x71, 0x19, 0x1c, 0xfd, 0xa4, 0x62, 0x37, + 0x36, 0x77, 0xd1, 0x60, 0x5e, 0x2f, 0xc1, 0xfd, 0x4d, 0x57, 0x1a, 0xd4, 0x2e, 0x3d, 0xe3, 0x7b, + 0xbf, 0x7a, 0x68, 0xbb, 0x6d, 0xc3, 0xaf, 0x40, 0x82, 0x11, 0xe6, 0xdc, 0x52, 0x0b, 0xf8, 0x29, + 0x5a, 0xc9, 0x5d, 0x5b, 0xba, 0x5e, 0x1c, 0x1c, 0x7c, 0x14, 0xdc, 0x3d, 0x50, 0x82, 0xc5, 0x46, + 0x3e, 0xba, 0x57, 0x9d, 0x28, 0x6a, 0xf8, 0xf8, 0x39, 0xea, 0xb7, 0x76, 0x5d, 0x83, 0x0e, 0x0e, + 0xc6, 0xff, 0xa6, 0x35, 0x69, 0xb0, 0xcf, 0xe4, 0x54, 0x35, 0x4a, 0x73, 0x3e, 0x7e, 0x88, 0xd6, + 0x24, 0x5c, 0x12, 0xc7, 0x74, 0xfd, 0xd9, 0x8f, 0xfa, 0x12, 0x2e, 0x8f, 0xab, 0x78, 0xef, 0xa7, + 0x65, 0xb4, 0xde, 0x65, 0xe3, 0x53, 0xb4, 0x5e, 0xcf, 0x30, 0x62, 0x2a, 0x4f, 0x8d, 0x93, 0x8f, + 0x03, 0x11, 0xb3, 0xa0, 0x3b, 0xe1, 0x82, 0xce, 0x4c, 0xab, 0xdc, 0xb8, 0x55, 0x97, 0x86, 0x68, + 0xc0, 0x6e, 0x03, 0xfc, 0x0d, 0xda, 0xaa, 0x52, 0x07, 0xd2, 0x14, 0xa6, 0x91, 0xac, 0x0d, 0x05, + 0xff, 0x29, 0xd9, 0xd2, 0x6a, 0xd5, 0x4d, 0xb6, 0x10, 0xe3, 0x53, 0xb4, 0x25, 0xa4, 0xb0, 0x82, + 0xa6, 0xa4, 0xa4, 0x29, 0x31, 0x60, 0xfd, 0xde, 0xa8, 0x37, 0x1e, 0x1c, 0x8c, 0xba, 0x3a, 0xd5, + 0xa8, 0x0e, 0x5e, 0xd2, 0x54, 0x70, 0x6a, 0x95, 0xfe, 0x3a, 0xe7, 0xd4, 0x42, 0x93, 0xa1, 0x8d, + 0x86, 0xfe, 0x92, 0xa6, 0xe7, 0x60, 0x8f, 0x4e, 0x5f, 0x5d, 0x0f, 0xbd, 0xd7, 0xd7, 0x43, 0xef, + 0xcf, 0xeb, 0xa1, 0xf7, 0xf3, 0xcd, 0x70, 0xe9, 0xf5, 0xcd, 0x70, 0xe9, 0xb7, 0x9b, 0xe1, 0xd2, + 0xb7, 0x87, 0x33, 0x61, 0x93, 0x22, 0x0e, 0x98, 0xca, 0x42, 0xa6, 0x4c, 0xa6, 0x4c, 0x78, 0x5b, + 0x8b, 0xc7, 0xf3, 0xa7, 0xa5, 0x3c, 0x0c, 0x7f, 0x70, 0xef, 0x8b, 0x7b, 0x19, 0xe2, 0x15, 0x77, + 0xeb, 0x3e, 0xfd, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xf1, 0x1a, 0xba, 0x87, 0x06, 0x00, 0x00, } func (m *ConsumerParams) Marshal() (dAtA []byte, err error) { @@ -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 566737c0b9..0097476824 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -2,10 +2,14 @@ package types import ( fmt "fmt" + "strconv" + "strings" "time" ibchost "github.com/cosmos/ibc-go/v7/modules/core/24-host" + errorsmod "cosmossdk.io/errors" + sdktypes "github.com/cosmos/cosmos-sdk/types" ) @@ -112,3 +116,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..9f73aaaf9d --- /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/v4/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 +} From b43bd93d4733d1166992e29f2f0b6ce754c87bcc Mon Sep 17 00:00:00 2001 From: mpoke Date: Thu, 19 Sep 2024 14:23:57 +0200 Subject: [PATCH 2/9] add RewardMemo to token transfer --- x/ccv/consumer/keeper/distribution.go | 6 +++- x/ccv/consumer/keeper/params.go | 5 +++ x/ccv/types/wire.go | 52 +++++++++++++++++++++++++++ x/ccv/types/wire_test.go | 14 ++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/x/ccv/consumer/keeper/distribution.go b/x/ccv/consumer/keeper/distribution.go index 3cc5b69111..8ec85ef22f 100644 --- a/x/ccv/consumer/keeper/distribution.go +++ b/x/ccv/consumer/keeper/distribution.go @@ -117,6 +117,10 @@ 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 @@ -133,7 +137,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 dec0a098e8..db2cca7779 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -144,3 +144,8 @@ func (k Keeper) GetConsumerId(ctx sdk.Context) string { // return cId return "" } + +func (k Keeper) GetConsumerId(ctx sdk.Context) string { + params := k.GetConsumerParams(ctx) + return params.ConsumerId +} 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 ab6692912e..a704fa3d2c 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) +} From 592c0b1c5661b6156a5f5b27572e9aff96119c30 Mon Sep 17 00:00:00 2001 From: mpoke Date: Thu, 19 Sep 2024 17:56:10 +0200 Subject: [PATCH 3/9] handle memo on provider side --- x/ccv/consumer/keeper/params.go | 12 +++--------- x/ccv/types/params.go | 3 +++ x/ccv/types/shared_params.go | 12 ++++++++---- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/x/ccv/consumer/keeper/params.go b/x/ccv/consumer/keeper/params.go index db2cca7779..cfc48a31f1 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -139,13 +139,7 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration { } func (k Keeper) GetConsumerId(ctx sdk.Context) string { - // var cId string - // k.paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &cId) - // return cId - return "" -} - -func (k Keeper) GetConsumerId(ctx sdk.Context) string { - params := k.GetConsumerParams(ctx) - return params.ConsumerId + var cId string + k.paramStore.Get(ctx, ccvtypes.KeyConsumerId, &cId) + return cId } diff --git a/x/ccv/types/params.go b/x/ccv/types/params.go index ee2446845f..4251a991bc 100644 --- a/x/ccv/types/params.go +++ b/x/ccv/types/params.go @@ -54,6 +54,7 @@ var ( KeyRewardDenoms = []byte("RewardDenoms") KeyProviderRewardDenoms = []byte("ProviderRewardDenoms") KeyRetryDelayPeriod = []byte("RetryDelayPeriod") + KeyConsumerId = []byte("ConsumerId") ) // ParamKeyTable type declaration for parameters @@ -180,6 +181,8 @@ func (p *ConsumerParams) ParamSetPairs() paramtypes.ParamSetPairs { p.ProviderRewardDenoms, ValidateDenoms), paramtypes.NewParamSetPair(KeyRetryDelayPeriod, p.RetryDelayPeriod, ValidateDuration), + paramtypes.NewParamSetPair(KeyConsumerId, + p.ConsumerId, ValidateConsumerId), } } diff --git a/x/ccv/types/shared_params.go b/x/ccv/types/shared_params.go index 0097476824..e3f8301b12 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -118,15 +118,19 @@ func CalculateTrustPeriod(unbondingPeriod time.Duration, defaultTrustPeriodFract } // ValidateConsumerId validates the provided consumer id and returns an error if it is not valid -func ValidateConsumerId(consumerId string) error { - if strings.TrimSpace(consumerId) == "" { +func ValidateConsumerId(i interface{}) error { + str, ok := i.(string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + if strings.TrimSpace(str) == "" { return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id cannot be blank") } // check that `consumerId` corresponds to a `uint64` - _, err := strconv.ParseUint(consumerId, 10, 64) + _, err := strconv.ParseUint(str, 10, 64) if err != nil { - return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id (%s) cannot be parsed: %s", consumerId, err.Error()) + return errorsmod.Wrapf(ErrInvalidConsumerId, "consumer id (%s) cannot be parsed: %s", str, err.Error()) } return nil From 6349db4a021a7e754d08ae95081e5f1131e7f764 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 25 Sep 2024 14:27:19 +0200 Subject: [PATCH 4/9] add consumer migration info --- UPGRADING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 235010079d..c5e7375810 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,28 @@ This guide provides instructions for upgrading to specific versions of Replicated Security. +## [v4.5.x](https://github.com/cosmos/interchain-security/releases/tag/v4.5.0) + +### Consumer + +Upgrading a consumer from v4.4.x to v4.5.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.GetConsumerParams(ctx) + if err != nil { + return err + } + params.ConsumerId = ConsumerId + return consumerKeeper.SetParams(ctx, params) +} +``` + ## [v4.4.x](https://github.com/cosmos/interchain-security/releases/tag/v4.4.0) ### Provider From 6469bba83d52aae772e8afe11eed74c5ac514139 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 25 Sep 2024 16:22:11 +0200 Subject: [PATCH 5/9] add changelog --- .../unreleased/features/2290-add-memo-to-ICS-rewards.md.md | 4 ++++ .../state-breaking/2290-add-memo-to-ICS-rewards.md.md | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 .changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.md create mode 100644 .changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md.md diff --git a/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.md b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.md new file mode 100644 index 0000000000..421967ddab --- /dev/null +++ b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.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.md b/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md.md new file mode 100644 index 0000000000..421967ddab --- /dev/null +++ b/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md.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 From fc278d779dba25c0968669580e8db4ebea4b0d17 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 27 Sep 2024 17:55:38 +0200 Subject: [PATCH 6/9] fix changelog --- ...-memo-to-ICS-rewards.md.md => 2290-add-memo-to-ICS-rewards.md} | 0 ...-memo-to-ICS-rewards.md.md => 2290-add-memo-to-ICS-rewards.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .changelog/unreleased/features/{2290-add-memo-to-ICS-rewards.md.md => 2290-add-memo-to-ICS-rewards.md} (100%) rename .changelog/unreleased/state-breaking/{2290-add-memo-to-ICS-rewards.md.md => 2290-add-memo-to-ICS-rewards.md} (100%) diff --git a/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.md b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md similarity index 100% rename from .changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md.md rename to .changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md diff --git a/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md.md b/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md similarity index 100% rename from .changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md.md rename to .changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md From 9a1b02f62b54dea5a24ea6e106cd72af9f89f191 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 27 Sep 2024 18:19:02 +0200 Subject: [PATCH 7/9] feat!: add memo to IBC transfers of ICS rewards (#2290) * add consumer ID to consumer genesis * add RewardMemo to token transfer * handle memo on provider side * fix democ tests * wip * add e2e-test * refactor consumer reward tests * add consumer migration info * add changelog * fix changelog filename --------- Co-authored-by: Simon Noetzlin (cherry picked from commit 0d7829598fa6e383d036571a80eadc3d57964ab9) # Conflicts: # UPGRADING.md # tests/e2e/actions.go # tests/e2e/state.go # tests/e2e/steps_democracy.go # tests/e2e/steps_inactive_vals.go # tests/e2e/test_driver.go # tests/e2e/testlib/types.go # tests/e2e/v4/state.go # x/ccv/consumer/migrations/v3/legacy_params.go # x/ccv/provider/ibc_middleware.go # x/ccv/provider/keeper/consumer_lifecycle.go # x/ccv/provider/keeper/consumer_lifecycle_test.go # x/ccv/provider/keeper/grpc_query.go # x/ccv/provider/types/errors.go # x/ccv/provider/types/msg.go # x/ccv/provider/types/msg_test.go # x/ccv/types/errors.go # x/ccv/types/shared_consumer.pb.go # x/ccv/types/shared_params.go --- .../features/2290-add-memo-to-ICS-rewards.md | 4 + .../2290-add-memo-to-ICS-rewards.md | 4 + UPGRADING.md | 178 +++ .../ccv/v1/shared_consumer.proto | 4 + tests/e2e/action_rapid_test.go | 2 +- tests/e2e/actions.go | 333 +++++- tests/e2e/state.go | 122 +- tests/e2e/state_rapid_test.go | 4 +- tests/e2e/steps_democracy.go | 136 ++- tests/e2e/steps_inactive_vals.go | 973 +++++++++++++++ tests/e2e/test_driver.go | 18 + tests/e2e/testlib/types.go | 366 ++++++ tests/e2e/v4/state.go | 648 ++++++++++ tests/mbt/driver/setup.go | 1 + x/ccv/consumer/keeper/distribution.go | 7 +- x/ccv/consumer/keeper/params.go | 5 + x/ccv/consumer/keeper/params_test.go | 3 +- x/ccv/consumer/migrations/v3/legacy_params.go | 108 ++ x/ccv/consumer/types/genesis_test.go | 3 + x/ccv/consumer/types/params_test.go | 38 +- x/ccv/provider/ibc_middleware.go | 69 ++ x/ccv/provider/keeper/consumer_lifecycle.go | 663 +++++++++++ .../keeper/consumer_lifecycle_test.go | 1046 +++++++++++++++++ x/ccv/provider/keeper/grpc_query.go | 50 + x/ccv/provider/types/errors.go | 36 + x/ccv/provider/types/msg.go | 327 ++++++ x/ccv/provider/types/msg_test.go | 649 ++++++++++ x/ccv/types/errors.go | 6 + x/ccv/types/params.go | 6 + x/ccv/types/shared_consumer.pb.go | 108 ++ x/ccv/types/shared_params.go | 24 + x/ccv/types/shared_params_test.go | 23 + x/ccv/types/wire.go | 52 + x/ccv/types/wire_test.go | 14 + 34 files changed, 5998 insertions(+), 32 deletions(-) create mode 100644 .changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md create mode 100644 .changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md create mode 100644 tests/e2e/steps_inactive_vals.go create mode 100644 tests/e2e/testlib/types.go create mode 100644 tests/e2e/v4/state.go create mode 100644 x/ccv/consumer/migrations/v3/legacy_params.go create mode 100644 x/ccv/provider/keeper/consumer_lifecycle.go create mode 100644 x/ccv/provider/keeper/consumer_lifecycle_test.go create mode 100644 x/ccv/provider/types/msg_test.go create mode 100644 x/ccv/types/shared_params_test.go 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 235010079d..742a4550f8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,183 @@ # Upgrading Replicated Security +<<<<<<< HEAD +======= +## 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: + +```go +// InitializeMaxProviderConsensusParam initializes the MaxProviderConsensusValidators parameter. +// It is set to 180, which is the current number of validators participating in consensus on the Cosmos Hub. +// This parameter will be used to govern the number of validators participating in consensus on the Cosmos Hub, +// and takes over this role from the MaxValidators parameter in the staking module. +func InitializeMaxProviderConsensusParam(ctx sdk.Context, providerKeeper providerkeeper.Keeper) { + params := providerKeeper.GetParams(ctx) + params.MaxProviderConsensusValidators = NewMaxProviderConsensusValidators + providerKeeper.SetParams(ctx, params) +} +``` + +```go +// SetMaxValidators sets the MaxValidators parameter in the staking module to 200, +// which is the current number of 180 plus 20. +// This is done in concert with the introduction of the inactive-validators feature +// in Interchain Security, after which the number of validators +// participating in consensus on the Cosmos Hub will be governed by the +// MaxProviderConsensusValidators parameter in the provider module. +func SetMaxValidators(ctx sdk.Context, stakingKeeper stakingkeeper.Keeper) error { + params, err := stakingKeeper.GetParams(ctx) + if err != nil { + return err + } + params.MaxValidators = NewMaxValidators + return stakingKeeper.SetParams(ctx, params) +} +``` + +```go +// InitializeLastProviderConsensusValidatorSet initializes the last provider consensus validator set +// by setting it to the first 180 validators from the current validator set of the staking module. +func InitializeLastProviderConsensusValidatorSet(ctx sdk.Context, providerKeeper providerkeeper.Keeper, stakingKeeper stakingkeeper.Keeper) error { + vals, err := stakingKeeper.GetBondedValidatorsByPower(ctx) + if err != nil { + return err + } + // cut the validator set to the first 180 validators + if len(vals) > NewMaxProviderConsensusValidators { + vals = vals[:NewMaxProviderConsensusValidators] + } + // create consensus validators for the staking validators + lastValidators := []providertypes.ConsensusValidator{} + for _, val := range vals { + consensusVal, err := providerKeeper.CreateProviderConsensusValidator(ctx, val) + if err != nil { + return err + } + + lastValidators = append(lastValidators, consensusVal) + } + return providerKeeper.SetLastProviderConsensusValSet(ctx, lastValidators) +} +``` + +```go +// SetICSConsumerMetadata sets the metadata for launched consumer chains +func SetICSConsumerMetadata(ctx sdk.Context, providerKeeper providerkeeper.Keeper) error { + for _, consumerID := range providerKeeper.GetAllActiveConsumerIds(ctx) { + phase := providerKeeper.GetConsumerPhase(ctx, consumerID) + if phase != providertypes.CONSUMER_PHASE_LAUNCHED { + continue + } + chainID, err := providerKeeper.GetConsumerChainId(ctx, consumerID) + if err != nil { + ctx.Logger().Error( + fmt.Sprintf("cannot get chain ID for consumer chain, consumerID(%s)", consumerID), + ) + continue + } + + // example of setting the metadata for Stride + if chainID == "stride-1" { + metadata := providertypes.ConsumerMetadata{ + Name: "Stride", + Description: "Description", + Metadata: "Metadata", + } + err = providerKeeper.SetConsumerMetadata(ctx, consumerID, metadata) + if err != nil { + ctx.Logger().Error( + fmt.Sprintf("cannot set consumer metadata, consumerID(%s), chainID(%s): %s", consumerID, chainID, err.Error()), + ) + continue + } + } + } +} +``` + +```go +// MigrateICSProposals migrates deprecated proposals +func MigrateICSProposals(ctx sdk.Context, msgServer providertypes.MsgServer, providerKeeper providerkeeper.Keeper, govKeeper govkeeper.Keeper) error { + proposals := []govtypesv1.Proposal{} + err := govKeeper.Proposals.Walk(ctx, nil, func(key uint64, proposal govtypesv1.Proposal) (stop bool, err error) { + proposals = append(proposals, proposal) + return false, nil // go through the entire collection + }) + if err != nil { + return errorsmod.Wrapf(err, "iterating through proposals") + } + for _, proposal := range proposals { + err := MigrateICSLegacyProposal(ctx, msgServer, providerKeeper, govKeeper, proposal) + if err != nil { + return errorsmod.Wrapf(err, "migrating legacy proposal %d", proposal.Id) + } + + err = MigrateICSProposal(ctx, msgServer, providerKeeper, govKeeper, proposal) + if err != nil { + return errorsmod.Wrapf(err, "migrating proposal %d", proposal.Id) + } + } + return nil +} + +// MigrateICSLegacyProposal migrates the following proposals +// - ConsumerAdditionProposal +// - ConsumerRemovalProposal +// - ConsumerModificationProposal +// - ChangeRewardDenomsProposal + +// MigrateICSProposal migrates the following proposals +// - MsgConsumerAddition +// - MsgConsumerRemoval +// - MsgConsumerModification +``` + +For an example, see the [Gaia v20 upgrade handler](https://github.com/cosmos/gaia/blob/e4656093955578b2efa6e8c2ea8dd8823008bba3/app/upgrades/v20/upgrades.go#L43). + +### Consumer + +Upgrading the consumer from `v5.0.0` or `v5.2.0` will not require state migration. + +## [v5.1.x](https://github.com/cosmos/interchain-security/releases/tag/v5.1.0) + +### Provider + +***Note that providers using v5.0.0 cannot upgrade to v5.1.0 cleanly*** + +Providers using versions `v4.0.x`, `v4.1.x`, `v4.2.x`, `v4.3.x` and `v4.4.x` can upgrade to `v5.1.0`. + +Upgrading from `v4.x` to `v5.1.0` will upgrade the provider `consensus version` to 7. + +Upgrade code will be executed automatically during the upgrade procedure. + +### Consumer + +Upgrading the consumer from `v5.0.0` to `v5.1.0` will not require state migration. + +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) This guide provides instructions for upgrading to specific versions of Replicated Security. ## [v4.4.x](https://github.com/cosmos/interchain-security/releases/tag/v4.4.0) diff --git a/proto/interchain_security/ccv/v1/shared_consumer.proto b/proto/interchain_security/ccv/v1/shared_consumer.proto index 6c8cccfe86..36e189e1c0 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 366f7f61c0..a61c8004bc 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -101,7 +101,7 @@ func CreateSubmitChangeRewardDenomsProposalActionGen() *rapid.Generator[SubmitCh return SubmitChangeRewardDenomsProposalAction{ 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 9628338019..f571fbcc59 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -267,7 +267,153 @@ type SubmitConsumerAdditionProposalAction struct { Denylist []string } +<<<<<<< HEAD func (tr TestConfig) submitConsumerAdditionProposal( +======= +func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer, verbose bool) { + content, err := json.Marshal(update) + if err != nil { + log.Fatal("failed marshalling MsgUpdateConsumer: ", err.Error()) + } + jsonFile := "/update-consumer.json" + bz, err := tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // Send consumer chain update + cmd := tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "tx", "provider", "update-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), + `--home`, tr.getValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + bz, err = cmd.CombinedOutput() + if err != nil { + fmt.Println("command failed: ", cmd) + log.Fatalf("update consumer failed error: %s, output: %s", err, string(bz)) + } + + // Check transaction + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx response on update-consumer: %s, json: %s", err.Error(), string(bz)) + } + + if txResponse.Code != 0 { + log.Fatalf("sending update-consumer transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) + } + + if verbose { + fmt.Println("running 'update-consumer' returned: ", txResponse) + } + + tr.waitBlocks(providerChain, 2, 10*time.Second) +} + +// CreateConsumer creates a consumer chain and returns its consumer-id +func (tr Chain) CreateConsumer(providerChain, consumerChain ChainID, validator ValidatorID, metadata types.ConsumerMetadata, initParams *types.ConsumerInitializationParameters, powerShapingParams *types.PowerShapingParameters) ConsumerID { + + msg := types.MsgCreateConsumer{ + ChainId: string(consumerChain), + Metadata: metadata, + InitializationParameters: initParams, + PowerShapingParameters: powerShapingParams, + } + + content, err := json.Marshal(msg) + if err != nil { + log.Fatalf("failed marshalling MsgCreateConsumer: %s", err.Error()) + } + jsonFile := "/create-consumer.json" + bz, err := tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // Send consumer chain creation + cmd := tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "tx", "provider", "create-consumer", jsonFile, + `--from`, `validator`+fmt.Sprint(validator), + `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), + `--home`, tr.getValidatorHome(providerChain, validator), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(providerChain, validator), + `--keyring-backend`, `test`, + "--output", "json", + `-y`, + ) + + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("create consumer failed ", "error: ", err, "output: ", string(bz)) + } + + txResponse := &TxResponse{} + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx response on create-consumer: %s, json: %s", err.Error(), string(bz)) + } + + if txResponse.Code != 0 { + log.Fatalf("sending transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) + } + + // TODO: introduce waitForTx (see issue #2198) + tr.waitBlocks(providerChain, 2, 10*time.Second) + + // Get Consumer ID from transaction + cmd = tr.target.ExecCommand( + tr.testConfig.chainConfigs[providerChain].BinaryName, + "query", "tx", txResponse.TxHash, + `--node`, tr.getValidatorNode(providerChain, validator), + "--output", "json", + ) + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatalf("not able to query tx containing creation-consumer: tx: %s, err: %s, out: %s", + txResponse.TxHash, err.Error(), string(bz)) + } + + err = json.Unmarshal(bz, txResponse) + if err != nil { + log.Fatalf("unmarshalling tx containing create-consumer: %s, json: %s", err.Error(), string(bz)) + } + + consumerId := "" + for _, event := range txResponse.Events { + if event.Type != "create_consumer" { + continue + } + attr, exists := event.GetAttribute("consumer_id") + if !exists { + log.Fatalf("no event with consumer_id found in tx content of create_consumer: %v", event) + } + consumerId = attr.Value + } + if consumerId == "" { + log.Fatalf("no consumer-id found in consumer creation transaction events for chain '%s'. events: %v", consumerChain, txResponse.Events) + } + + return ConsumerID(consumerId) +} + +// submitConsumerAdditionProposal initializes a consumer chain and submits a governance proposal +func (tr Chain) submitConsumerAdditionProposal( +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) action SubmitConsumerAdditionProposalAction, target ExecutionTarget, verbose bool, @@ -1987,20 +2133,85 @@ func (tr TestConfig) registerRepresentative( } type SubmitChangeRewardDenomsProposalAction struct { +<<<<<<< HEAD Denom string +======= + Chain ChainID + Denoms []string +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) Deposit uint From ValidatorID } +<<<<<<< HEAD func (tr TestConfig) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, target ExecutionTarget, verbose bool) { providerChain := tr.chainConfigs[ChainID("provi")] +======= +func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { + changeRewMsg := types.MsgChangeRewardDenoms{ + DenomsToAdd: action.Denoms, + DenomsToRemove: []string{"stake"}, + Authority: "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", + } + + // Generate proposal content + title := "change reward denoms" + description := "change reward denoms" + summary := "Proposal to change reward denoms" + expedited := false + metadata := "ipfs://CID" + deposit := fmt.Sprintf("%dstake", action.Deposit) + jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &changeRewMsg) + + //#nosec G204 -- bypass unsafe quoting warning (no production code) + proposalFile := "/change-rewards-proposal.json" + bz, err := tr.target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + // CHANGE REWARDS DENOM PROPOSAL + cmd := tr.target.ExecCommand( + tr.testConfig.chainConfigs[action.Chain].BinaryName, + "tx", "gov", "submit-proposal", proposalFile, + `--from`, `validator`+fmt.Sprint(action.From), + `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), + `--home`, tr.getValidatorHome(action.Chain, action.From), + `--gas`, `900000`, + `--node`, tr.getValidatorNode(action.Chain, action.From), + `--keyring-backend`, `test`, + `-y`, + ) + + if verbose { + fmt.Println("change rewards denom props cmd:", cmd.String()) + fmt.Println("change rewards denom props json:", jsonStr) + } + bz, err = cmd.CombinedOutput() + if err != nil { + log.Fatal("submit-proposal failed:", err, "\n", string(bz)) + } + + if verbose { + fmt.Println("change rewards denom props output:", string(bz)) + } + + // wait for inclusion in a block -> '--broadcast-mode block' is deprecated + tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) +} + +func (tr Chain) submitChangeRewardDenomsLegacyProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { + providerChain := tr.testConfig.chainConfigs[action.Chain] +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) prop := client.ChangeRewardDenomsProposalJSON{ Summary: "Change reward denoms", 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`, @@ -2492,3 +2703,123 @@ func (tr TestConfig) AdvanceTimeForChain(chain ChainID, duration time.Duration) // wait for 1 block of the chain to get a block with the advanced timestamp tr.waitBlocks(chain, 1, time.Minute) } +<<<<<<< HEAD +======= + +func (tr Commands) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) { + assignKey := fmt.Sprintf( + `%s tx provider assign-consensus-key %s '%s' --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, + tr.chainConfigs[ChainID("provi")].BinaryName, + string(tr.chainConfigs[action.Chain].ConsumerId), + action.ConsumerPubkey, + action.Validator, + tr.chainConfigs[ChainID("provi")].ChainId, + home, + node, + gas, + ) + + cmd := tr.target.ExecCommand( + "/bin/bash", "-c", + assignKey, + ) + + if verbose { + fmt.Println("assignConsumerPubKey cmd:", cmd.String()) + } + + 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) +} +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 1b3e0795b4..221f7c6037 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -7,6 +7,7 @@ import ( "os/exec" "regexp" "strconv" + "strings" "time" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -328,13 +329,113 @@ func (tr TestConfig) getRewards(chain ChainID, modelState Rewards) Rewards { currentBlock = 1 } for k := range modelState.IsRewarded { +<<<<<<< HEAD receivedRewards[k] = tr.getReward(chain, k, nextBlock, modelState.IsNativeDenom) > tr.getReward(chain, k, currentBlock, modelState.IsNativeDenom) +======= + receivedRewards[k] = tr.target.GetReward(chain, k, nextBlock, modelState.Denom) > tr.target.GetReward(chain, k, currentBlock, modelState.Denom) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } - return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, IsNativeDenom: modelState.IsNativeDenom} + return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, Denom: modelState.Denom} } +<<<<<<< HEAD func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { +======= +func (tr Chain) GetConsumerAddresses(chain ChainID, modelState map[ValidatorID]string) map[ValidatorID]string { + actualState := map[ValidatorID]string{} + for k := range modelState { + actualState[k] = tr.target.GetConsumerAddress(chain, k) + } + + return actualState +} + +func (tr Chain) GetProviderAddresses(chain ChainID, modelState map[ValidatorID]string) map[ValidatorID]string { + actualState := map[ValidatorID]string{} + for k := range modelState { + actualState[k] = tr.target.GetProviderAddressFromConsumer(chain, k) + } + + return actualState +} + +func (tr Chain) getValidatorNode(chain ChainID, validator ValidatorID) string { + // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) + if tr.testConfig.useCometmock { + return tr.target.GetQueryNode(chain) + } + + return "tcp://" + tr.getValidatorIP(chain, validator) + ":26658" +} + +func (tr Chain) getValidatorIP(chain ChainID, validator ValidatorID) string { + return tr.testConfig.chainConfigs[chain].IpPrefix + "." + tr.testConfig.validatorConfigs[validator].IpSuffix +} + +func (tr Chain) getValidatorHome(chain ChainID, validator ValidatorID) string { + return `/` + string(chain) + `/validator` + fmt.Sprint(validator) +} + +func (tr Chain) curlJsonRPCRequest(method, params, address string) { + cmd_template := `curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"%s","params":%s,"id":1}' %s` + + cmd := tr.target.ExecCommand("bash", "-c", fmt.Sprintf(cmd_template, method, params, address)) + + verbosity := false + e2e.ExecuteCommand(cmd, "curlJsonRPCRequest", verbosity) +} + +func uintPtr(i uint) *uint { + return &i +} + +func intPtr(i int) *int { + return &i +} + +type Commands struct { + containerConfig *ContainerConfig + validatorConfigs map[ValidatorID]ValidatorConfig + chainConfigs map[ChainID]ChainConfig + target e2e.PlatformDriver +} + +func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { + return tr.target.ExecCommand(name, arg...) +} + +func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { + return tr.target.ExecDetachedCommand(name, args...) +} + +func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { + return tr.target.GetTestScriptPath(isConsumer, script) +} + +func (tr Commands) GetBlockHeight(chain ChainID) uint { + binaryName := tr.chainConfigs[chain].BinaryName + bz, err := tr.target.ExecCommand(binaryName, + + "query", "tendermint-validator-set", + + `--node`, tr.GetQueryNode(chain), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) + blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) + if err != nil { + log.Fatal(err) + } + + return uint(blockHeight) +} + +func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) valCfg := tr.validatorConfigs[validator] delAddresss := valCfg.DelAddress if chain != ChainID("provi") { @@ -361,12 +462,23 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight log.Fatal(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 } func (tr TestConfig) getBalance(chain ChainID, validator ValidatorID) uint { diff --git a/tests/e2e/state_rapid_test.go b/tests/e2e/state_rapid_test.go index 3192662d27..3416c0d347 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 30288e7ba9..7c5e51d567 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -1,6 +1,15 @@ package main +<<<<<<< HEAD const consumerRewardDenom = "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" +======= +import gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + +var consumerRewardDenoms = []string{ + "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9", // transfer channel-1 + "ibc/D549749C93524DA1831A4B3C850DFC1BA9060261BEDFB224B3B0B4744CD77A70", // transfer channel-2 +} +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool) []Step { return []Step{ @@ -23,7 +32,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): false, }, IsIncrementalReward: true, - IsNativeDenom: true, + Denom: "stake", }, }, }, @@ -56,7 +65,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): true, }, IsIncrementalReward: true, - IsNativeDenom: true, + Denom: "stake", }, }, }, @@ -138,7 +147,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{}, @@ -147,7 +156,12 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool }, { Action: SubmitChangeRewardDenomsProposalAction{ +<<<<<<< HEAD Denom: consumerRewardDenom, +======= + Chain: ChainID("provi"), + Denoms: consumerRewardDenoms, +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) Deposit: 10000001, From: ValidatorID("bob"), }, @@ -168,10 +182,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), @@ -181,8 +196,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, @@ -190,7 +312,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 new file mode 100644 index 0000000000..dab57b4eba --- /dev/null +++ b/tests/e2e/steps_inactive_vals.go @@ -0,0 +1,973 @@ +package main + +import ( + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +// stepsInactiveValidatorsOnConsumer tests situations where validators that are *not* in the active set on the +// provider chain validate on the consumer chain. +// The provider chain is set to have at most *2* validators active in consensus, and there are 3 validators in total. +// high-level, this test does: +// - start the provider chain +// - start a consumer chain +// - check that non-consensus validators do not get slashed for downtime on the provider; and that they don't get rewards +// - check that active validators *do* get slashed for downtime on the provider, and don't get rewards while they are down +// - check that non-consensus validators *do* get jailed for consumer downtime on the provider +// - check that non-consensus validators *become* consensus validators when they have enough power +func stepsInactiveProviderValidators() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &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{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + }, + setupOptInChain(), + []Step{ + // check that active-but-not-consensus validators do not get slashed for downtime + { + // alices provider node goes offline + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // still 0 consensus power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, // but alice does not get jailed or slashed + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // give carol more power so that she has enough power to validate if bob goes down + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 700000000, // carol needs to have more than 2/3rds of power(alice) + power(carol) + power(bob) to run both chains alone, so we stake some more to her + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 200, + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 1000000000, + }, + // check that bob and carol get rewards, but alice does not + Rewards: &Rewards{ + 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, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + // bob goes offline + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice gets into the active set + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 198000000, // 1% slash + ValidatorID("carol"): 1000000000, + }, + }, + }, + }, + { + // relay packets so that the consumer gets up to date with the provider + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + Rewards: &Rewards{ + 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 + ValidatorID("carol"): true, + }, + }, + }, + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 198, // bob was slashed 1% + ValidatorID("carol"): 1000, + }, + // check that between two blocks now, alice does not get rewarded with the native denom + Rewards: &Rewards{ + 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, + ValidatorID("carol"): true, + }, + }, + }, + // bob is still at 0 power on the consumer chain + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay packets so that the consumer gets up to date with the provider + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // alice goes offline on the consumer chain + { + Action: DowntimeSlashAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // power not affected yet + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the provider chain knows about alice's downtime + { + Action: RelayPacketsAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + Port: "consumer", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too. + // we cannot test directly whether alice is jailed, but we will test this below + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // we need to double-check that alice is actually jailed, so we get bob jailed, too, which usually would mean alice gets into power + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail alice + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // alice was not slashed because consumer downtime just jails without slashing tokens + ValidatorID("alice"): 100, // alice is back as an active consensus validator. + ValidatorID("bob"): 0, // bob is still jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // both alice and bob are validating the consumer + ValidatorID("bob"): 196, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + }, + ) + + return s +} + +// Precondition: The provider chain is running. +// Postcondition: A consumer chain with Top N = 0 is running, including an up-and-running IBC connection to the provider. +// "alice", "bob", "carol" have opted in and are validating. +func setupOptInChain() []Step { + return concatSteps([]Step{ + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + AllowInactiveVals: true, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), + }, + }, + }, + }, + }, + { + // we start all the validators but only "alice" and "bob" have opted in and hence + // only "alice" and "bob" are validating blocks + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ClientA: 0, + ClientB: 0, + }, + State: State{}, + }, + { + Action: AddIbcChannelAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ConnectionA: 0, + PortA: "consumer", + PortB: "provider", + Order: "ordered", + }, + State: State{}, + }, + }, + ) +} + +func stepsOptInValidators(validators ...ValidatorID) []Step { + s := make([]Step, 0) + for _, val := range validators { + // Οpt in all validators + s = append(s, Step{ + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: val, + }, + State: State{ + ChainID("provi"): ChainState{}, + }, + }, + ) + } + return s +} + +// stepsInactiveProviderValidatorsGovernance validates that inactive validators +// are not included in the calculation of the quorum for governance proposals. +// It checks that when the quorum is met *among active validators*, +// the proposal can pass, even though the quorum would not be met if inactive validators +// would be counted. +func stepsInactiveProviderValidatorsGovernance() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 1, so alice and bob should not be in power + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should have passed because carol voted for it. + // carol alone is enough to pass the quorum, because stake of the other validators is not counted + Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsInactiveProviderValidatorsGovernanceBasecase is a sanity check to go along with +// stepsInactiveProviderValidatorsGovernance. It tests that with all validators being active, +// the proposal does not pass if it does not meet the quorum among validators. +func stepsInactiveProviderValidatorsGovernanceBasecase() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should *not* have passed because only carol voted for it, + // and carol is not enough to pass the quorum + Status: gov.ProposalStatus_PROPOSAL_STATUS_REJECTED.String(), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsMinStake validates that a validator with less stake than the specified minStake parameter +// cannot validate the consumer chain. +func stepsMinStake() []Step { + return concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + MinStake: 300000000, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), + }, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), + }, + }, + }, + }, + }, + { + // we start all the validators, but due to the min stake, only carol can validate + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, // due to min stake of 300000000, only carol can validate + }, + }, + }, + }, + }, + ) +} + +// This test case validates that inactive validators are not included when computing +// the top N. +func stepsInactiveValsWithTopN() []Step { + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &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{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, // bob doesn't have to validate because he is not in the top N + ValidatorID("carol"): {"consu"}, + }, + }, + }, + }, + { + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice and bob are not in the top N, so aren't in the validator set + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + }, + }, + }, + } +} + +// stepsInactiveValsMint tests the minting of tokens with inactive validators +// It checks that inactive validators are not counted when computing whether the +// inflation rate should go up or down. +func stepsInactiveValsMint() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 29, // other validators are not in power since only 1 can be active + }, + InflationRateChange: intPtr(1), // inflation rate goes up because less than the goal is bonded, since only carol is active + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal + }, + }, + }, + } +} + +// stepsMintBasecase tests the minting of tokens without inactive validators. +// This is done as a sanity check to complement stepsInactiveValsMint. +func stepsMintBasecase() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 29, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down because more than the goal is bonded + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate *still* goes down + }, + }, + }, + } +} diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index 245d70020b..094e0171fb 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -143,7 +143,25 @@ func (td *DefaultDriver) runAction(action interface{}) error { case OptInAction: td.testCfg.optIn(action, td.target, td.verbose) case OptOutAction: +<<<<<<< HEAD td.testCfg.optOut(action, td.target, td.verbose) +======= + target := td.getTargetDriver("provider") + target.optOut(action, td.verbose) + case SetConsumerCommissionRateAction: + target := td.getTargetDriver("provider") + target.setConsumerCommissionRate(action, td.verbose) + 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) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) 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 new file mode 100644 index 0000000000..0fd7ada259 --- /dev/null +++ b/tests/e2e/testlib/types.go @@ -0,0 +1,366 @@ +package e2e + +import ( + "encoding/json" + "fmt" + "os/exec" + "reflect" + "time" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +type ( + ChainID string + ConsumerID string + ValidatorID string +) + +type AssignConsumerPubKeyAction struct { + Chain ChainID + Validator ValidatorID + ConsumerPubkey string + // ReconfigureNode will change keys the node uses and restart + ReconfigureNode bool + // executing the action should raise an error + ExpectError bool + ExpectedError string +} + +type ChainCommands interface { + GetBlockHeight(chain ChainID) uint + GetBalance(chain ChainID, validator ValidatorID) uint + GetConsumerChains(chain ChainID) map[ChainID]bool + GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string + GetClientFrozenHeight(chain ChainID, clientID string) (RevisionNumber, RevisionHeight uint64) + GetHasToValidate(validator ValidatorID) []ChainID + GetIBCTransferParams(chain ChainID) IBCTransferParams + 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, denom string) float64 + GetRegisteredConsumerRewardDenoms(chain ChainID) []string + GetSlashMeter() int64 + GetPendingPacketQueueSize(chain ChainID) uint + GetProposedConsumerChains(chain ChainID) []string + GetQueryNode(chain ChainID) string + GetQueryNodeRPCAddress(chain ChainID) string + GetTrustedHeight(chain ChainID, clientID string, index int) (uint64, uint64) + GetValPower(chain ChainID, validator ValidatorID) uint + GetValStakedTokens(chain ChainID, validatorAddress string) uint + GetQueryNodeIP(chain ChainID) string + GetInflationRate(chain ChainID) float64 + GetConsumerCommissionRate(chain ChainID, validator ValidatorID) float64 + // Action commands + AssignConsumerPubKey(action AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) +} + +// TODO: replace ExecutionTarget with new TargetDriver interface +type PlatformDriver interface { + ExecCommand(name string, arg ...string) *exec.Cmd + // ExecDetachedCommand: when executed the command will be run in the background and call will return immediately + ExecDetachedCommand(name string, args ...string) *exec.Cmd + GetTestScriptPath(isConsumer bool, script string) string +} +type TargetDriver interface { + // ChainCommands + ChainCommands + PlatformDriver +} + +// TODO: this should not be here. mv 'Now' to a better suited type here and then move ContainerConfig back +type ContainerConfig struct { + ContainerName string + InstanceName string + CcvVersion string + Now time.Time +} + +// Attributes that are unique to a validator. Allows us to map (part of) +// the set of strings defined above to a set of viable validators +type ValidatorConfig struct { + // Seed phrase to generate a secp256k1 key used by the validator on the provider + Mnemonic string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + DelAddress string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + DelAddressOnConsumer string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ValoperAddress string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ValoperAddressOnConsumer string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. It matches the PrivValidatorKey below. + ValconsAddress string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. + ValconsAddressOnConsumer string + // Key used for consensus on provider + PrivValidatorKey string + NodeKey string + // Must be an integer greater than 0 and less than 253 + IpSuffix string + + // consumer chain key assignment data + // keys are used on a new node + + // Seed phrase to generate a secp256k1 key used by the validator on the consumer + ConsumerMnemonic string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerDelAddress string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerDelAddressOnProvider string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerValoperAddress string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerValoperAddressOnProvider string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. It matches the PrivValidatorKey below. + ConsumerValconsAddress string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. + ConsumerValconsAddressOnProvider string + ConsumerValPubKey string + // Key used for consensus on consumer + ConsumerPrivValidatorKey string + ConsumerNodeKey string + UseConsumerKey bool // if true the validator node will start with consumer key +} + +// Attributes that are unique to a chain. Allows us to map (part of) +// the set of strings defined above to a set of viable chains +type ChainConfig struct { + ChainId ChainID + ConsumerId ConsumerID + // The account prefix configured on the chain. For example, on the Hub, this is "cosmos" + AccountPrefix string + // Must be unique per chain + IpPrefix string + VotingWaitTime uint + // Any transformations to apply to the genesis file of all chains instantiated with this chain config, as a jq string. + // Example: ".app_state.gov.params.voting_period = \"5s\" | .app_state.slashing.params.signed_blocks_window = \"2\" | .app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\"" + GenesisChanges string + BinaryName string + + // binary to use after upgrade height + UpgradeBinary string +} + +type ( + // to have a ChainState object that does not have the overridden Marshal/Unmarshal method + ChainStateCopy ChainState + + // duplicated from the ChainState with a minor change to the Proposals field + ChainStateWithProposalTypes struct { + ChainStateCopy + Proposals *map[uint]ProposalAndType // the only thing changed from the real ChainState + } +) + +// stores a proposal as a raw json, together with its type +type ProposalAndType struct { + RawProposal json.RawMessage + Type string +} +type ChainState struct { + ValBalances *map[ValidatorID]uint + Proposals *map[uint]Proposal + ProposedConsumerChains *[]string + ValPowers *map[ValidatorID]uint + StakedTokens *map[ValidatorID]uint + IBCTransferParams *IBCTransferParams + Params *[]Param + Rewards *Rewards + ConsumerChains *map[ChainID]bool + AssignedKeys *map[ValidatorID]string + ProviderKeys *map[ValidatorID]string // validatorID: validator provider key + ConsumerPendingPacketQueueSize *uint // Only relevant to consumer chains + RegisteredConsumerRewardDenoms *[]string + ClientsFrozenHeights *map[string]clienttypes.Height + HasToValidate *map[ValidatorID][]ChainID // only relevant to provider chain + InflationRateChange *int // whether the inflation rate between two blocks changes negatively (any negative number), is equal (0), or changes positively (any positive number) + ConsumerCommissionRates *map[ValidatorID]float64 +} + +// custom marshal and unmarshal functions for the chainstate that convert proposals to/from the auxiliary type with type info + +// MarshalJSON transforms the ChainState into a ChainStateWithProposalTypes by adding type info to the proposals +func (c ChainState) MarshalJSON() ([]byte, error) { + chainStateCopy := ChainStateCopy(c) + chainStateWithProposalTypes := ChainStateWithProposalTypes{chainStateCopy, nil} + if c.Proposals != nil { + proposalsWithTypes := make(map[uint]ProposalAndType) + for k, v := range *c.Proposals { + rawMessage, err := json.Marshal(v) + if err != nil { + return nil, err + } + proposalsWithTypes[k] = ProposalAndType{rawMessage, reflect.TypeOf(v).String()} + } + chainStateWithProposalTypes.Proposals = &proposalsWithTypes + } + return json.Marshal(chainStateWithProposalTypes) +} + +// UnmarshalJSON unmarshals the ChainStateWithProposalTypes into a ChainState by removing the type info from the proposals and getting back standard proposals +func (c *ChainState) UnmarshalJSON(data []byte) error { + chainStateWithProposalTypes := ChainStateWithProposalTypes{} + err := json.Unmarshal(data, &chainStateWithProposalTypes) + if err != nil { + return err + } + + chainState := ChainState(chainStateWithProposalTypes.ChainStateCopy) + *c = chainState + + if chainStateWithProposalTypes.Proposals != nil { + proposals := make(map[uint]Proposal) + for k, v := range *chainStateWithProposalTypes.Proposals { + proposal, err := UnmarshalProposalWithType(v.RawProposal, v.Type) + if err != nil { + return err + } + proposals[k] = proposal + } + c.Proposals = &proposals + } + return nil +} + +// UnmarshalProposalWithType takes a JSON object and a proposal type and marshals into an object of the corresponding proposal. +func UnmarshalProposalWithType(inputMap json.RawMessage, proposalType string) (Proposal, error) { + var err error + switch proposalType { + case "main.TextProposal": + prop := TextProposal{} + err := json.Unmarshal(inputMap, &prop) + if err == nil { + return prop, nil + } + case "main.ConsumerAdditionProposal": + prop := ConsumerAdditionProposal{} + err := json.Unmarshal(inputMap, &prop) + if err == nil { + return prop, nil + } + case "main.UpgradeProposal": + prop := UpgradeProposal{} + err := json.Unmarshal(inputMap, &prop) + if err == nil { + return prop, nil + } + case "main.ConsumerRemovalProposal": + prop := ConsumerRemovalProposal{} + err := json.Unmarshal(inputMap, &prop) + if err == nil { + return prop, nil + } + case "main.IBCTransferParamsProposal": + prop := IBCTransferParamsProposal{} + err := json.Unmarshal(inputMap, &prop) + if err == nil { + return prop, nil + } + default: + return nil, fmt.Errorf("%s is not a known proposal type", proposalType) + } + + return nil, err +} + +type Proposal interface { + isProposal() +} +type TextProposal struct { + Title string + Description string + Deposit uint + Status string +} + +func (p TextProposal) isProposal() {} + +type IBCTransferParamsProposal struct { + Title string + Deposit uint + Status string + Params IBCTransferParams +} + +func (ibct IBCTransferParamsProposal) isProposal() {} + +type ConsumerAdditionProposal struct { + Deposit uint + Chain ChainID + SpawnTime int + InitialHeight clienttypes.Height + Status string +} + +type UpgradeProposal struct { + Title string + Description string + UpgradeHeight uint64 + Type string + Deposit uint + Status string +} + +func (p UpgradeProposal) isProposal() {} + +func (p ConsumerAdditionProposal) isProposal() {} + +type ConsumerRemovalProposal struct { + Deposit uint + Chain ChainID + Status string +} + +func (p ConsumerRemovalProposal) isProposal() {} + +type ConsumerModificationProposal struct { + Deposit uint + Chain ChainID + Status string +} + +func (p ConsumerModificationProposal) isProposal() {} + +type Rewards struct { + IsRewarded map[ValidatorID]bool + // 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 + // 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 { + Deposit uint + Status string + Subspace string + Key string + Value string +} + +func (p ParamsProposal) isProposal() {} + +type Param struct { + Subspace string + Key string + Value string +} + +type IBCTransferParams struct { + SendEnabled bool `json:"send_enabled"` + ReceiveEnabled bool `json:"receive_enabled"` +} diff --git a/tests/e2e/v4/state.go b/tests/e2e/v4/state.go new file mode 100644 index 0000000000..f0ba1bca4b --- /dev/null +++ b/tests/e2e/v4/state.go @@ -0,0 +1,648 @@ +package v4 + +import ( + "bufio" + "fmt" + "log" + "os/exec" + "regexp" + "strconv" + "strings" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/kylelemons/godebug/pretty" + "github.com/tidwall/gjson" + "gopkg.in/yaml.v2" + + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + + e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" +) + +type ( + ChainID = e2e.ChainID + ValidatorID = e2e.ValidatorID + ChainState = e2e.ChainState + Proposal = e2e.Proposal + Rewards = e2e.Rewards + TextProposal = e2e.TextProposal + UpgradeProposal = e2e.UpgradeProposal + ConsumerAdditionProposal = e2e.ConsumerAdditionProposal + ConsumerRemovalProposal = e2e.ConsumerRemovalProposal + IBCTransferParams = e2e.IBCTransferParams + IBCTransferParamsProposal = e2e.IBCTransferParamsProposal + Param = e2e.Param + ParamsProposal = e2e.ParamsProposal + ValidatorConfig = e2e.ValidatorConfig + ChainConfig = e2e.ChainConfig + ContainerConfig = e2e.ContainerConfig + TargetDriver = e2e.TargetDriver +) + +type State map[ChainID]ChainState + +type Commands struct { + ContainerConfig ContainerConfig // FIXME only needed for 'Now' time tracking + ValidatorConfigs map[ValidatorID]ValidatorConfig + ChainConfigs map[ChainID]ChainConfig + Target e2e.PlatformDriver +} + +func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { + return tr.Target.ExecCommand(name, arg...) +} + +func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { + return tr.Target.ExecDetachedCommand(name, args...) +} + +func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { + return tr.Target.GetTestScriptPath(isConsumer, script) +} + +func (tr Commands) GetBlockHeight(chain ChainID) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + + "query", "tendermint-validator-set", + + `--node`, tr.GetQueryNode(chain), + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) + blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) + if err != nil { + log.Fatal(err) + } + + return uint(blockHeight) +} + +type ValPubKey struct { + Value string `yaml:"value"` +} + +type TmValidatorSetYaml struct { + Total string `yaml:"total"` + Validators []struct { + Address string `yaml:"address"` + VotingPower string `yaml:"voting_power"` + PubKey ValPubKey `yaml:"pub_key"` + } +} + +func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { + /* if *verbose { + log.Println("getting validator power for chain: ", chain, " validator: ", validator) + } + */ + binaryName := tr.ChainConfigs[chain].BinaryName + command := tr.Target.ExecCommand(binaryName, + + "query", "tendermint-validator-set", + + `--node`, tr.GetQueryNode(chain), + ) + bz, err := command.CombinedOutput() + if err != nil { + log.Fatalf("encountered an error when executing command '%s': %v, output: %s", command.String(), err, string(bz)) + } + + valset := TmValidatorSetYaml{} + err = yaml.Unmarshal(bz, &valset) + if err != nil { + log.Fatalf("yaml.Unmarshal returned an error while unmarshalling validator set: %v, input: %s", err, string(bz)) + } + + total, err := strconv.Atoi(valset.Total) + if err != nil { + log.Fatalf("v4: strconv.Atoi returned an error while converting total for validator set: %v, input: %s, validator set: %s, src: %s", + err, valset.Total, pretty.Sprint(valset), string(bz)) + } + + if total != len(valset.Validators) { + log.Fatalf("Total number of validators %v does not match number of validators in list %v. Probably a query pagination issue. Validator set: %v", + valset.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) + } + + for _, val := range valset.Validators { + if chain == ChainID("provi") { + // use binary with Bech32Prefix set to ProviderAccountPrefix + if val.Address != tr.ValidatorConfigs[validator].ValconsAddress { + continue + } + } else { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if val.Address != tr.ValidatorConfigs[validator].ValconsAddressOnConsumer && + val.Address != tr.ValidatorConfigs[validator].ConsumerValconsAddress { + continue + } + } + + votingPower, err := strconv.Atoi(val.VotingPower) + if err != nil { + log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) + } + + return uint(votingPower) + } + + // Validator not in set, its validator power is zero. + return 0 +} + +func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { + valCfg := tr.ValidatorConfigs[validator] + delAddresss := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + delAddresss = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delAddresss = valCfg.DelAddressOnConsumer + } + } + + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "distribution", "rewards", + delAddresss, + `--height`, fmt.Sprint(blockHeight), + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("running cmd: ", cmd) + log.Fatal("failed getting rewards: ", err, "\n", string(bz)) + } + + 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 res +} + +func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { + valCfg := tr.ValidatorConfigs[validator] + valDelAddress := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + valDelAddress = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + valDelAddress = valCfg.DelAddressOnConsumer + } + } + + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "bank", "balances", + valDelAddress, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal("getBalance() failed: ", cmd, ": ", err, "\n", string(bz)) + } + + amount := gjson.Get(string(bz), `balances.#(denom=="stake").amount`) + + return uint(amount.Uint()) +} + +// interchain-securityd query gov proposals +func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { + noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) + + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + + "query", "gov", "proposal", + fmt.Sprint(proposal), + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + + prop := TextProposal{} + + if err != nil { + if noProposalRegex.Match(bz) { + return prop + } + + log.Fatal(err, "\n", string(bz)) + } + + propType := gjson.Get(string(bz), `messages.0.content.@type`).String() + deposit := gjson.Get(string(bz), `total_deposit.#(denom=="stake").amount`).Uint() + status := gjson.Get(string(bz), `status`).String() + + // This is a breaking change in the query output for proposals: bug in SDK?? + proposal_value, exists := gov.ProposalStatus_value[status] + if !exists { + panic("invalid proposal status value: " + status) + } + status = strconv.Itoa(int(proposal_value)) + + chainConfigs := tr.ChainConfigs + containerConfig := tr.ContainerConfig + + switch propType { + case "/cosmos.gov.v1beta1.TextProposal": + title := gjson.Get(string(bz), `content.title`).String() + description := gjson.Get(string(bz), `content.description`).String() + + return TextProposal{ + Deposit: uint(deposit), + Status: status, + Title: title, + Description: description, + } + case "/interchain_security.ccv.provider.v1.ConsumerAdditionProposal": + chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() + spawnTime := gjson.Get(string(bz), `messages.0.content.spawn_time`).Time().Sub(containerConfig.Now) + + var chain ChainID + for i, conf := range chainConfigs { + if string(conf.ChainId) == chainId { + chain = i + break + } + } + + return ConsumerAdditionProposal{ + Deposit: uint(deposit), + Status: status, + Chain: chain, + SpawnTime: int(spawnTime.Milliseconds()), + InitialHeight: clienttypes.Height{ + RevisionNumber: gjson.Get(string(bz), `messages.0.content.initial_height.revision_number`).Uint(), + RevisionHeight: gjson.Get(string(bz), `messages.0.content.initial_height.revision_height`).Uint(), + }, + } + case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": + height := gjson.Get(string(bz), `messages.0.content.plan.height`).Uint() + title := gjson.Get(string(bz), `messages.0.content.plan.name`).String() + return UpgradeProposal{ + Deposit: uint(deposit), + Status: status, + UpgradeHeight: height, + Title: title, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + } + case "/interchain_security.ccv.provider.v1.ConsumerRemovalProposal": + chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() + + var chain ChainID + for i, conf := range chainConfigs { + if string(conf.ChainId) == chainId { + chain = i + break + } + } + + return ConsumerRemovalProposal{ + Deposit: uint(deposit), + Status: status, + Chain: chain, + } + case "/cosmos.params.v1beta1.ParameterChangeProposal": + return ParamsProposal{ + Deposit: uint(deposit), + Status: status, + Subspace: gjson.Get(string(bz), `messages.0.content.changes.0.subspace`).String(), + Key: gjson.Get(string(bz), `messages.0.content.changes.0.key`).String(), + Value: gjson.Get(string(bz), `messages.0.content.changes.0.value`).String(), + } + } + + log.Fatal("unknown proposal type", string(bz)) + + return nil +} + +func (tr Commands) GetValStakedTokens(chain ChainID, valoperAddress string) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + + "query", "staking", "validator", + valoperAddress, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + amount := gjson.Get(string(bz), `tokens`) + + return uint(amount.Uint()) +} + +func (tr Commands) GetParam(chain ChainID, param Param) string { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "params", "subspace", + param.Subspace, + param.Key, + + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + value := gjson.Get(string(bz), `value`) + + return value.String() +} + +// GetConsumerChains returns a list of consumer chains that're being secured by the provider chain, +// determined by querying the provider chain. +func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "provider", "list-consumer-chains", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "chains").Array() + chains := make(map[ChainID]bool) + for _, c := range arr { + id := c.Get("chain_id").String() + chains[ChainID(id)] = true + } + + return chains +} + +func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "validator-consumer-key", + string(consumerChain), tr.ValidatorConfigs[validator].ValconsAddress, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + addr := gjson.Get(string(bz), "consumer_address").String() + return addr +} + +func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "validator-provider-key", + string(consumerChain), tr.ValidatorConfigs[validator].ConsumerValconsAddressOnProvider, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Println("error running ", cmd) + log.Fatal(err, "\n", string(bz)) + } + + addr := gjson.Get(string(bz), "provider_address").String() + return addr +} + +func (tr Commands) GetSlashMeter() int64 { + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + + "query", "provider", "throttle-state", + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + slashMeter := gjson.Get(string(bz), "slash_meter") + return slashMeter.Int() +} + +func (tr Commands) GetRegisteredConsumerRewardDenoms(chain ChainID) []string { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "provider", "registered-consumer-reward-denoms", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + denoms := gjson.Get(string(bz), "denoms").Array() + rewardDenoms := make([]string, len(denoms)) + for i, d := range denoms { + rewardDenoms[i] = d.String() + } + + return rewardDenoms +} + +func (tr Commands) GetPendingPacketQueueSize(chain ChainID) uint { + binaryName := tr.ChainConfigs[chain].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "ccvconsumer", "throttle-state", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ) + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + if !gjson.ValidBytes(bz) { + panic("invalid json response from query ccvconsumer throttle-state: " + string(bz)) + } + + packetData := gjson.Get(string(bz), "packet_data_queue").Array() + return uint(len(packetData)) +} + +func (tr Commands) GetValidatorIP(chain ChainID, validator ValidatorID) string { + return tr.ChainConfigs[chain].IpPrefix + "." + tr.ValidatorConfigs[validator].IpSuffix +} + +// getQueryNode returns query node tcp address on chain. +func (tr Commands) GetQueryNode(chain ChainID) string { + return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) +} + +func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { + return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) +} + +// getQueryNodeIP returns query node IP for chain, +// ipSuffix is hardcoded to be 253 on all query nodes +// except for "sover" chain where there's only one node +func (tr Commands) GetQueryNodeIP(chain ChainID) string { + if chain == ChainID("sover") { + // return address of first and only validator + return fmt.Sprintf("%s.%s", + tr.ChainConfigs[chain].IpPrefix, + tr.ValidatorConfigs[ValidatorID("alice")].IpSuffix) + } + return fmt.Sprintf("%s.253", tr.ChainConfigs[chain].IpPrefix) +} + +// GetClientFrozenHeight returns the frozen height for a client with the given client ID +// by querying the hosting chain with the given chainID +func (tr Commands) GetClientFrozenHeight(chain ChainID, clientID string) (uint64, uint64) { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + // cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[ChainID("provi")].BinaryName, + binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName + cmd := tr.Target.ExecCommand(binaryName, + "query", "ibc", "client", "state", clientID, + `--node`, tr.GetQueryNode(ChainID("provi")), + `-o`, `json`, + ) + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + frozenHeight := gjson.Get(string(bz), "client_state.frozen_height") + + revHeight, err := strconv.Atoi(frozenHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + revNumber, err := strconv.Atoi(frozenHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + return uint64(revHeight), uint64(revNumber) +} + +func (tr Commands) GetTrustedHeight( + chain ChainID, + clientID string, + index int, +) (uint64, uint64) { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + // configureNodeCmd := exec.Command("docker", "exec", tc.testConfig.containerConfig.InstanceName, "hermes", + configureNodeCmd := tr.Target.ExecCommand("hermes", + "--json", "query", "client", "consensus", "--chain", string(chain), + `--client`, clientID, + ) + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + var trustedHeight gjson.Result + // iterate on the relayer's response + // and parse the the command "result" + for scanner.Scan() { + out := scanner.Text() + if len(gjson.Get(out, "result").Array()) > 0 { + trustedHeight = gjson.Get(out, "result").Array()[index] + break + } + } + + revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err) + } + + revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err) + } + return uint64(revHeight), uint64(revNumber) +} + +func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + // bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "provider", "list-proposed-consumer-chains", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "proposedChains").Array() + chains := []string{} + for _, c := range arr { + cid := c.Get("chainID").String() + chains = append(chains, cid) + } + + return chains +} + +// Breaking forward compatibility +func (tr Commands) GetIBCTransferParams(chain ChainID) IBCTransferParams { + panic("'GetIBCTransferParams' is not implemented in this version") +} + +func (tr Commands) GetHasToValidate(validator ValidatorID) []ChainID { + panic("'GetHasToValidate' is not implemented in this version") +} + +func (tr Commands) GetConsumerCommissionRate(chain ChainID, validator ValidatorID) float64 { + panic("'GetConsumerCommissionRate' is not implemented in this version") +} + +func (tr Commands) GetInflationRate( + chain ChainID, +) float64 { + panic("'GetInflationRate' is not implemented in this version") +} diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 9bf6459bf9..5cd4bfa121 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -491,6 +491,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 3cc5b69111..92510ecb74 100644 --- a/x/ccv/consumer/keeper/distribution.go +++ b/x/ccv/consumer/keeper/distribution.go @@ -117,6 +117,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 @@ -133,7 +138,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 8d74205b8a..84d85f2098 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -136,3 +136,8 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration { k.paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &period) return period } + +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 536ae98236..801f96478d 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 new file mode 100644 index 0000000000..debfe832f6 --- /dev/null +++ b/x/ccv/consumer/migrations/v3/legacy_params.go @@ -0,0 +1,108 @@ +package v3 + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" +) + +// Legacy: used for migration only! +// GetConsumerParamsLegacy returns the params for the consumer ccv module from legacy subspace +func GetConsumerParamsLegacy(ctx sdk.Context, paramSpace ccvtypes.LegacyParamSubspace) ccvtypes.ConsumerParams { + return ccvtypes.NewParams( + getEnabled(ctx, paramSpace), + getBlocksPerDistributionTransmission(ctx, paramSpace), + getDistributionTransmissionChannel(ctx, paramSpace), + getProviderFeePoolAddrStr(ctx, paramSpace), + getCCVTimeoutPeriod(ctx, paramSpace), + getTransferTimeoutPeriod(ctx, paramSpace), + getConsumerRedistributionFrac(ctx, paramSpace), + getHistoricalEntries(ctx, paramSpace), + getUnbondingPeriod(ctx, paramSpace), + getRewardDenoms(ctx, paramSpace), + getProviderRewardDenoms(ctx, paramSpace), + getRetryDelayPeriod(ctx, paramSpace), + "0", + ) +} + +// getEnabled returns the enabled flag for the consumer module +func getEnabled(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) bool { + var enabled bool + paramStore.Get(ctx, ccvtypes.KeyEnabled, &enabled) + return enabled +} + +func getBlocksPerDistributionTransmission(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) int64 { + var bpdt int64 + paramStore.Get(ctx, ccvtypes.KeyBlocksPerDistributionTransmission, &bpdt) + return bpdt +} + +func getDistributionTransmissionChannel(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { + var s string + paramStore.Get(ctx, ccvtypes.KeyDistributionTransmissionChannel, &s) + return s +} + +func getProviderFeePoolAddrStr(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { + var s string + paramStore.Get(ctx, ccvtypes.KeyProviderFeePoolAddrStr, &s) + return s +} + +// getCCVTimeoutPeriod returns the timeout period for sent ccv related ibc packets +func getCCVTimeoutPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { + var p time.Duration + paramStore.Get(ctx, ccvtypes.KeyCCVTimeoutPeriod, &p) + return p +} + +// getTransferTimeoutPeriod returns the timeout period for sent transfer related ibc packets +func getTransferTimeoutPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { + var p time.Duration + paramStore.Get(ctx, ccvtypes.KeyTransferTimeoutPeriod, &p) + return p +} + +// getConsumerRedistributionFrac returns the fraction of tokens allocated to the consumer redistribution +// address during distribution events. The fraction is a string representing a +// decimal number. For example "0.75" would represent 75%. +func getConsumerRedistributionFrac(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { + var str string + paramStore.Get(ctx, ccvtypes.KeyConsumerRedistributionFrac, &str) + return str +} + +// getHistoricalEntries returns the number of historical info entries to persist in store +func getHistoricalEntries(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) int64 { + var n int64 + paramStore.Get(ctx, ccvtypes.KeyHistoricalEntries, &n) + return n +} + +func getUnbondingPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { + var period time.Duration + paramStore.Get(ctx, ccvtypes.KeyConsumerUnbondingPeriod, &period) + return period +} + +func getRewardDenoms(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) []string { + var denoms []string + paramStore.Get(ctx, ccvtypes.KeyRewardDenoms, &denoms) + return denoms +} + +func getProviderRewardDenoms(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) []string { + var denoms []string + paramStore.Get(ctx, ccvtypes.KeyProviderRewardDenoms, &denoms) + return denoms +} + +func getRetryDelayPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { + var period time.Duration + paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &period) + return period +} diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index c8c61776ec..7e62574b3b 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 8e81e2301c..e76422266d 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 ef0837ae5b..1dedd695be 100644 --- a/x/ccv/provider/ibc_middleware.go +++ b/x/ccv/provider/ibc_middleware.go @@ -12,8 +12,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" +<<<<<<< HEAD "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" +======= + "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" +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) var _ porttypes.Middleware = &IBCMiddleware{} @@ -121,12 +127,15 @@ func (im IBCMiddleware) OnRecvPacket( // that the packet data is valid and can be safely // deserialized without checking errors. if ack.Success() { +<<<<<<< HEAD // execute the middleware logic only if the sender is a consumer chain consumerID, err := im.keeper.IdentifyConsumerChainIDFromIBCPacket(ctx, packet) if err != nil { return ack } +======= +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // extract the coin info received from the packet data var data ibctransfertypes.FungibleTokenPacketData _ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data) @@ -137,8 +146,68 @@ func (im IBCMiddleware) OnRecvPacket( return ack } +<<<<<<< HEAD coinAmt, _ := math.NewIntFromString(data.Amount) coinDenom := GetProviderDenom(data.Denom, packet) +======= + 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( + "cannot get consumer chain id in transfer middleware", + "consumerId", consumerId, + "packet", packet.String(), + "fungibleTokenPacketData", data.String(), + "error", err.Error(), + ) + return ack + } + + logger.Info( + "received ICS rewards from consumer chain", + "consumerId", consumerId, + "chainId", chainId, + "denom", coinDenom, + "amount", data.Amount, + ) + + // initialize an empty slice to store event attributes + eventAttributes := []sdk.Attribute{} + + // add event attributes + eventAttributes = append(eventAttributes, []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeConsumerId, consumerId), + sdk.NewAttribute(types.AttributeConsumerChainId, chainId), + sdk.NewAttribute(types.AttributeRewardDenom, coinDenom), + sdk.NewAttribute(types.AttributeRewardAmount, data.Amount), + }...) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // verify that the coin's denom is a whitelisted consumer denom, // and if so, adds it to the consumer chain rewards allocation, diff --git a/x/ccv/provider/keeper/consumer_lifecycle.go b/x/ccv/provider/keeper/consumer_lifecycle.go new file mode 100644 index 0000000000..c578df4ff2 --- /dev/null +++ b/x/ccv/provider/keeper/consumer_lifecycle.go @@ -0,0 +1,663 @@ +package keeper + +import ( + "fmt" + "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" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + 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/v6/x/ccv/provider/types" + ccv "github.com/cosmos/interchain-security/v6/x/ccv/types" +) + +// PrepareConsumerForLaunch prepares to move the launch of a consumer chain from the previous spawn time to spawn time. +// Previous spawn time can correspond to its zero value if the validator was not previously set for launch. +func (k Keeper) PrepareConsumerForLaunch(ctx sdk.Context, consumerId string, previousSpawnTime, spawnTime time.Time) error { + if !previousSpawnTime.IsZero() { + // if this is not the first initialization and hence `previousSpawnTime` does not contain the zero value of `Time` + // remove the consumer id from the previous spawn time + err := k.RemoveConsumerToBeLaunched(ctx, consumerId, previousSpawnTime) + if err != nil { + return err + } + } + return k.AppendConsumerToBeLaunched(ctx, consumerId, spawnTime) +} + +// InitializeConsumer tries to move a consumer with `consumerId` to the initialized phase. +// If successful, it returns the spawn time and true. +func (k Keeper) InitializeConsumer(ctx sdk.Context, consumerId string) (time.Time, bool) { + // a chain needs to be in the registered or initialized phase + phase := k.GetConsumerPhase(ctx, consumerId) + if phase != types.CONSUMER_PHASE_REGISTERED && phase != types.CONSUMER_PHASE_INITIALIZED { + return time.Time{}, false + } + + initializationParameters, err := k.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return time.Time{}, false + } + + // the spawn time needs to be positive + if initializationParameters.SpawnTime.IsZero() { + return time.Time{}, false + } + + k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_INITIALIZED) + + return initializationParameters.SpawnTime, true +} + +// BeginBlockLaunchConsumers launches initialized consumers chains for which the spawn time has passed +func (k Keeper) BeginBlockLaunchConsumers(ctx sdk.Context) error { + bondedValidators := []stakingtypes.Validator{} + activeValidators := []stakingtypes.Validator{} + + consumerIds, err := k.ConsumeIdsFromTimeQueue( + ctx, + types.SpawnTimeToConsumerIdsKeyPrefix(), + k.GetConsumersToBeLaunched, + k.DeleteAllConsumersToBeLaunched, + k.AppendConsumerToBeLaunched, + 200, + ) + if err != nil { + return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "getting consumers ready to laumch: %s", err.Error()) + } + if len(consumerIds) > 0 { + // get the bonded validators from the staking module + bondedValidators, err = k.GetLastBondedValidators(ctx) + if err != nil { + return fmt.Errorf("getting last bonded validators: %w", err) + } + // get the provider active validators + activeValidators, err = k.GetLastProviderConsensusActiveValidators(ctx) + if err != nil { + return fmt.Errorf("getting last provider active validators: %w", err) + } + } + + for _, consumerId := range consumerIds { + cachedCtx, writeFn := ctx.CacheContext() + err = k.LaunchConsumer(cachedCtx, bondedValidators, activeValidators, consumerId) + if err != nil { + ctx.Logger().Error("could not launch chain", + "consumerId", consumerId, + "error", err) + + // reset spawn time to zero so that owner can try again later + initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, + "getting initialization parameters, consumerId(%s): %s", consumerId, err.Error()) + } + initializationRecord.SpawnTime = time.Time{} + err = k.SetConsumerInitializationParameters(ctx, consumerId, initializationRecord) + if err != nil { + return fmt.Errorf("setting consumer initialization parameters, consumerId(%s): %w", consumerId, err) + } + // also set the phase to registered + k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_REGISTERED) + + continue + } + + writeFn() + } + return nil +} + +// ConsumeIdsFromTimeQueue returns from a time queue the consumer ids for which the associated time passed. +// The number of ids return is limited to 'limit'. The ids returned are removed from the time queue. +func (k Keeper) ConsumeIdsFromTimeQueue( + ctx sdk.Context, + timeQueueKeyPrefix byte, + getIds func(sdk.Context, time.Time) (types.ConsumerIds, error), + deleteAllIds func(sdk.Context, time.Time), + appendId func(sdk.Context, string, time.Time) error, + limit int, +) ([]string, error) { + store := ctx.KVStore(k.storeKey) + + result := []string{} + nextTime := []string{} + timestampsToDelete := []time.Time{} + + iterator := storetypes.KVStorePrefixIterator(store, []byte{timeQueueKeyPrefix}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + if len(result) >= limit { + break + } + ts, err := types.ParseTime(timeQueueKeyPrefix, iterator.Key()) + if err != nil { + return result, fmt.Errorf("parsing removal time: %w", err) + } + if ts.After(ctx.BlockTime()) { + break + } + + consumerIds, err := getIds(ctx, ts) + if err != nil { + return result, + fmt.Errorf("getting consumers ids, ts(%s): %w", ts.String(), err) + } + + timestampsToDelete = append(timestampsToDelete, ts) + + availableSlots := limit - len(result) + if availableSlots >= len(consumerIds.Ids) { + // consumer all the ids + result = append(result, consumerIds.Ids...) + } else { + // consume only availableSlots + result = append(result, consumerIds.Ids[:availableSlots]...) + // and leave the others for next time + nextTime = consumerIds.Ids[availableSlots:] + break + } + } + + // remove consumers to prevent handling them twice + for i, ts := range timestampsToDelete { + deleteAllIds(ctx, ts) + if i == len(timestampsToDelete)-1 { + // for the last ts consumed, store back the ids for later + for _, consumerId := range nextTime { + err := appendId(ctx, consumerId, ts) + if err != nil { + return result, + fmt.Errorf("failed to append consumer id, consumerId(%s), ts(%s): %w", + consumerId, ts.String(), err) + } + } + } + } + + return result, nil +} + +// LaunchConsumer launches the chain with the provided consumer id by creating the consumer client and the respective +// consumer genesis file +// +// TODO add unit test for LaunchConsumer +func (k Keeper) LaunchConsumer( + ctx sdk.Context, + bondedValidators []stakingtypes.Validator, + activeValidators []stakingtypes.Validator, + consumerId string, +) error { + // compute consumer initial validator set + initialValUpdates, err := k.ComputeConsumerNextValSet(ctx, bondedValidators, activeValidators, consumerId, []types.ConsensusValidator{}) + if err != nil { + return fmt.Errorf("computing consumer next validator set, consumerId(%s): %w", consumerId, err) + } + if len(initialValUpdates) == 0 { + return fmt.Errorf("cannot launch consumer with no validator opted in, consumerId(%s)", consumerId) + } + + // create consumer genesis + genesisState, err := k.MakeConsumerGenesis(ctx, consumerId, initialValUpdates) + if err != nil { + return fmt.Errorf("creating consumer genesis state, consumerId(%s): %w", consumerId, err) + } + err = k.SetConsumerGenesis(ctx, consumerId, genesisState) + if err != nil { + return fmt.Errorf("setting consumer genesis state, consumerId(%s): %w", consumerId, err) + } + + // compute the hash of the consumer initial validator updates + updatesAsValSet, err := tmtypes.PB2TM.ValidatorUpdates(initialValUpdates) + if err != nil { + return fmt.Errorf("unable to create initial validator set from initial validator updates: %w", err) + } + valsetHash := tmtypes.NewValidatorSet(updatesAsValSet).Hash() + + // create the consumer client and the genesis + err = k.CreateConsumerClient(ctx, consumerId, valsetHash) + if err != nil { + return fmt.Errorf("crating consumer client, consumerId(%s): %w", consumerId, err) + } + + k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_LAUNCHED) + + k.Logger(ctx).Info("consumer successfully launched", + "consumerId", consumerId, + "valset size", len(initialValUpdates), + "valsetHash", string(valsetHash), + ) + + return nil +} + +// CreateConsumerClient will create the CCV client for the given consumer chain. The CCV channel must be built +// on top of the CCV client to ensure connection with the right consumer chain. +func (k Keeper) CreateConsumerClient( + ctx sdk.Context, + consumerId string, + valsetHash []byte, +) error { + initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return err + } + + phase := k.GetConsumerPhase(ctx, consumerId) + if phase != types.CONSUMER_PHASE_INITIALIZED { + return errorsmod.Wrapf(types.ErrInvalidPhase, + "cannot create client for consumer chain that is not in the Initialized phase but in phase %d: %s", phase, consumerId) + } + + chainId, err := k.GetConsumerChainId(ctx, consumerId) + if err != nil { + return err + } + + // Set minimum height for equivocation evidence from this consumer chain + k.SetEquivocationEvidenceMinHeight(ctx, consumerId, initializationRecord.InitialHeight.RevisionHeight) + + // Consumers start out with the unbonding period from the initialization parameters + consumerUnbondingPeriod := initializationRecord.UnbondingPeriod + + // Create client state by getting template client from initialization parameters + clientState := k.GetTemplateClient(ctx) + clientState.ChainId = chainId + clientState.LatestHeight = initializationRecord.InitialHeight + + trustPeriod, err := ccv.CalculateTrustPeriod(consumerUnbondingPeriod, k.GetTrustingPeriodFraction(ctx)) + if err != nil { + return err + } + clientState.TrustingPeriod = trustPeriod + clientState.UnbondingPeriod = consumerUnbondingPeriod + + // Create consensus state + consensusState := ibctmtypes.NewConsensusState( + ctx.BlockTime(), + commitmenttypes.NewMerkleRoot([]byte(ibctmtypes.SentinelRoot)), + valsetHash, + ) + + clientID, err := k.clientKeeper.CreateClient(ctx, clientState, consensusState) + if err != nil { + return err + } + k.SetConsumerClientId(ctx, consumerId, clientID) + + k.Logger(ctx).Info("consumer client created", + "consumer id", consumerId, + "client id", clientID, + ) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeConsumerClientCreated, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeConsumerId, consumerId), + sdk.NewAttribute(types.AttributeConsumerChainId, chainId), + sdk.NewAttribute(clienttypes.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeInitialHeight, initializationRecord.InitialHeight.String()), + sdk.NewAttribute(types.AttributeTrustingPeriod, clientState.TrustingPeriod.String()), + sdk.NewAttribute(types.AttributeUnbondingPeriod, clientState.UnbondingPeriod.String()), + sdk.NewAttribute(types.AttributeValsetHash, string(valsetHash)), + ), + ) + + return nil +} + +// MakeConsumerGenesis returns the created consumer genesis state for consumer chain `consumerId`, +// as well as the validator hash of the initial validator set of the consumer chain +func (k Keeper) MakeConsumerGenesis( + ctx sdk.Context, + consumerId string, + initialValidatorUpdates []abci.ValidatorUpdate, +) (gen ccv.ConsumerGenesisState, err error) { + initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return gen, errorsmod.Wrapf(ccv.ErrInvalidConsumerState, + "getting initialization parameters, consumerId(%s): %s", consumerId, err.Error()) + } + // note that providerFeePoolAddrStr is sent to the consumer during the IBC Channel handshake; + // see HandshakeMetadata in OnChanOpenTry on the provider-side, and OnChanOpenAck on the consumer-side + consumerGenesisParams := ccv.NewParams( + true, + initializationRecord.BlocksPerDistributionTransmission, + initializationRecord.DistributionTransmissionChannel, + "", // providerFeePoolAddrStr, + initializationRecord.CcvTimeoutPeriod, + initializationRecord.TransferTimeoutPeriod, + initializationRecord.ConsumerRedistributionFraction, + initializationRecord.HistoricalEntries, + initializationRecord.UnbondingPeriod, + []string{}, + []string{}, + ccv.DefaultRetryDelayPeriod, + consumerId, + ) + + // create provider client state and consensus state for the consumer to be able + // to create a provider client + + providerUnbondingPeriod, err := k.stakingKeeper.UnbondingTime(ctx) + if err != nil { + return gen, errorsmod.Wrapf(types.ErrNoUnbondingTime, "unbonding time not found: %s", err) + } + height := clienttypes.GetSelfHeight(ctx) + + clientState := k.GetTemplateClient(ctx) + // this is the counter party chain ID for the consumer + clientState.ChainId = ctx.ChainID() + // this is the latest height the client was updated at, i.e., + // the height of the latest consensus state (see below) + clientState.LatestHeight = height + trustPeriod, err := ccv.CalculateTrustPeriod(providerUnbondingPeriod, k.GetTrustingPeriodFraction(ctx)) + if err != nil { + return gen, errorsmod.Wrapf(sdkerrors.ErrInvalidHeight, "error %s calculating trusting_period for: %s", err, height) + } + clientState.TrustingPeriod = trustPeriod + clientState.UnbondingPeriod = providerUnbondingPeriod + + consState, err := k.clientKeeper.GetSelfConsensusState(ctx, height) + if err != nil { + return gen, errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "error %s getting self consensus state for: %s", err, height) + } + + gen = *ccv.NewInitialConsumerGenesisState( + clientState, + consState.(*ibctmtypes.ConsensusState), + initialValidatorUpdates, + consumerGenesisParams, + ) + return gen, nil +} + +// StopAndPrepareForConsumerRemoval sets the phase of the chain to stopped and prepares to get the state of the +// chain removed after unbonding period elapses +func (k Keeper) StopAndPrepareForConsumerRemoval(ctx sdk.Context, consumerId string) error { + // The phase of the chain is immediately set to stopped, albeit its state is removed later (see below). + // Setting the phase here helps in not considering this chain when we look at launched chains (e.g., in `QueueVSCPackets) + k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_STOPPED) + + // state of this chain is removed once UnbondingPeriod elapses + unbondingPeriod, err := k.stakingKeeper.UnbondingTime(ctx) + if err != nil { + return err + } + removalTime := ctx.BlockTime().Add(unbondingPeriod) + + if err := k.SetConsumerRemovalTime(ctx, consumerId, removalTime); err != nil { + return fmt.Errorf("cannot set removal time (%s): %s", removalTime.String(), err.Error()) + } + if err := k.AppendConsumerToBeRemoved(ctx, consumerId, removalTime); err != nil { + return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "cannot set consumer to be removed: %s", err.Error()) + } + + return nil +} + +// BeginBlockRemoveConsumers removes stopped consumer chain for which the removal time has passed +func (k Keeper) BeginBlockRemoveConsumers(ctx sdk.Context) error { + consumerIds, err := k.ConsumeIdsFromTimeQueue( + ctx, + types.RemovalTimeToConsumerIdsKeyPrefix(), + k.GetConsumersToBeRemoved, + k.DeleteAllConsumersToBeRemoved, + k.AppendConsumerToBeRemoved, + 200, + ) + if err != nil { + return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "getting consumers ready to stop: %s", err.Error()) + } + for _, consumerId := range consumerIds { + // delete consumer chain in a cached context to abort deletion in case of errors + cachedCtx, writeFn := ctx.CacheContext() + err = k.DeleteConsumerChain(cachedCtx, consumerId) + if err != nil { + k.Logger(ctx).Error("consumer chain could not be removed", + "consumerId", consumerId, + "error", err.Error()) + continue + } + + writeFn() + } + return nil +} + +// DeleteConsumerChain cleans up the state of the given consumer chain +func (k Keeper) DeleteConsumerChain(ctx sdk.Context, consumerId string) (err error) { + phase := k.GetConsumerPhase(ctx, consumerId) + if phase != types.CONSUMER_PHASE_STOPPED { + return fmt.Errorf("cannot delete non-stopped chain: %s", consumerId) + } + + // clean up states + k.DeleteConsumerClientId(ctx, consumerId) + k.DeleteConsumerGenesis(ctx, consumerId) + // Note: this call panics if the key assignment state is invalid + k.DeleteKeyAssignments(ctx, consumerId) + k.DeleteMinimumPowerInTopN(ctx, consumerId) + k.DeleteEquivocationEvidenceMinHeight(ctx, consumerId) + + // close channel and delete the mappings between chain ID and channel ID + if channelID, found := k.GetConsumerIdToChannelId(ctx, consumerId); found { + // Close the channel for the given channel ID on the condition + // that the channel exists and isn't already in the CLOSED state + channel, found := k.channelKeeper.GetChannel(ctx, ccv.ProviderPortID, channelID) + if found && channel.State != channeltypes.CLOSED { + err := k.chanCloseInit(ctx, channelID) + if err != nil { + k.Logger(ctx).Error("channel to consumer chain could not be closed", + "consumerId", consumerId, + "channelID", channelID, + "error", err.Error(), + ) + } + } + k.DeleteConsumerIdToChannelId(ctx, consumerId) + k.DeleteChannelIdToConsumerId(ctx, channelID) + } + + // delete consumer commission rate + provAddrs := k.GetAllCommissionRateValidators(ctx, consumerId) + for _, addr := range provAddrs { + k.DeleteConsumerCommissionRate(ctx, consumerId, addr) + } + + k.DeleteInitChainHeight(ctx, consumerId) + k.DeleteSlashAcks(ctx, consumerId) + k.DeletePendingVSCPackets(ctx, consumerId) + + k.DeleteAllowlist(ctx, consumerId) + k.DeleteDenylist(ctx, consumerId) + k.DeleteAllOptedIn(ctx, consumerId) + k.DeleteConsumerValSet(ctx, consumerId) + + k.DeleteConsumerRewardsAllocation(ctx, consumerId) + k.DeleteConsumerRemovalTime(ctx, consumerId) + + // TODO (PERMISSIONLESS) add newly-added state to be deleted + + // Note that we do not delete ConsumerIdToChainIdKey and ConsumerIdToPhase, as well + // as consumer metadata, initialization and power-shaping parameters. + // This is to enable block explorers and front ends to show information of + // consumer chains that were removed without needing an archive node. + + k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_DELETED) + k.Logger(ctx).Info("consumer chain deleted from provider", "consumerId", consumerId) + + return nil +} + +// +// Setters and Getters +// + +// GetConsumerRemovalTime returns the removal time associated with the to-be-removed chain with consumer id +func (k Keeper) GetConsumerRemovalTime(ctx sdk.Context, consumerId string) (time.Time, error) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.ConsumerIdToRemovalTimeKey(consumerId)) + if buf == nil { + return time.Time{}, fmt.Errorf("failed to retrieve removal time for consumer id (%s)", consumerId) + } + var time time.Time + if err := time.UnmarshalBinary(buf); err != nil { + return time, fmt.Errorf("failed to unmarshal removal time for consumer id (%s): %w", consumerId, err) + } + return time, nil +} + +// SetConsumerRemovalTime sets the removal time associated with this consumer id +func (k Keeper) SetConsumerRemovalTime(ctx sdk.Context, consumerId string, removalTime time.Time) error { + store := ctx.KVStore(k.storeKey) + buf, err := removalTime.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal removal time (%+v) for consumer id (%s): %w", removalTime, consumerId, err) + } + store.Set(types.ConsumerIdToRemovalTimeKey(consumerId), buf) + return nil +} + +// DeleteConsumerRemovalTime deletes the removal time associated with this consumer id +func (k Keeper) DeleteConsumerRemovalTime(ctx sdk.Context, consumerId string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ConsumerIdToRemovalTimeKey(consumerId)) +} + +// getConsumerIdsBasedOnTime returns all the consumer ids stored under this specific `key(time)` +func (k Keeper) getConsumerIdsBasedOnTime(ctx sdk.Context, key func(time.Time) []byte, time time.Time) (types.ConsumerIds, error) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(key(time)) + if bz == nil { + return types.ConsumerIds{}, nil + } + + var consumerIds types.ConsumerIds + + if err := consumerIds.Unmarshal(bz); err != nil { + return types.ConsumerIds{}, fmt.Errorf("failed to unmarshal consumer ids: %w", err) + } + return consumerIds, nil +} + +// appendConsumerIdOnTime appends the consumer id on all the other consumer ids under `key(time)` +func (k Keeper) appendConsumerIdOnTime(ctx sdk.Context, consumerId string, key func(time.Time) []byte, time time.Time) error { + store := ctx.KVStore(k.storeKey) + + consumers, err := k.getConsumerIdsBasedOnTime(ctx, key, time) + if err != nil { + return err + } + + consumersWithAppend := types.ConsumerIds{ + Ids: append(consumers.Ids, consumerId), + } + + bz, err := consumersWithAppend.Marshal() + if err != nil { + return err + } + + store.Set(key(time), bz) + return nil +} + +// removeConsumerIdFromTime removes consumer id stored under `key(time)` +func (k Keeper) removeConsumerIdFromTime(ctx sdk.Context, consumerId string, key func(time.Time) []byte, time time.Time) error { + store := ctx.KVStore(k.storeKey) + + consumers, err := k.getConsumerIdsBasedOnTime(ctx, key, time) + if err != nil { + return err + } + + if len(consumers.Ids) == 0 { + return fmt.Errorf("no consumer ids found for this time: %s", time.String()) + } + + // find the index of the consumer we want to remove + index := -1 + for i := 0; i < len(consumers.Ids); i++ { + if consumers.Ids[i] == consumerId { + index = i + break + } + } + + if index == -1 { + return fmt.Errorf("failed to find consumer id (%s)", consumerId) + } + + if len(consumers.Ids) == 1 { + store.Delete(key(time)) + return nil + } + + consumersWithRemoval := types.ConsumerIds{ + Ids: append(consumers.Ids[:index], consumers.Ids[index+1:]...), + } + + bz, err := consumersWithRemoval.Marshal() + if err != nil { + return err + } + + store.Set(key(time), bz) + return nil +} + +// GetConsumersToBeLaunched returns all the consumer ids of chains stored under this spawn time +func (k Keeper) GetConsumersToBeLaunched(ctx sdk.Context, spawnTime time.Time) (types.ConsumerIds, error) { + return k.getConsumerIdsBasedOnTime(ctx, types.SpawnTimeToConsumerIdsKey, spawnTime) +} + +// AppendConsumerToBeLaunched appends the provider consumer id for the given spawn time +func (k Keeper) AppendConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { + return k.appendConsumerIdOnTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) +} + +// RemoveConsumerToBeLaunched removes consumer id from if stored for this specific spawn time +func (k Keeper) RemoveConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { + return k.removeConsumerIdFromTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) +} + +// DeleteAllConsumersToBeLaunched deletes all consumer to be launched at this specific spawn time +func (k Keeper) DeleteAllConsumersToBeLaunched(ctx sdk.Context, spawnTime time.Time) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.SpawnTimeToConsumerIdsKey(spawnTime)) +} + +// GetConsumersToBeRemoved returns all the consumer ids of chains stored under this removal time +func (k Keeper) GetConsumersToBeRemoved(ctx sdk.Context, removalTime time.Time) (types.ConsumerIds, error) { + return k.getConsumerIdsBasedOnTime(ctx, types.RemovalTimeToConsumerIdsKey, removalTime) +} + +// AppendConsumerToBeRemoved appends the provider consumer id for the given removal time +func (k Keeper) AppendConsumerToBeRemoved(ctx sdk.Context, consumerId string, removalTime time.Time) error { + return k.appendConsumerIdOnTime(ctx, consumerId, types.RemovalTimeToConsumerIdsKey, removalTime) +} + +// RemoveConsumerToBeRemoved removes consumer id from the given removal time +func (k Keeper) RemoveConsumerToBeRemoved(ctx sdk.Context, consumerId string, removalTime time.Time) error { + return k.removeConsumerIdFromTime(ctx, consumerId, types.RemovalTimeToConsumerIdsKey, removalTime) +} + +// DeleteAllConsumersToBeRemoved deletes all consumer to be removed at this specific removal time +func (k Keeper) DeleteAllConsumersToBeRemoved(ctx sdk.Context, removalTime time.Time) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.RemovalTimeToConsumerIdsKey(removalTime)) +} diff --git a/x/ccv/provider/keeper/consumer_lifecycle_test.go b/x/ccv/provider/keeper/consumer_lifecycle_test.go new file mode 100644 index 0000000000..6b258ae003 --- /dev/null +++ b/x/ccv/provider/keeper/consumer_lifecycle_test.go @@ -0,0 +1,1046 @@ +package keeper_test + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v8/testing" + _go "github.com/cosmos/ics23/go" + "github.com/golang/mock/gomock" + "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" + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + + cryptotestutil "github.com/cosmos/interchain-security/v6/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v6/testutil/keeper" + providerkeeper "github.com/cosmos/interchain-security/v6/x/ccv/provider/keeper" + providertypes "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" +) + +func TestPrepareConsumerForLaunch(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + spawnTime := time.Now().UTC() + err := providerKeeper.PrepareConsumerForLaunch(ctx, CONSUMER_ID, time.Time{}, spawnTime) + require.NoError(t, err) + + consumers, err := providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, providertypes.ConsumerIds{Ids: []string{CONSUMER_ID}}, consumers) + + nextSpawnTime := spawnTime.Add(time.Hour) + err = providerKeeper.PrepareConsumerForLaunch(ctx, CONSUMER_ID, spawnTime, nextSpawnTime) + require.NoError(t, err) + + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Empty(t, consumers) + + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, nextSpawnTime) + require.NoError(t, err) + require.Equal(t, providertypes.ConsumerIds{Ids: []string{CONSUMER_ID}}, consumers) +} + +func TestInitializeConsumer(t *testing.T) { + now := time.Now().UTC() + consumerId := "13" + + testCases := []struct { + name string + spawnTime time.Time + setup func(*providerkeeper.Keeper, sdk.Context, time.Time) + expInitialized bool + }{ + { + name: "valid", + spawnTime: now, + setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { + pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) + err := pk.SetConsumerInitializationParameters(ctx, consumerId, + providertypes.ConsumerInitializationParameters{ + SpawnTime: spawnTime, + }) + require.NoError(t, err) + }, + expInitialized: true, + }, + { + name: "invalid: no phase", + spawnTime: now, + setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { + }, + expInitialized: false, + }, + { + name: "invalid: wrong phase", + spawnTime: now, + setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { + pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_LAUNCHED) + err := pk.SetConsumerInitializationParameters(ctx, consumerId, + providertypes.ConsumerInitializationParameters{ + SpawnTime: spawnTime, + }) + require.NoError(t, err) + }, + expInitialized: false, + }, + { + name: "invalid: no init params", + spawnTime: now, + setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { + pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) + }, + expInitialized: false, + }, + { + name: "invalid: zero spawn time", + spawnTime: now, + setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { + pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) + err := pk.SetConsumerInitializationParameters(ctx, consumerId, + providertypes.ConsumerInitializationParameters{ + SpawnTime: time.Time{}, + }) + require.NoError(t, err) + }, + expInitialized: false, + }, + } + + for _, tc := range testCases { + pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tc.setup(&pk, ctx, tc.spawnTime) + + spawnTime, initialized := pk.InitializeConsumer(ctx, consumerId) + require.Equal(t, tc.expInitialized, initialized, tc.name) + if initialized { + require.Equal(t, tc.spawnTime, spawnTime, tc.name) + require.Equal(t, providertypes.CONSUMER_PHASE_INITIALIZED, pk.GetConsumerPhase(ctx, consumerId)) + } + } +} + +// TestBeginBlockInit directly tests BeginBlockLaunchConsumers against the spec using helpers defined above. +func TestBeginBlockLaunchConsumers(t *testing.T) { + now := time.Now().UTC() + + keeperParams := testkeeper.NewInMemKeeperParams(t) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + providerKeeper.SetParams(ctx, providertypes.DefaultParams()) + defer ctrl.Finish() + ctx = ctx.WithBlockTime(now) + + // initialize registration, initialization, and update records + chainIds := []string{"chain0", "chain1", "chain2", "chain3", "chain4"} + + initializationParameters := []providertypes.ConsumerInitializationParameters{ + { + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{}, + BinaryHash: []byte{}, + SpawnTime: now.Add(-time.Hour * 2).UTC(), + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + { + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{}, + BinaryHash: []byte{}, + SpawnTime: now.Add(-time.Hour).UTC(), + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + { + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{}, + BinaryHash: []byte{}, + SpawnTime: now.Add(time.Hour).UTC(), + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + { + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{}, + BinaryHash: []byte{}, + SpawnTime: now.Add(-time.Hour).UTC(), + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + { + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{}, + BinaryHash: []byte{}, + SpawnTime: now.Add(-time.Minute).UTC(), + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + } + powerShapingParameters := []providertypes.PowerShapingParameters{ + { + Top_N: 50, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: []string{}, + Denylist: []string{}, + }, + { + Top_N: 50, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: []string{}, + Denylist: []string{}, + }, + { + Top_N: 50, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: []string{}, + Denylist: []string{}, + }, + { + Top_N: 0, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: []string{}, + Denylist: []string{}, + }, + { + Top_N: 0, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: []string{}, + Denylist: []string{}, + }, + } + + // set up all the records + for i, chainId := range chainIds { + providerKeeper.SetConsumerChainId(ctx, fmt.Sprintf("%d", i), chainId) + } + + for i, r := range initializationParameters { + err := providerKeeper.SetConsumerInitializationParameters(ctx, fmt.Sprintf("%d", i), r) + require.NoError(t, err) + // set up the chains in their initialized phase, hence they could launch + providerKeeper.SetConsumerPhase(ctx, fmt.Sprintf("%d", i), providertypes.CONSUMER_PHASE_INITIALIZED) + err = providerKeeper.AppendConsumerToBeLaunched(ctx, fmt.Sprintf("%d", i), r.SpawnTime) + require.NoError(t, err) + } + for i, r := range powerShapingParameters { + err := providerKeeper.SetConsumerPowerShapingParameters(ctx, fmt.Sprintf("%d", i), r) + require.NoError(t, err) + } + + // opt in a sample validator so the chain's proposal can successfully execute + validator := cryptotestutil.NewCryptoIdentityFromIntSeed(0).SDKStakingValidator() + consAddr, _ := validator.GetConsAddr() + testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 1, []stakingtypes.Validator{validator}, -1) // -1 to allow any number of calls + + valAddr, _ := sdk.ValAddressFromBech32(validator.GetOperator()) + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), valAddr).Return(int64(1), nil).AnyTimes() + + providerKeeper.SetOptedIn(ctx, "3", providertypes.NewProviderConsAddress(consAddr)) + + // Expect genesis and client creation for only the first, second, and fifth chains (spawn time already passed and valid) + expectedCalls := testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour) + expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain0", clienttypes.NewHeight(3, 4))...) + expectedCalls = append(expectedCalls, testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour)...) + expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain1", clienttypes.NewHeight(3, 4))...) + expectedCalls = append(expectedCalls, testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour)...) + expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain3", clienttypes.NewHeight(3, 4))...) + + gomock.InOrder(expectedCalls...) + + err := providerKeeper.BeginBlockLaunchConsumers(ctx) + require.NoError(t, err) + + // first chain was successfully launched + phase := providerKeeper.GetConsumerPhase(ctx, "0") + require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) + _, found := providerKeeper.GetConsumerGenesis(ctx, "0") + require.True(t, found) + + // second chain was successfully launched + phase = providerKeeper.GetConsumerPhase(ctx, "1") + require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) + _, found = providerKeeper.GetConsumerGenesis(ctx, "1") + require.True(t, found) + + // third chain was not launched because its spawn time has not passed + phase = providerKeeper.GetConsumerPhase(ctx, "2") + require.Equal(t, providertypes.CONSUMER_PHASE_INITIALIZED, phase) + _, found = providerKeeper.GetConsumerGenesis(ctx, "2") + require.False(t, found) + + // fourth chain corresponds to an Opt-In chain with one opted-in validator and hence the chain gets + // successfully executed + phase = providerKeeper.GetConsumerPhase(ctx, "3") + require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) + _, found = providerKeeper.GetConsumerGenesis(ctx, "3") + require.True(t, found) + + // fifth chain corresponds to an Opt-In chain with no opted-in validators and hence the + // chain launch is NOT successful + phase = providerKeeper.GetConsumerPhase(ctx, "4") + require.Equal(t, providertypes.CONSUMER_PHASE_REGISTERED, phase) + _, found = providerKeeper.GetConsumerGenesis(ctx, "4") + require.False(t, found) +} + +func TestConsumeIdsFromTimeQueue(t *testing.T) { + expectedConsumerIds := []string{"1", "2", "3", "4"} + timestamps := []time.Time{time.Unix(10, 0), time.Unix(20, 0), time.Unix(30, 0)} + + testCases := []struct { + name string + ts time.Time + limit int + expOutcome func(sdk.Context, []string, func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) + }{ + { + name: "timestamp too early", + ts: time.Unix(9, 999999999), + limit: 3, + expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { + require.Empty(t, ids) + }, + }, + { + name: "first timestamp", + ts: timestamps[0], + limit: 2, + expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { + require.Equal(t, expectedConsumerIds[0:2], ids) + + // check that all consumers where removed + consumerIds, err := getIds(ctx, timestamps[0]) + require.NoError(t, err) + require.Empty(t, consumerIds) + }, + }, + { + name: "first timestamp, with limit", + ts: timestamps[0], + limit: 1, + expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { + require.Equal(t, expectedConsumerIds[0:1], ids) + + // second consumer remained + ret, err := getIds(ctx, timestamps[0]) + require.NoError(t, err) + require.Equal(t, providertypes.ConsumerIds{ + Ids: []string{expectedConsumerIds[1]}, + }, ret) + }, + }, + { + name: "second timestamp", + ts: timestamps[1], + limit: 3, + expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { + require.Equal(t, expectedConsumerIds[0:3], ids) + + // check that all consumers where removed + ret, err := getIds(ctx, timestamps[0]) + require.NoError(t, err) + require.Empty(t, ret) + ret, err = getIds(ctx, timestamps[1]) + require.NoError(t, err) + require.Empty(t, ret) + }, + }, + { + name: "third timestamp, with limit", + ts: timestamps[1], + limit: 3, + expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { + require.Equal(t, expectedConsumerIds[0:3], ids) + + // 4th consumer remained + ret, err := getIds(ctx, timestamps[0]) + require.NoError(t, err) + require.Empty(t, ret) + ret, err = getIds(ctx, timestamps[1]) + require.NoError(t, err) + require.Empty(t, ret) + ret, err = getIds(ctx, timestamps[2]) + require.NoError(t, err) + require.Equal(t, providertypes.ConsumerIds{ + Ids: []string{expectedConsumerIds[3]}, + }, ret) + }, + }, + } + + // test for consumers to be launched + for _, tc := range testCases { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + callCases := []struct { + timeQueueKeyPrefix byte + getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error) + deleteAllIds func(sdk.Context, time.Time) + appendId func(sdk.Context, string, time.Time) error + }{ + { + timeQueueKeyPrefix: providertypes.SpawnTimeToConsumerIdsKeyPrefix(), + getIds: providerKeeper.GetConsumersToBeLaunched, + deleteAllIds: providerKeeper.DeleteAllConsumersToBeLaunched, + appendId: providerKeeper.AppendConsumerToBeLaunched, + }, + { + timeQueueKeyPrefix: providertypes.RemovalTimeToConsumerIdsKeyPrefix(), + getIds: providerKeeper.GetConsumersToBeRemoved, + deleteAllIds: providerKeeper.DeleteAllConsumersToBeRemoved, + appendId: providerKeeper.AppendConsumerToBeRemoved, + }, + } + for _, cc := range callCases { + err := cc.appendId(ctx, expectedConsumerIds[0], timestamps[0]) + require.NoError(t, err) + err = cc.appendId(ctx, expectedConsumerIds[1], timestamps[0]) + require.NoError(t, err) + err = cc.appendId(ctx, expectedConsumerIds[2], timestamps[1]) + require.NoError(t, err) + err = cc.appendId(ctx, expectedConsumerIds[3], timestamps[2]) + require.NoError(t, err) + + ctx = ctx.WithBlockTime(tc.ts) + + consumerIds, err := providerKeeper.ConsumeIdsFromTimeQueue( + ctx, + cc.timeQueueKeyPrefix, + cc.getIds, + cc.deleteAllIds, + cc.appendId, + tc.limit, + ) + require.NoError(t, err) + + tc.expOutcome(ctx, consumerIds, cc.getIds) + } + } +} + +func TestCreateConsumerClient(t *testing.T) { + type testCase struct { + description string + // Any state-mutating setup on keeper and expected mock calls, specific to this test case + setup func(*providerkeeper.Keeper, sdk.Context, *testkeeper.MockedKeepers) + // Whether a client should be created + expClientCreated bool + } + tests := []testCase{ + { + description: "No state mutation, new client should be created", + setup: func(providerKeeper *providerkeeper.Keeper, ctx sdk.Context, mocks *testkeeper.MockedKeepers) { + providerKeeper.SetConsumerPhase(ctx, CONSUMER_ID, providertypes.CONSUMER_PHASE_INITIALIZED) + + // Valid client creation is asserted with mock expectations here + gomock.InOrder( + testkeeper.GetMocksForCreateConsumerClient(ctx, mocks, CONSUMER_CHAIN_ID, clienttypes.NewHeight(4, 5))..., + ) + }, + expClientCreated: true, + }, + { + description: "chain for this consumer id has already launched, and hence client was created, NO new one is created", + setup: func(providerKeeper *providerkeeper.Keeper, ctx sdk.Context, mocks *testkeeper.MockedKeepers) { + providerKeeper.SetConsumerPhase(ctx, CONSUMER_ID, providertypes.CONSUMER_PHASE_LAUNCHED) + + // Expect none of the client creation related calls to happen + mocks.MockClientKeeper.EXPECT().CreateClient(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + expClientCreated: false, + }, + } + + for _, tc := range tests { + // Common setup + keeperParams := testkeeper.NewInMemKeeperParams(t) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + providerKeeper.SetParams(ctx, providertypes.DefaultParams()) + + // Test specific setup + tc.setup(&providerKeeper, ctx, &mocks) + + // Call method with same arbitrary values as defined above in mock expectations. + providerKeeper.SetConsumerChainId(ctx, CONSUMER_ID, CONSUMER_CHAIN_ID) + err := providerKeeper.SetConsumerInitializationParameters(ctx, CONSUMER_ID, testkeeper.GetTestInitializationParameters()) + require.NoError(t, err) + + err = providerKeeper.CreateConsumerClient(ctx, CONSUMER_ID, []byte{}) + if tc.expClientCreated { + require.NoError(t, err) + clientId, found := providerKeeper.GetConsumerClientId(ctx, CONSUMER_ID) + require.True(t, found) + require.Equal(t, "clientID", clientId) + } else { + require.Error(t, err) + } + + // Assert mock calls from setup functions + ctrl.Finish() + } +} + +// TestMakeConsumerGenesis tests the MakeConsumerGenesis keeper method. +// An expected genesis state is hardcoded in json, unmarshaled, and compared +// against an actual consumer genesis state constructed by a provider keeper. +func TestMakeConsumerGenesis(t *testing.T) { + keeperParams := testkeeper.NewInMemKeeperParams(t) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + defer ctrl.Finish() + + moduleParams := providertypes.Params{ + TemplateClient: &ibctmtypes.ClientState{ + TrustLevel: ibctmtypes.DefaultTrustLevel, + MaxClockDrift: 10000000000, + ProofSpecs: []*_go.ProofSpec{ + { + LeafSpec: &_go.LeafOp{ + Hash: _go.HashOp_SHA256, + PrehashKey: _go.HashOp_NO_HASH, + PrehashValue: _go.HashOp_SHA256, + Length: _go.LengthOp_VAR_PROTO, + Prefix: []byte{0x00}, + }, + InnerSpec: &_go.InnerSpec{ + ChildOrder: []int32{0, 1}, + ChildSize: 33, + MinPrefixLength: 4, + MaxPrefixLength: 12, + Hash: _go.HashOp_SHA256, + }, + MaxDepth: 0, + MinDepth: 0, + }, + { + LeafSpec: &_go.LeafOp{ + Hash: _go.HashOp_SHA256, + PrehashKey: _go.HashOp_NO_HASH, + PrehashValue: _go.HashOp_SHA256, + Length: _go.LengthOp_VAR_PROTO, + Prefix: []byte{0x00}, + }, + InnerSpec: &_go.InnerSpec{ + ChildOrder: []int32{0, 1}, + ChildSize: 32, + MinPrefixLength: 1, + MaxPrefixLength: 1, + Hash: _go.HashOp_SHA256, + }, + MaxDepth: 0, + }, + }, + UpgradePath: []string{"upgrade", "upgradedIBCState"}, + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + }, + // Note these are unused provider parameters for this test, and not actually asserted against + // They must be populated with reasonable values to satisfy SetParams though. + TrustingPeriodFraction: providertypes.DefaultTrustingPeriodFraction, + CcvTimeoutPeriod: ccvtypes.DefaultCCVTimeoutPeriod, + SlashMeterReplenishPeriod: providertypes.DefaultSlashMeterReplenishPeriod, + SlashMeterReplenishFraction: providertypes.DefaultSlashMeterReplenishFraction, + ConsumerRewardDenomRegistrationFee: sdk.Coin{ + Denom: "stake", + Amount: math.NewInt(1000000), + }, + BlocksPerEpoch: 600, + NumberOfEpochsToStartReceivingRewards: 24, + } + providerKeeper.SetParams(ctx, moduleParams) + + // matches params from jsonString + ccvTimeoutPeriod := time.Duration(2419200000000000) + transferTimeoutPeriod := time.Duration(3600000000000) + consumerUnbondingPeriod := time.Duration(1728000000000000) + providerUnbondingPeriod := time.Duration(1814400000000000) + trustingPeriod := time.Duration(1197504000000000) + providerChainId := "provider-1" + providerRevisionNumber := uint64(1) + providerRevisionHeight := int64(5) + + initializationParameters := providertypes.ConsumerInitializationParameters{ + BlocksPerDistributionTransmission: 1000, + CcvTimeoutPeriod: ccvTimeoutPeriod, + TransferTimeoutPeriod: transferTimeoutPeriod, + ConsumerRedistributionFraction: "0.75", + HistoricalEntries: 10000, + UnbondingPeriod: consumerUnbondingPeriod, + } + + // + // Other setup not covered by custom template client state + // + ctx = ctx.WithChainID(providerChainId) // consumerId is obtained from ctx + ctx = ctx.WithBlockHeight(providerRevisionHeight) // RevisionHeight obtained from ctx + gomock.InOrder(testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, providerUnbondingPeriod)...) + + providerKeeper.SetConsumerChainId(ctx, CONSUMER_ID, CONSUMER_CHAIN_ID) + err := providerKeeper.SetConsumerInitializationParameters(ctx, CONSUMER_ID, initializationParameters) + require.NoError(t, err) + + _, pks, _ := ibctesting.GenerateKeys(t, 2) + var ppks [2]tmprotocrypto.PublicKey + for i, pk := range pks { + ppks[i], _ = cryptocodec.ToCmtProtoPublicKey(pk) + } + initialValUpdates := []abci.ValidatorUpdate{ + {PubKey: ppks[0], Power: 1}, + {PubKey: ppks[1], Power: 2}, + } + + actualGenesis, err := providerKeeper.MakeConsumerGenesis(ctx, CONSUMER_ID, initialValUpdates) + require.NoError(t, err) + + // JSON string with tabs, newlines and spaces for readability + jsonString := fmt.Sprintf(`{ + "params": { + "enabled": true, + "blocks_per_distribution_transmission": %d, + "ccv_timeout_period": %d, + "transfer_timeout_period": %d, + "consumer_redistribution_fraction": "%s", + "historical_entries": %d, + "unbonding_period": %d, + "soft_opt_out_threshold": "0", + "reward_denoms": [], + "provider_reward_denoms": [], + "retry_delay_period": %d, + "consumer_id": "%s" + }, + "new_chain": true, + "provider" : { + "client_state": { + "chain_id": "%s", + "trust_level": { + "numerator": 1, + "denominator": 3 + }, + "trusting_period": %d, + "unbonding_period": %d, + "max_clock_drift": %d, + "frozen_height": {}, + "latest_height": { + "revision_number": %d, + "revision_height": %d + }, + "proof_specs": [ + { + "leaf_spec": { + "hash": 1, + "prehash_value": 1, + "length": 1, + "prefix": "AA==" + }, + "inner_spec": { + "child_order": [0, 1], + "child_size": 33, + "min_prefix_length": 4, + "max_prefix_length": 12, + "hash": 1 + } + }, + { + "leaf_spec": { + "hash": 1, + "prehash_value": 1, + "length": 1, + "prefix": "AA==" + }, + "inner_spec": { + "child_order": [0, 1], + "child_size": 32, + "min_prefix_length": 1, + "max_prefix_length": 1, + "hash": 1 + } + } + ], + "upgrade_path": ["upgrade", "upgradedIBCState"], + "allow_update_after_expiry": true, + "allow_update_after_misbehaviour": true + }, + "consensus_state": { + "timestamp": "2020-01-02T00:00:10Z", + "root": { + "hash": "LpGpeyQVLUo9HpdsgJr12NP2eCICspcULiWa5u9udOA=" + }, + "next_validators_hash": "E30CE736441FB9101FADDAF7E578ABBE6DFDB67207112350A9A904D554E1F5BE" + }, + "initial_val_set": [{}] + } + }`, + initializationParameters.BlocksPerDistributionTransmission, + ccvTimeoutPeriod.Nanoseconds(), + transferTimeoutPeriod.Nanoseconds(), + initializationParameters.ConsumerRedistributionFraction, + initializationParameters.HistoricalEntries, + consumerUnbondingPeriod.Nanoseconds(), + ccvtypes.DefaultRetryDelayPeriod.Nanoseconds(), + CONSUMER_ID, + providerChainId, + trustingPeriod.Nanoseconds(), + providerUnbondingPeriod.Nanoseconds(), + providertypes.DefaultMaxClockDrift.Nanoseconds(), + providerRevisionNumber, + providerRevisionHeight, + ) + + var expectedGenesis ccvtypes.ConsumerGenesisState + err = json.Unmarshal([]byte(jsonString), &expectedGenesis) // ignores tabs, newlines and spaces + require.NoError(t, err) + expectedGenesis.Provider.InitialValSet = initialValUpdates + + // Zeroing out different fields that are challenging to mock + actualGenesis.Provider.ConsensusState = &ibctmtypes.ConsensusState{} + expectedGenesis.Provider.ConsensusState = &ibctmtypes.ConsensusState{} + + require.Equal(t, expectedGenesis, actualGenesis, "consumer chain genesis created incorrectly") +} + +func TestBeginBlockStopConsumers(t *testing.T) { + now := time.Now().UTC() + + keeperParams := testkeeper.NewInMemKeeperParams(t) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + providerKeeper.SetParams(ctx, providertypes.DefaultParams()) + defer ctrl.Finish() + ctx = ctx.WithBlockTime(now) + + chainIds := []string{"chain1", "chain2", "chain3"} + consumerIds := []string{"consumerId1", "consumerId2", "consumerId3"} + err := providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[0], now.Add(-time.Hour)) + require.NoError(t, err) + err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[0], now.Add(-time.Hour)) + require.NoError(t, err) + err = providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[1], now) + require.NoError(t, err) + err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[1], now) + require.NoError(t, err) + err = providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[2], now.Add(time.Hour)) + require.NoError(t, err) + err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[2], now.Add(time.Hour)) + require.NoError(t, err) + + // + // Mock expectations + // + expectations := []*gomock.Call{} + for i := range consumerIds { + chainId := chainIds[i] + // A consumer chain is setup corresponding to each consumerId, making these mocks necessary + expectations = append(expectations, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, + chainId, clienttypes.NewHeight(2, 3))...) + expectations = append(expectations, testkeeper.GetMocksForSetConsumerChain(ctx, &mocks, chainId)...) + } + // Only first two consumer chains should be stopped + expectations = append(expectations, testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) + expectations = append(expectations, testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) + + gomock.InOrder(expectations...) + + // + // Remaining setup + // + for i, consumerId := range consumerIds { + // Setup a valid consumer chain for each consumerId + initializationRecord := testkeeper.GetTestInitializationParameters() + initializationRecord.InitialHeight = clienttypes.NewHeight(2, 3) + registrationRecord := testkeeper.GetTestConsumerMetadata() + + providerKeeper.SetConsumerChainId(ctx, consumerId, chainIds[i]) + err = providerKeeper.SetConsumerMetadata(ctx, consumerId, registrationRecord) + require.NoError(t, err) + err = providerKeeper.SetConsumerInitializationParameters(ctx, consumerId, initializationRecord) + require.NoError(t, err) + err = providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerId, testkeeper.GetTestPowerShapingParameters()) + require.NoError(t, err) + providerKeeper.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_INITIALIZED) + providerKeeper.SetConsumerClientId(ctx, consumerId, "clientID") + + err = providerKeeper.CreateConsumerClient(ctx, consumerId, []byte{}) + require.NoError(t, err) + err = providerKeeper.SetConsumerChain(ctx, "channelID") + require.NoError(t, err) + + // the chain is considered to be stopped and ready for deletion (i.e., `StopAndPrepareForConsumerRemoval` is called) + providerKeeper.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_STOPPED) + } + + // + // Test execution + // + + err = providerKeeper.BeginBlockRemoveConsumers(ctx) + require.NoError(t, err) + + // Only the 3rd (final) proposal is still stored as pending + phase := providerKeeper.GetConsumerPhase(ctx, consumerIds[0]) + require.Equal(t, providertypes.CONSUMER_PHASE_DELETED, phase) + phase = providerKeeper.GetConsumerPhase(ctx, consumerIds[1]) + require.Equal(t, providertypes.CONSUMER_PHASE_DELETED, phase) + // third chain had a removal time in the future and hence did not get deleted + phase = providerKeeper.GetConsumerPhase(ctx, consumerIds[2]) + require.Equal(t, providertypes.CONSUMER_PHASE_STOPPED, phase) +} + +// Tests the DeleteConsumerChain method against the spec, +// with more granularity than what's covered in TestHandleLegacyConsumerRemovalProposal, or integration tests. +// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-stcc1 +// Spec tag: [CCV-PCF-STCC.1] +func TestStopConsumerChain(t *testing.T) { + type testCase struct { + description string + // State-mutating setup specific to this test case + setup func(sdk.Context, *providerkeeper.Keeper, testkeeper.MockedKeepers) + // Whether we should expect the method to return an error + expErr bool + } + + consumerId := "0" + + tests := []testCase{ + { + description: "proposal dropped, client doesn't exist", + setup: func(ctx sdk.Context, providerKeeper *providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { + // No mocks, meaning no external keeper methods are allowed to be called. + }, + expErr: true, + }, + { + description: "valid stop of consumer chain, all mock calls hit", + setup: func(ctx sdk.Context, providerKeeper *providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { + testkeeper.SetupForDeleteConsumerChain(t, ctx, providerKeeper, mocks, consumerId) + + // set consumer minimum equivocation height + providerKeeper.SetEquivocationEvidenceMinHeight(ctx, consumerId, 1) + + // assert mocks for expected calls to `DeleteConsumerChain` when closing the underlying channel + gomock.InOrder(testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) + }, + expErr: false, + }, + } + + for _, tc := range tests { + + // Common setup + keeperParams := testkeeper.NewInMemKeeperParams(t) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + providerKeeper.SetParams(ctx, providertypes.DefaultParams()) + + // Setup specific to test case + tc.setup(ctx, &providerKeeper, mocks) + + err := providerKeeper.DeleteConsumerChain(ctx, consumerId) + + if tc.expErr { + require.Error(t, err, t) + } else { + require.NoError(t, err) + } + + testkeeper.TestProviderStateIsCleanedAfterConsumerChainIsDeleted(t, ctx, providerKeeper, consumerId, "channelID", tc.expErr) + + ctrl.Finish() + } +} + +// +// Setters and Getters +// + +// TestConsumerRemovalTime tests the getter, setter, and deletion of the consumer id to removal times methods +func TestConsumerRemovalTime(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + _, err := providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) + require.Error(t, err) + + expectedRemovalTime := time.Unix(1234, 56789) + providerKeeper.SetConsumerRemovalTime(ctx, CONSUMER_ID, expectedRemovalTime) + actualRemovalTime, err := providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) + require.NoError(t, err) + require.Equal(t, actualRemovalTime, expectedRemovalTime) + + providerKeeper.DeleteConsumerRemovalTime(ctx, CONSUMER_ID) + _, err = providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) + require.Error(t, err) +} + +// TestConsumersToBeLaunched tests `AppendConsumerToBeLaunched`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunched` +func TestConsumersToBeLaunched(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + spawnTime := time.Now() + err := providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId1", spawnTime) + require.NoError(t, err) + consumers, err := providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1"}, consumers.Ids) + + err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId2", spawnTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) + + err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId3", spawnTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) + + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId2", spawnTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) + + // also add consumer ids under a different spawn time and verify everything under the original spawn time is still there + spawnTimePlusOneHour := spawnTime.Add(time.Hour) + err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) + require.NoError(t, err) + require.Equal(t, []string{"consumerId4"}, consumers.Ids) + + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) + + // start removing all consumers from `spawnTime` + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId3", spawnTime) + require.NoError(t, err) + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId1", spawnTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Empty(t, consumers.Ids) + + // remove from `spawnTimePlusOneHour` + err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) + require.NoError(t, err) + require.Empty(t, consumers.Ids) + + // add another consumer for `spawnTime` + err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId5", spawnTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId5"}, consumers.Ids) +} + +// TestConsumersToBeRemoved tests `AppendConsumerToBeRemoved`, `GetConsumersToBeRemoved`, and `RemoveConsumerToBeRemoved` +func TestConsumersToBeRemoved(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + removalTime := time.Now() + err := providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId1", removalTime) + require.NoError(t, err) + consumers, err := providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1"}, consumers.Ids) + + err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId2", removalTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) + + err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId3", removalTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) + + err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId2", removalTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) + + // also add consumer ids under a different removal time and verify everything under the original removal time is still there + removalTimePlusOneHour := removalTime.Add(time.Hour) + err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId4", removalTimePlusOneHour) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTimePlusOneHour) + require.NoError(t, err) + require.Equal(t, []string{"consumerId4"}, consumers.Ids) + + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) + + // start removing all consumers from `removalTime` + err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId3", removalTime) + require.NoError(t, err) + err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId1", removalTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Empty(t, consumers.Ids) + + // remove from `removalTimePlusOneHour` + err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId4", removalTimePlusOneHour) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTimePlusOneHour) + require.NoError(t, err) + require.Empty(t, consumers.Ids) + + // add another consumer for `removalTime` + err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId5", removalTime) + require.NoError(t, err) + consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) + require.NoError(t, err) + require.Equal(t, []string{"consumerId5"}, consumers.Ids) +} diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index b1a5b36dcd..4fce69c17b 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -24,8 +24,14 @@ func (k Keeper) QueryConsumerGenesis(c context.Context, req *types.QueryConsumer return nil, status.Errorf(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD if req.ChainId == "" { return nil, status.Errorf(codes.InvalidArgument, "invalid request: chain id cannot be empty") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } gen, ok := k.GetConsumerGenesis(ctx, req.ChainId) @@ -146,6 +152,14 @@ func (k Keeper) QueryValidatorConsumerAddr(goCtx context.Context, req *types.Que ctx := sdk.UnwrapSDKContext(goCtx) +<<<<<<< HEAD +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) providerAddrTmp, err := sdk.ConsAddressFromBech32(req.ProviderAddress) if err != nil { return nil, err @@ -227,6 +241,7 @@ func (k Keeper) QueryProposedConsumerChainIDs(goCtx context.Context, req *types. return nil, status.Error(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD ctx := sdk.UnwrapSDKContext(goCtx) chains := k.GetAllProposedConsumerChainIDs(ctx) @@ -243,6 +258,11 @@ func (k Keeper) QueryAllPairsValConAddrByConsumerChainID(goCtx context.Context, if req.ChainId == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } // list of pairs valconsensus addr @@ -281,9 +301,15 @@ func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req * return nil, status.Error(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } optedInVals := []string{} @@ -308,9 +334,15 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC return nil, status.Error(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } ctx := sdk.UnwrapSDKContext(goCtx) @@ -413,9 +445,15 @@ func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req return nil, status.Error(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress) @@ -454,8 +492,20 @@ func (k Keeper) QueryOldestUnconfirmedVsc(goCtx context.Context, req *types.Quer return nil, status.Errorf(codes.InvalidArgument, "empty request") } +<<<<<<< HEAD if req.ChainId == "" { return nil, status.Errorf(codes.InvalidArgument, "invalid request: chain id cannot be empty") +======= + consumerId := req.ConsumerId + if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + ctx := sdk.UnwrapSDKContext(goCtx) + + chainId, err := k.GetConsumerChainId(ctx, consumerId) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "cannot retrieve chain id for consumer id: %s", consumerId) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } if _, consumerRegistered := k.GetConsumerClientId(ctx, req.ChainId); !consumerRegistered { diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index 93e88397c4..cb5c403fc1 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -6,6 +6,7 @@ import ( // Provider sentinel errors var ( +<<<<<<< HEAD ErrInvalidConsumerAdditionProposal = errorsmod.Register(ModuleName, 1, "invalid consumer addition proposal") ErrInvalidConsumerRemovalProp = errorsmod.Register(ModuleName, 2, "invalid consumer removal proposal") ErrUnknownConsumerChainId = errorsmod.Register(ModuleName, 3, "no consumer chain with this chain id") @@ -28,4 +29,39 @@ var ( ErrCannotOptOutFromTopN = errorsmod.Register(ModuleName, 20, "cannot opt out from a Top N chain") ErrNoUnconfirmedVSCPacket = errorsmod.Register(ModuleName, 21, "no unconfirmed vsc packet for this chain id") ErrInvalidConsumerModificationProposal = errorsmod.Register(ModuleName, 22, "invalid consumer modification proposal") +======= + ErrUnknownConsumerId = errorsmod.Register(ModuleName, 3, "no consumer chain with this consumer id") + ErrUnknownConsumerChannelId = errorsmod.Register(ModuleName, 4, "no consumer chain with this channel 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") + ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 16, "ccv channel is not built on correct client") + ErrCannotOptOutFromTopN = errorsmod.Register(ModuleName, 20, "cannot opt out from a Top N chain") + ErrNoUnbondingTime = errorsmod.Register(ModuleName, 23, "provider unbonding time not found") + ErrUnauthorized = errorsmod.Register(ModuleName, 25, "unauthorized") + ErrInvalidPhase = errorsmod.Register(ModuleName, 27, "cannot perform action in the current phase of consumer chain") + ErrInvalidConsumerMetadata = errorsmod.Register(ModuleName, 28, "invalid consumer metadata") + ErrInvalidPowerShapingParameters = errorsmod.Register(ModuleName, 29, "invalid power shaping parameters") + ErrInvalidConsumerInitializationParameters = errorsmod.Register(ModuleName, 30, "invalid consumer initialization parameters") + ErrCannotUpdateMinimumPowerInTopN = errorsmod.Register(ModuleName, 31, "cannot update minimum power in Top N") + ErrNoConsumerGenesis = errorsmod.Register(ModuleName, 33, "missing consumer genesis") + ErrInvalidConsumerGenesis = errorsmod.Register(ModuleName, 34, "invalid consumer genesis") + ErrNoConsumerId = errorsmod.Register(ModuleName, 35, "missing consumer id") + ErrAlreadyOptedIn = errorsmod.Register(ModuleName, 36, "already opted in to a chain with the same chain id") + ErrNoOwnerAddress = errorsmod.Register(ModuleName, 37, "missing owner address") + ErrInvalidNewOwnerAddress = errorsmod.Register(ModuleName, 38, "invalid new owner address") + ErrInvalidTransformToTopN = errorsmod.Register(ModuleName, 39, "invalid transform to Top N chain") + ErrInvalidTransformToOptIn = errorsmod.Register(ModuleName, 40, "invalid transform to Opt In chain") + ErrCannotCreateTopNChain = errorsmod.Register(ModuleName, 41, "cannot create Top N chain outside permissionlessly") + ErrInvalidRemovalTime = errorsmod.Register(ModuleName, 43, "invalid removal time") + ErrInvalidMsgCreateConsumer = errorsmod.Register(ModuleName, 44, "invalid create consumer message") + ErrInvalidMsgUpdateConsumer = errorsmod.Register(ModuleName, 45, "invalid update consumer message") + ErrInvalidMsgAssignConsumerKey = errorsmod.Register(ModuleName, 46, "invalid assign consumer key message") + ErrInvalidMsgSubmitConsumerMisbehaviour = errorsmod.Register(ModuleName, 47, "invalid submit consumer misbehaviour message") + ErrInvalidMsgSubmitConsumerDoubleVoting = errorsmod.Register(ModuleName, 48, "invalid submit consumer double voting message") + ErrInvalidMsgOptIn = errorsmod.Register(ModuleName, 49, "invalid opt in message") + ErrInvalidMsgOptOut = errorsmod.Register(ModuleName, 50, "invalid opt out message") + ErrInvalidMsgSetConsumerCommissionRate = errorsmod.Register(ModuleName, 51, "invalid set consumer commission rate message") + ErrInvalidMsgChangeRewardDenoms = errorsmod.Register(ModuleName, 52, "invalid change reward denoms message") +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index b80e608785..a8843ceee3 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -80,11 +80,17 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { if strings.TrimSpace(msg.ChainId) == "" { return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") } +<<<<<<< HEAD // It is possible to assign keys for consumer chains that are not yet approved. // This can only be done by a signing validator, but it is still sensible // to limit the chainID size to prevent abuse. if 128 < len(msg.ChainId) { return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") +======= + + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ConsumerId: %s", err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { @@ -94,11 +100,319 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { return ErrInvalidConsumerConsensusPubKey } if _, _, err := ParseConsumerKeyFromJson(msg.ConsumerKey); err != nil { +<<<<<<< HEAD return ErrInvalidConsumerConsensusPubKey +======= + return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ConsumerKey: %s", err.Error()) + } + + return nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg *MsgChangeRewardDenoms) ValidateBasic() error { + emptyDenomsToAdd := len(msg.DenomsToAdd) == 0 + emptyDenomsToRemove := len(msg.DenomsToRemove) == 0 + // Return error if both sets are empty or nil + if emptyDenomsToAdd && emptyDenomsToRemove { + return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "both DenomsToAdd and DenomsToRemove are empty") + } + + denomMap := map[string]struct{}{} + for _, denom := range msg.DenomsToAdd { + // validate the denom + if !sdk.NewCoin(denom, math.NewInt(1)).IsValid() { + return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "DenomsToAdd: invalid denom(%s)", denom) + } + denomMap[denom] = struct{}{} + } + for _, denom := range msg.DenomsToRemove { + // validate the denom + if !sdk.NewCoin(denom, math.NewInt(1)).IsValid() { + return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "DenomsToRemove: invalid denom(%s)", denom) + } + // denom cannot be in both sets + if _, found := denomMap[denom]; found { + return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, + "denom(%s) cannot be both added and removed", denom) + } + } + + return nil +} + +func NewMsgSubmitConsumerMisbehaviour( + consumerId string, + submitter sdk.AccAddress, + misbehaviour *ibctmtypes.Misbehaviour, +) (*MsgSubmitConsumerMisbehaviour, error) { + return &MsgSubmitConsumerMisbehaviour{ + Submitter: submitter.String(), + Misbehaviour: misbehaviour, + ConsumerId: consumerId, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerMisbehaviour, "ConsumerId: %s", err.Error()) + } + + if err := msg.Misbehaviour.ValidateBasic(); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerMisbehaviour, "Misbehaviour: %s", err.Error()) +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) + } + return nil +} + +<<<<<<< HEAD +======= +func NewMsgSubmitConsumerDoubleVoting( + consumerId string, + submitter sdk.AccAddress, + ev *tmtypes.DuplicateVoteEvidence, + header *ibctmtypes.Header, +) (*MsgSubmitConsumerDoubleVoting, error) { + return &MsgSubmitConsumerDoubleVoting{ + Submitter: submitter.String(), + DuplicateVoteEvidence: ev, + InfractionBlockHeader: header, + ConsumerId: consumerId, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { + if dve, err := cmttypes.DuplicateVoteEvidenceFromProto(msg.DuplicateVoteEvidence); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "DuplicateVoteEvidence: %s", err.Error()) + } else { + if err = dve.ValidateBasic(); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "DuplicateVoteEvidence: %s", err.Error()) + } + } + + if err := ValidateHeaderForConsumerDoubleVoting(msg.InfractionBlockHeader); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ValidateTendermintHeader: %s", err.Error()) + } + + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ConsumerId: %s", err.Error()) + } + + return nil +} + +// NewMsgOptIn creates a new NewMsgOptIn instance. +func NewMsgOptIn(consumerId string, providerValidatorAddress sdk.ValAddress, consumerConsensusPubKey, signer string) (*MsgOptIn, error) { + return &MsgOptIn{ + ConsumerId: consumerId, + ProviderAddr: providerValidatorAddress.String(), + ConsumerKey: consumerConsensusPubKey, + Signer: signer, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgOptIn) ValidateBasic() error { + if err := validateDeprecatedChainId(msg.ChainId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ChainId: %s", err.Error()) + } + + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ConsumerId: %s", err.Error()) + } + + if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ProviderAddr: %s", err.Error()) + } + + if msg.ConsumerKey != "" { + if _, _, err := ParseConsumerKeyFromJson(msg.ConsumerKey); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ConsumerKey: %s", err.Error()) + } + } + return nil +} + +// NewMsgOptOut creates a new NewMsgOptIn instance. +func NewMsgOptOut(consumerId string, providerValidatorAddress sdk.ValAddress, signer string) (*MsgOptOut, error) { + return &MsgOptOut{ + ConsumerId: consumerId, + ProviderAddr: providerValidatorAddress.String(), + Signer: signer, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgOptOut) ValidateBasic() error { + if err := validateDeprecatedChainId(msg.ChainId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ChainId: %s", err.Error()) + } + + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ConsumerId: %s", err.Error()) + } + + if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ProviderAddr: %s", err.Error()) + } + + return nil +} + +// NewMsgSetConsumerCommissionRate creates a new MsgSetConsumerCommissionRate msg instance. +func NewMsgSetConsumerCommissionRate( + consumerId string, + commission math.LegacyDec, + providerValidatorAddress sdk.ValAddress, + signer string, +) *MsgSetConsumerCommissionRate { + return &MsgSetConsumerCommissionRate{ + ConsumerId: consumerId, + Rate: commission, + ProviderAddr: providerValidatorAddress.String(), + Signer: signer, + } +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgSetConsumerCommissionRate) ValidateBasic() error { + if err := validateDeprecatedChainId(msg.ChainId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ChainId: %s", err.Error()) + } + + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ConsumerId: %s", err.Error()) } + + if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ProviderAddr: %s", err.Error()) + } + + if msg.Rate.IsNegative() || msg.Rate.GT(math.LegacyOneDec()) { + return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "consumer commission rate should be in the range [0, 1]") + } + return nil } +// NewMsgCreateConsumer creates a new MsgCreateConsumer instance +func NewMsgCreateConsumer(submitter, chainId string, metadata ConsumerMetadata, + initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters, +) (*MsgCreateConsumer, error) { + return &MsgCreateConsumer{ + Submitter: submitter, + ChainId: chainId, + Metadata: metadata, + InitializationParameters: initializationParameters, + PowerShapingParameters: powerShapingParameters, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgCreateConsumer) ValidateBasic() error { + if err := ValidateStringField("ChainId", msg.ChainId, cmttypes.MaxChainIDLen); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "ChainId: %s", err.Error()) + } + + // With permissionless ICS, we can have multiple consumer chains with the exact same chain id. + // However, as we already have the Neutron and Stride Top N chains running, as a first step we would like to + // prevent permissionless chains from re-using the chain ids of Neutron and Stride. Note that this is just a + // preliminary measure that will be removed later on as part of: + // TODO (#2242): find a better way of ignoring past misbehaviors + if msg.ChainId == "neutron-1" || msg.ChainId == "stride-1" { + return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, + "cannot reuse chain ids of existing Neutron and Stride Top N consumer chains") + } + + if err := ValidateConsumerMetadata(msg.Metadata); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "Metadata: %s", err.Error()) + } + + if msg.InitializationParameters != nil { + if err := ValidateInitializationParameters(*msg.InitializationParameters); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "InitializationParameters: %s", err.Error()) + } + } + + if msg.PowerShapingParameters != nil { + if msg.PowerShapingParameters.Top_N != 0 { + return fmt.Errorf("cannot create a Top N chain through `MsgCreateConsumer`; " + + "first create the chain and then use `MsgUpdateConsumer` to make the chain Top N") + } + if err := ValidatePowerShapingParameters(*msg.PowerShapingParameters); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "PowerShapingParameters: %s", err.Error()) + } + } + + return nil +} + +// NewMsgUpdateConsumer creates a new MsgUpdateConsumer instance +func NewMsgUpdateConsumer(owner, consumerId, ownerAddress string, metadata *ConsumerMetadata, + initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters, +) (*MsgUpdateConsumer, error) { + return &MsgUpdateConsumer{ + Owner: owner, + ConsumerId: consumerId, + NewOwnerAddress: ownerAddress, + Metadata: metadata, + InitializationParameters: initializationParameters, + PowerShapingParameters: powerShapingParameters, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgUpdateConsumer) ValidateBasic() error { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "ConsumerId: %s", err.Error()) + } + + // Note that NewOwnerAddress is validated when handling the message in UpdateConsumer + + if msg.Metadata != nil { + if err := ValidateConsumerMetadata(*msg.Metadata); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "Metadata: %s", err.Error()) + } + } + + if msg.InitializationParameters != nil { + if err := ValidateInitializationParameters(*msg.InitializationParameters); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "InitializationParameters: %s", err.Error()) + } + } + + if msg.PowerShapingParameters != nil { + if err := ValidatePowerShapingParameters(*msg.PowerShapingParameters); err != nil { + return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "PowerShapingParameters: %s", err.Error()) + } + } + + return nil +} + +// NewMsgRemoveConsumer creates a new MsgRemoveConsumer instance +func NewMsgRemoveConsumer(owner, consumerId string) (*MsgRemoveConsumer, error) { + return &MsgRemoveConsumer{ + Owner: owner, + ConsumerId: consumerId, + }, nil +} + +// ValidateBasic implements the sdk.HasValidateBasic interface. +func (msg MsgRemoveConsumer) ValidateBasic() error { + if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { + return err + } + return nil +} + +// +// Validation methods +// + +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // ParseConsumerKeyFromJson parses the consumer key from a JSON string, // this replaces deserializing a protobuf any. func ParseConsumerKeyFromJson(jsonStr string) (pkType, key string, err error) { @@ -194,6 +508,7 @@ func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { return nil } +<<<<<<< HEAD // Type implements the sdk.Msg interface. func (msg MsgSubmitConsumerDoubleVoting) GetSignBytes() []byte { bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) @@ -217,6 +532,18 @@ func NewMsgOptIn(chainID string, providerValidatorAddress sdk.ValAddress, consum ProviderAddr: providerValidatorAddress.String(), ConsumerKey: consumerConsensusPubKey, }, nil +======= +// ValidateStringField validates that a string `field` satisfies the following properties: +// - is not empty +// - has at most `maxLength` characters +func ValidateStringField(nameOfTheField, field string, maxLength int) error { + if strings.TrimSpace(field) == "" { + return fmt.Errorf("%s cannot be empty", nameOfTheField) + } else if len(field) > maxLength { + return fmt.Errorf("%s is too long; got: %d, max: %d", nameOfTheField, len(field), maxLength) + } + return nil +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } // Route implements the sdk.Msg interface. diff --git a/x/ccv/provider/types/msg_test.go b/x/ccv/provider/types/msg_test.go new file mode 100644 index 0000000000..6867343b65 --- /dev/null +++ b/x/ccv/provider/types/msg_test.go @@ -0,0 +1,649 @@ +package types_test + +import ( + "testing" + "time" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cryptoutil "github.com/cosmos/interchain-security/v6/testutil/crypto" + "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" +) + +func TestValidateStringField(t *testing.T) { + testCases := []struct { + name string + field string + maxLength int + valid bool + }{ + { + name: "invalid: empty", + field: "", + maxLength: 5, + valid: false, + }, + { + name: "invalid: too long", + field: "this field is too long", + maxLength: 5, + valid: false, + }, + { + name: "valid", + field: "valid", + maxLength: 5, + valid: true, + }, + } + + for _, tc := range testCases { + err := types.ValidateStringField(tc.name, tc.field, tc.maxLength) + if tc.valid { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestTruncateString(t *testing.T) { + testCases := []struct { + str string + maxLength int + expStr string + }{ + {"drink", 3, "dri"}, + {"drink", 6, "drink"}, + {"drink", 0, ""}, + {"drink", -1, ""}, + {"drink", 100, "drink"}, + {"pub", 100, "pub"}, + {"こんにちは", 3, "こんに"}, + } + + for _, tc := range testCases { + truncated := types.TruncateString(tc.str, tc.maxLength) + require.Equal(t, tc.expStr, truncated) + } +} + +func TestValidateConsumerMetadata(t *testing.T) { + generateLongString := func(length int) string { + result := make([]byte, length) + for i := range result { + result[i] = byte('a') + } + return string(result) + } + + testCases := []struct { + name string + metadata types.ConsumerMetadata + valid bool + }{ + { + name: "valid", + metadata: types.ConsumerMetadata{ + Name: "name", + Description: "description", + Metadata: "metadata", + }, + valid: true, + }, + { + name: "valid with long strings", + metadata: types.ConsumerMetadata{ + Name: generateLongString(types.MaxNameLength), + Description: generateLongString(types.MaxDescriptionLength), + Metadata: generateLongString(types.MaxMetadataLength), + }, + valid: true, + }, + { + name: "invalid name", + metadata: types.ConsumerMetadata{ + Name: generateLongString(types.MaxNameLength + 1), + Description: "description", + Metadata: "metadata", + }, + valid: false, + }, + { + name: "invalid description", + metadata: types.ConsumerMetadata{ + Name: "name", + Description: generateLongString(types.MaxDescriptionLength + 1), + Metadata: "metadata", + }, + valid: false, + }, + { + name: "invalid metadata", + metadata: types.ConsumerMetadata{ + Name: "name", + Description: "description", + Metadata: generateLongString(types.MaxMetadataLength + 1), + }, + valid: false, + }, + } + + for _, tc := range testCases { + err := types.ValidateConsumerMetadata(tc.metadata) + if tc.valid { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestValidateInitializationParameters(t *testing.T) { + now := time.Now().UTC() + coolStr := "Cosmos Hub is the best place to launch a chain. Interchain Security is awesome." + tooLongHash := []byte(coolStr) + + testCases := []struct { + name string + params types.ConsumerInitializationParameters + valid bool + }{ + { + name: "valid", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: true, + }, + { + name: "invalid - zero height", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.ZeroHeight(), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid - hash too long", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: tooLongHash, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid - zero spawn time", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: time.Time{}, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: true, + }, + { + name: "invalid - zero duration", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: 0, + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid -- ConsumerRedistributionFraction > 1", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "1.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid -- ConsumerRedistributionFraction wrong format", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: coolStr, + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid - BlocksPerDistributionTransmission zero", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 0, + HistoricalEntries: 10000, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid - HistoricalEntries zero", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 0, + DistributionTransmissionChannel: "", + }, + valid: false, + }, + { + name: "invalid - DistributionTransmissionChannel too long", + params: types.ConsumerInitializationParameters{ + InitialHeight: clienttypes.NewHeight(3, 4), + GenesisHash: []byte{0x01}, + BinaryHash: []byte{0x01}, + SpawnTime: now, + UnbondingPeriod: time.Duration(100000000000), + CcvTimeoutPeriod: time.Duration(100000000000), + TransferTimeoutPeriod: time.Duration(100000000000), + ConsumerRedistributionFraction: "0.75", + BlocksPerDistributionTransmission: 10, + HistoricalEntries: 10000, + DistributionTransmissionChannel: coolStr, + }, + valid: false, + }, + } + + for _, tc := range testCases { + err := types.ValidateInitializationParameters(tc.params) + if tc.valid { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestValidateConsAddressList(t *testing.T) { + consAddr1 := "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" + consAddr2 := "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39" + invalidConsAddr := "cosmosvalcons1nx7n5uh0ztxsynn4sje6ey" + + testCases := []struct { + name string + list []string + maxLength int + valid bool + }{ + { + name: "valid - empty list", + list: []string{}, + maxLength: 10, + valid: true, + }, + { + name: "valid - non-empty list", + list: []string{consAddr1, consAddr2}, + maxLength: 10, + valid: true, + }, + { + name: "invalid - address with wrong format", + list: []string{invalidConsAddr}, + maxLength: 10, + valid: false, + }, + { + name: "invalid - empty address", + list: []string{""}, + maxLength: 10, + valid: false, + }, + { + name: "invalid - list length", + list: []string{consAddr1, consAddr2}, + maxLength: 1, + valid: false, + }, + } + + for _, tc := range testCases { + err := types.ValidateConsAddressList(tc.list, tc.maxLength) + if tc.valid { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestValidateByteSlice(t *testing.T) { + testCases := []struct { + name string + slice []byte + maxLength int + valid bool + }{ + { + name: "valid: empty", + slice: []byte{}, + maxLength: 5, + valid: true, + }, + { + name: "invalid: too long", + slice: []byte{0x01, 0x02}, + maxLength: 1, + valid: false, + }, + { + name: "valid", + slice: []byte{0x01, 0x02}, + maxLength: 5, + valid: true, + }, + } + + for _, tc := range testCases { + err := types.ValidateByteSlice(tc.slice, tc.maxLength) + if tc.valid { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestMsgCreateConsumerValidateBasic(t *testing.T) { + testCases := []struct { + name string + chainId string + powerShapingParameters *types.PowerShapingParameters + expPass bool + }{ + { + "empty chain id", + "", + nil, // no power-shaping parameters + false, + }, + { + "empty chain id after trimming", + " ", + nil, // no power-shaping parameters + false, + }, + { + "neutron chain id that cannot be reused", + "neutron-1", + nil, // no power-shaping parameters + false, + }, + { + "stride chain id that cannot be reused", + "stride-1", + nil, // no power-shaping parameters + false, + }, + { + "valid chain id", + "somechain-1", + nil, // no power-shaping parameters + true, + }, + { + "valid chain id and invalid power-shaping parameters", + "somechain-1", + &types.PowerShapingParameters{Top_N: 51}, // TopN cannot be > 0 in MsgCreateConsumer + false, + }, + } + + for _, tc := range testCases { + validConsumerMetadata := types.ConsumerMetadata{Name: "name", Description: "description", Metadata: "metadata"} + msg, err := types.NewMsgCreateConsumer("submitter", tc.chainId, validConsumerMetadata, nil, tc.powerShapingParameters) + require.NoError(t, err) + err = msg.ValidateBasic() + if tc.expPass { + require.NoError(t, err, "valid case: %s should not return error. got %w", tc.name, err) + } else { + require.Error(t, err, "invalid case: '%s' must return error but got none", tc.name) + } + } +} + +func TestMsgUpdateConsumerValidateBasic(t *testing.T) { + consAddr1 := "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" + consAddr2 := "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39" + consAddr3 := "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe" + + testCases := []struct { + name string + powerShapingParameters types.PowerShapingParameters + expPass bool + }{ + { + "success", + types.PowerShapingParameters{ + Top_N: 50, + ValidatorsPowerCap: 100, + ValidatorSetCap: 34, + Allowlist: []string{consAddr1}, + Denylist: nil, + MinStake: 0, + AllowInactiveVals: false, + }, + true, + }, + { + "top N is invalid", + types.PowerShapingParameters{ + Top_N: 10, + ValidatorsPowerCap: 0, + ValidatorSetCap: 0, + Allowlist: nil, + Denylist: nil, + }, + false, + }, + { + "validators power cap is invalid", + types.PowerShapingParameters{ + Top_N: 50, + ValidatorsPowerCap: 101, + ValidatorSetCap: 0, + Allowlist: nil, + Denylist: nil, + MinStake: 0, + AllowInactiveVals: false, + }, + false, + }, + { + "valid proposal", + types.PowerShapingParameters{ + Top_N: 54, + ValidatorsPowerCap: 92, + ValidatorSetCap: 0, + Allowlist: []string{consAddr1}, + Denylist: []string{consAddr2, consAddr3}, + MinStake: 0, + AllowInactiveVals: false, + }, + true, + }, + } + + for _, tc := range testCases { + // TODO (PERMISSIONLESS) add more tests + msg, _ := types.NewMsgUpdateConsumer("", "0", "cosmos1p3ucd3ptpw902fluyjzhq3ffgq4ntddac9sa3s", nil, nil, &tc.powerShapingParameters) + err := msg.ValidateBasic() + if tc.expPass { + require.NoError(t, err, "valid case: %s should not return error. got %w", tc.name, err) + } else { + require.Error(t, err, "invalid case: '%s' must return error but got none", tc.name) + } + } +} + +func TestMsgAssignConsumerKeyValidateBasic(t *testing.T) { + cId1 := cryptoutil.NewCryptoIdentityFromIntSeed(35443543534) + cId2 := cryptoutil.NewCryptoIdentityFromIntSeed(65465464564) + + valOpAddr1 := cId1.SDKValOpAddress() + acc1 := sdk.AccAddress(valOpAddr1.Bytes()).String() + acc2 := sdk.AccAddress(cId2.SDKValOpAddress().Bytes()).String() + + longChainId := "abcdefghijklmnopqrstuvwxyz" + for i := 0; i < 3; i++ { + longChainId += longChainId + } + + testCases := []struct { + name string + chainId string + providerAddr string + signer string + consumerKey string + consumerId string + expErr bool + }{ + { + name: "invalid: chainId non-empty", + chainId: "chainId", + expErr: true, + }, + { + name: "invalid: consumerId empty", + consumerId: "", + expErr: true, + }, + { + name: "invalid: consumerId is not a number", + consumerId: "consumerId", + expErr: true, + }, + { + name: "invalid: provider address is empty", + consumerId: "1", + expErr: true, + }, + { + name: "invalid: provider address is invalid", + consumerId: "1", + providerAddr: "some address", + expErr: true, + }, + { + name: "invalid: provider address != submitter address", + consumerId: "1", + providerAddr: valOpAddr1.String(), + signer: acc2, + expErr: true, + }, + { + name: "invalid: consumer pubkey empty", + consumerId: "1", + providerAddr: valOpAddr1.String(), + signer: acc1, + expErr: true, + }, + { + name: "valid", + consumerId: "1", + providerAddr: valOpAddr1.String(), + signer: acc1, + consumerKey: "{\"@type\": \"/cosmos.crypto.ed25519.PubKey\", \"key\": \"e3BehnEIlGUAnJYn9V8gBXuMh4tXO8xxlxyXD1APGyk=\"}", + expErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + msg := types.MsgAssignConsumerKey{ + ChainId: tc.chainId, + ConsumerKey: tc.consumerKey, + ProviderAddr: tc.providerAddr, + Signer: tc.signer, + ConsumerId: tc.consumerId, + } + + err := msg.ValidateBasic() + if tc.expErr { + require.Error(t, err, tc.name) + } else { + require.NoError(t, err, tc.name) + } + }) + } +} diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index e492984e48..d155c17c1a 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -22,4 +22,10 @@ var ( ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 14, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 15, "consumer chain not found") ErrInvalidDoubleVotingEvidence = errorsmod.Register(ModuleName, 16, "invalid consumer double voting evidence") +<<<<<<< HEAD +======= + 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") +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) diff --git a/x/ccv/types/params.go b/x/ccv/types/params.go index 5759ce28f9..ee2446845f 100644 --- a/x/ccv/types/params.go +++ b/x/ccv/types/params.go @@ -68,6 +68,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, @@ -84,6 +85,7 @@ func NewParams(enabled bool, blocksPerDistributionTransmission int64, RewardDenoms: rewardDenoms, ProviderRewardDenoms: providerRewardDenoms, RetryDelayPeriod: retryDelayPeriod, + ConsumerId: consumerId, } } @@ -104,6 +106,7 @@ func DefaultParams() ConsumerParams { rewardDenoms, provideRewardDenoms, DefaultRetryDelayPeriod, + "0", ) } @@ -145,6 +148,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 92d7105d41..32b3c515b2 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,6 +350,7 @@ func init() { } var fileDescriptor_d0a8be0efc64dfbc = []byte{ +<<<<<<< HEAD // 817 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0x41, 0x6f, 0xe4, 0x34, 0x14, 0x6e, 0x3a, 0x4b, 0x77, 0xea, 0x69, 0xb7, 0x8b, 0x29, 0x4b, 0xe8, 0x4a, 0xd3, 0x6c, 0xe1, @@ -393,6 +403,61 @@ var fileDescriptor_d0a8be0efc64dfbc = []byte{ 0x7d, 0xb7, 0x3f, 0x17, 0x36, 0x2d, 0x93, 0x90, 0xa9, 0x3c, 0x62, 0xca, 0xe4, 0xca, 0x44, 0x37, 0x67, 0xf1, 0xf4, 0xfa, 0xe5, 0xa8, 0xf6, 0xa3, 0x1f, 0xdd, 0xf3, 0xe1, 0x06, 0x7f, 0xb2, 0xe2, 0x2e, 0xd5, 0xa7, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x67, 0x6a, 0xf6, 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, +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) 0x00, } @@ -416,6 +481,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 +765,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 +1228,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 566737c0b9..312b877024 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -2,9 +2,18 @@ package types import ( fmt "fmt" + "strconv" + "strings" "time" +<<<<<<< HEAD ibchost "github.com/cosmos/ibc-go/v7/modules/core/24-host" +======= + ibchost "github.com/cosmos/ibc-go/v8/modules/core/24-host" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" +>>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) sdktypes "github.com/cosmos/cosmos-sdk/types" ) @@ -112,3 +121,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 ab6692912e..a704fa3d2c 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) +} From f875cf4da0a6d5e62359030440ff768ed5a448bd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 27 Sep 2024 18:50:24 +0200 Subject: [PATCH 8/9] Revert "feat!: add memo to IBC transfers of ICS rewards (#2290)" This reverts commit 9a1b02f62b54dea5a24ea6e106cd72af9f89f191. --- .../features/2290-add-memo-to-ICS-rewards.md | 4 - .../2290-add-memo-to-ICS-rewards.md | 4 - UPGRADING.md | 178 --- .../ccv/v1/shared_consumer.proto | 4 - tests/e2e/action_rapid_test.go | 2 +- tests/e2e/actions.go | 333 +----- tests/e2e/state.go | 122 +- tests/e2e/state_rapid_test.go | 4 +- tests/e2e/steps_democracy.go | 136 +-- tests/e2e/steps_inactive_vals.go | 973 --------------- tests/e2e/test_driver.go | 18 - tests/e2e/testlib/types.go | 366 ------ tests/e2e/v4/state.go | 648 ---------- tests/mbt/driver/setup.go | 1 - x/ccv/consumer/keeper/distribution.go | 7 +- x/ccv/consumer/keeper/params.go | 5 - x/ccv/consumer/keeper/params_test.go | 3 +- x/ccv/consumer/migrations/v3/legacy_params.go | 108 -- x/ccv/consumer/types/genesis_test.go | 3 - x/ccv/consumer/types/params_test.go | 38 +- x/ccv/provider/ibc_middleware.go | 69 -- x/ccv/provider/keeper/consumer_lifecycle.go | 663 ----------- .../keeper/consumer_lifecycle_test.go | 1046 ----------------- x/ccv/provider/keeper/grpc_query.go | 50 - x/ccv/provider/types/errors.go | 36 - x/ccv/provider/types/msg.go | 327 ------ x/ccv/provider/types/msg_test.go | 649 ---------- x/ccv/types/errors.go | 6 - x/ccv/types/params.go | 6 - x/ccv/types/shared_consumer.pb.go | 108 -- x/ccv/types/shared_params.go | 24 - x/ccv/types/shared_params_test.go | 23 - x/ccv/types/wire.go | 52 - x/ccv/types/wire_test.go | 14 - 34 files changed, 32 insertions(+), 5998 deletions(-) delete mode 100644 .changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md delete mode 100644 .changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md delete mode 100644 tests/e2e/steps_inactive_vals.go delete mode 100644 tests/e2e/testlib/types.go delete mode 100644 tests/e2e/v4/state.go delete mode 100644 x/ccv/consumer/migrations/v3/legacy_params.go delete mode 100644 x/ccv/provider/keeper/consumer_lifecycle.go delete mode 100644 x/ccv/provider/keeper/consumer_lifecycle_test.go delete mode 100644 x/ccv/provider/types/msg_test.go delete mode 100644 x/ccv/types/shared_params_test.go diff --git a/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md b/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md deleted file mode 100644 index 421967ddab..0000000000 --- a/.changelog/unreleased/features/2290-add-memo-to-ICS-rewards.md +++ /dev/null @@ -1,4 +0,0 @@ -- `[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 deleted file mode 100644 index 421967ddab..0000000000 --- a/.changelog/unreleased/state-breaking/2290-add-memo-to-ICS-rewards.md +++ /dev/null @@ -1,4 +0,0 @@ -- `[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 742a4550f8..235010079d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,183 +1,5 @@ # Upgrading Replicated Security -<<<<<<< HEAD -======= -## 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: - -```go -// InitializeMaxProviderConsensusParam initializes the MaxProviderConsensusValidators parameter. -// It is set to 180, which is the current number of validators participating in consensus on the Cosmos Hub. -// This parameter will be used to govern the number of validators participating in consensus on the Cosmos Hub, -// and takes over this role from the MaxValidators parameter in the staking module. -func InitializeMaxProviderConsensusParam(ctx sdk.Context, providerKeeper providerkeeper.Keeper) { - params := providerKeeper.GetParams(ctx) - params.MaxProviderConsensusValidators = NewMaxProviderConsensusValidators - providerKeeper.SetParams(ctx, params) -} -``` - -```go -// SetMaxValidators sets the MaxValidators parameter in the staking module to 200, -// which is the current number of 180 plus 20. -// This is done in concert with the introduction of the inactive-validators feature -// in Interchain Security, after which the number of validators -// participating in consensus on the Cosmos Hub will be governed by the -// MaxProviderConsensusValidators parameter in the provider module. -func SetMaxValidators(ctx sdk.Context, stakingKeeper stakingkeeper.Keeper) error { - params, err := stakingKeeper.GetParams(ctx) - if err != nil { - return err - } - params.MaxValidators = NewMaxValidators - return stakingKeeper.SetParams(ctx, params) -} -``` - -```go -// InitializeLastProviderConsensusValidatorSet initializes the last provider consensus validator set -// by setting it to the first 180 validators from the current validator set of the staking module. -func InitializeLastProviderConsensusValidatorSet(ctx sdk.Context, providerKeeper providerkeeper.Keeper, stakingKeeper stakingkeeper.Keeper) error { - vals, err := stakingKeeper.GetBondedValidatorsByPower(ctx) - if err != nil { - return err - } - // cut the validator set to the first 180 validators - if len(vals) > NewMaxProviderConsensusValidators { - vals = vals[:NewMaxProviderConsensusValidators] - } - // create consensus validators for the staking validators - lastValidators := []providertypes.ConsensusValidator{} - for _, val := range vals { - consensusVal, err := providerKeeper.CreateProviderConsensusValidator(ctx, val) - if err != nil { - return err - } - - lastValidators = append(lastValidators, consensusVal) - } - return providerKeeper.SetLastProviderConsensusValSet(ctx, lastValidators) -} -``` - -```go -// SetICSConsumerMetadata sets the metadata for launched consumer chains -func SetICSConsumerMetadata(ctx sdk.Context, providerKeeper providerkeeper.Keeper) error { - for _, consumerID := range providerKeeper.GetAllActiveConsumerIds(ctx) { - phase := providerKeeper.GetConsumerPhase(ctx, consumerID) - if phase != providertypes.CONSUMER_PHASE_LAUNCHED { - continue - } - chainID, err := providerKeeper.GetConsumerChainId(ctx, consumerID) - if err != nil { - ctx.Logger().Error( - fmt.Sprintf("cannot get chain ID for consumer chain, consumerID(%s)", consumerID), - ) - continue - } - - // example of setting the metadata for Stride - if chainID == "stride-1" { - metadata := providertypes.ConsumerMetadata{ - Name: "Stride", - Description: "Description", - Metadata: "Metadata", - } - err = providerKeeper.SetConsumerMetadata(ctx, consumerID, metadata) - if err != nil { - ctx.Logger().Error( - fmt.Sprintf("cannot set consumer metadata, consumerID(%s), chainID(%s): %s", consumerID, chainID, err.Error()), - ) - continue - } - } - } -} -``` - -```go -// MigrateICSProposals migrates deprecated proposals -func MigrateICSProposals(ctx sdk.Context, msgServer providertypes.MsgServer, providerKeeper providerkeeper.Keeper, govKeeper govkeeper.Keeper) error { - proposals := []govtypesv1.Proposal{} - err := govKeeper.Proposals.Walk(ctx, nil, func(key uint64, proposal govtypesv1.Proposal) (stop bool, err error) { - proposals = append(proposals, proposal) - return false, nil // go through the entire collection - }) - if err != nil { - return errorsmod.Wrapf(err, "iterating through proposals") - } - for _, proposal := range proposals { - err := MigrateICSLegacyProposal(ctx, msgServer, providerKeeper, govKeeper, proposal) - if err != nil { - return errorsmod.Wrapf(err, "migrating legacy proposal %d", proposal.Id) - } - - err = MigrateICSProposal(ctx, msgServer, providerKeeper, govKeeper, proposal) - if err != nil { - return errorsmod.Wrapf(err, "migrating proposal %d", proposal.Id) - } - } - return nil -} - -// MigrateICSLegacyProposal migrates the following proposals -// - ConsumerAdditionProposal -// - ConsumerRemovalProposal -// - ConsumerModificationProposal -// - ChangeRewardDenomsProposal - -// MigrateICSProposal migrates the following proposals -// - MsgConsumerAddition -// - MsgConsumerRemoval -// - MsgConsumerModification -``` - -For an example, see the [Gaia v20 upgrade handler](https://github.com/cosmos/gaia/blob/e4656093955578b2efa6e8c2ea8dd8823008bba3/app/upgrades/v20/upgrades.go#L43). - -### Consumer - -Upgrading the consumer from `v5.0.0` or `v5.2.0` will not require state migration. - -## [v5.1.x](https://github.com/cosmos/interchain-security/releases/tag/v5.1.0) - -### Provider - -***Note that providers using v5.0.0 cannot upgrade to v5.1.0 cleanly*** - -Providers using versions `v4.0.x`, `v4.1.x`, `v4.2.x`, `v4.3.x` and `v4.4.x` can upgrade to `v5.1.0`. - -Upgrading from `v4.x` to `v5.1.0` will upgrade the provider `consensus version` to 7. - -Upgrade code will be executed automatically during the upgrade procedure. - -### Consumer - -Upgrading the consumer from `v5.0.0` to `v5.1.0` will not require state migration. - ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) This guide provides instructions for upgrading to specific versions of Replicated Security. ## [v4.4.x](https://github.com/cosmos/interchain-security/releases/tag/v4.4.0) diff --git a/proto/interchain_security/ccv/v1/shared_consumer.proto b/proto/interchain_security/ccv/v1/shared_consumer.proto index 36e189e1c0..6c8cccfe86 100644 --- a/proto/interchain_security/ccv/v1/shared_consumer.proto +++ b/proto/interchain_security/ccv/v1/shared_consumer.proto @@ -76,10 +76,6 @@ 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 a61c8004bc..366f7f61c0 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -101,7 +101,7 @@ func CreateSubmitChangeRewardDenomsProposalActionGen() *rapid.Generator[SubmitCh return SubmitChangeRewardDenomsProposalAction{ From: GetValidatorIDGen().Draw(t, "From"), Deposit: rapid.Uint().Draw(t, "Deposit"), - Denoms: rapid.SliceOf(rapid.String()).Draw(t, "Denoms"), + Denom: rapid.String().Draw(t, "Denom"), } }) } diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index f571fbcc59..9628338019 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -267,153 +267,7 @@ type SubmitConsumerAdditionProposalAction struct { Denylist []string } -<<<<<<< HEAD func (tr TestConfig) submitConsumerAdditionProposal( -======= -func (tr Chain) UpdateConsumer(providerChain ChainID, validator ValidatorID, update types.MsgUpdateConsumer, verbose bool) { - content, err := json.Marshal(update) - if err != nil { - log.Fatal("failed marshalling MsgUpdateConsumer: ", err.Error()) - } - jsonFile := "/update-consumer.json" - bz, err := tr.target.ExecCommand( - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // Send consumer chain update - cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[providerChain].BinaryName, - "tx", "provider", "update-consumer", jsonFile, - `--from`, `validator`+fmt.Sprint(validator), - `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), - `--home`, tr.getValidatorHome(providerChain, validator), - `--gas`, `900000`, - `--node`, tr.getValidatorNode(providerChain, validator), - `--keyring-backend`, `test`, - "--output", "json", - `-y`, - ) - - bz, err = cmd.CombinedOutput() - if err != nil { - fmt.Println("command failed: ", cmd) - log.Fatalf("update consumer failed error: %s, output: %s", err, string(bz)) - } - - // Check transaction - txResponse := &TxResponse{} - err = json.Unmarshal(bz, txResponse) - if err != nil { - log.Fatalf("unmarshalling tx response on update-consumer: %s, json: %s", err.Error(), string(bz)) - } - - if txResponse.Code != 0 { - log.Fatalf("sending update-consumer transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) - } - - if verbose { - fmt.Println("running 'update-consumer' returned: ", txResponse) - } - - tr.waitBlocks(providerChain, 2, 10*time.Second) -} - -// CreateConsumer creates a consumer chain and returns its consumer-id -func (tr Chain) CreateConsumer(providerChain, consumerChain ChainID, validator ValidatorID, metadata types.ConsumerMetadata, initParams *types.ConsumerInitializationParameters, powerShapingParams *types.PowerShapingParameters) ConsumerID { - - msg := types.MsgCreateConsumer{ - ChainId: string(consumerChain), - Metadata: metadata, - InitializationParameters: initParams, - PowerShapingParameters: powerShapingParams, - } - - content, err := json.Marshal(msg) - if err != nil { - log.Fatalf("failed marshalling MsgCreateConsumer: %s", err.Error()) - } - jsonFile := "/create-consumer.json" - bz, err := tr.target.ExecCommand( - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, content, jsonFile), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // Send consumer chain creation - cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[providerChain].BinaryName, - "tx", "provider", "create-consumer", jsonFile, - `--from`, `validator`+fmt.Sprint(validator), - `--chain-id`, string(tr.testConfig.chainConfigs[providerChain].ChainId), - `--home`, tr.getValidatorHome(providerChain, validator), - `--gas`, `900000`, - `--node`, tr.getValidatorNode(providerChain, validator), - `--keyring-backend`, `test`, - "--output", "json", - `-y`, - ) - - bz, err = cmd.CombinedOutput() - if err != nil { - log.Fatal("create consumer failed ", "error: ", err, "output: ", string(bz)) - } - - txResponse := &TxResponse{} - err = json.Unmarshal(bz, txResponse) - if err != nil { - log.Fatalf("unmarshalling tx response on create-consumer: %s, json: %s", err.Error(), string(bz)) - } - - if txResponse.Code != 0 { - log.Fatalf("sending transaction failed with error code %d, Log:'%s'", txResponse.Code, txResponse.RawLog) - } - - // TODO: introduce waitForTx (see issue #2198) - tr.waitBlocks(providerChain, 2, 10*time.Second) - - // Get Consumer ID from transaction - cmd = tr.target.ExecCommand( - tr.testConfig.chainConfigs[providerChain].BinaryName, - "query", "tx", txResponse.TxHash, - `--node`, tr.getValidatorNode(providerChain, validator), - "--output", "json", - ) - bz, err = cmd.CombinedOutput() - if err != nil { - log.Fatalf("not able to query tx containing creation-consumer: tx: %s, err: %s, out: %s", - txResponse.TxHash, err.Error(), string(bz)) - } - - err = json.Unmarshal(bz, txResponse) - if err != nil { - log.Fatalf("unmarshalling tx containing create-consumer: %s, json: %s", err.Error(), string(bz)) - } - - consumerId := "" - for _, event := range txResponse.Events { - if event.Type != "create_consumer" { - continue - } - attr, exists := event.GetAttribute("consumer_id") - if !exists { - log.Fatalf("no event with consumer_id found in tx content of create_consumer: %v", event) - } - consumerId = attr.Value - } - if consumerId == "" { - log.Fatalf("no consumer-id found in consumer creation transaction events for chain '%s'. events: %v", consumerChain, txResponse.Events) - } - - return ConsumerID(consumerId) -} - -// submitConsumerAdditionProposal initializes a consumer chain and submits a governance proposal -func (tr Chain) submitConsumerAdditionProposal( ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) action SubmitConsumerAdditionProposalAction, target ExecutionTarget, verbose bool, @@ -2133,85 +1987,20 @@ func (tr TestConfig) registerRepresentative( } type SubmitChangeRewardDenomsProposalAction struct { -<<<<<<< HEAD Denom string -======= - Chain ChainID - Denoms []string ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) Deposit uint From ValidatorID } -<<<<<<< HEAD func (tr TestConfig) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, target ExecutionTarget, verbose bool) { providerChain := tr.chainConfigs[ChainID("provi")] -======= -func (tr Chain) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { - changeRewMsg := types.MsgChangeRewardDenoms{ - DenomsToAdd: action.Denoms, - DenomsToRemove: []string{"stake"}, - Authority: "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", - } - - // Generate proposal content - title := "change reward denoms" - description := "change reward denoms" - summary := "Proposal to change reward denoms" - expedited := false - metadata := "ipfs://CID" - deposit := fmt.Sprintf("%dstake", action.Deposit) - jsonStr := e2e.GenerateGovProposalContent(title, summary, metadata, deposit, description, expedited, &changeRewMsg) - - //#nosec G204 -- bypass unsafe quoting warning (no production code) - proposalFile := "/change-rewards-proposal.json" - bz, err := tr.target.ExecCommand( - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, proposalFile), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // CHANGE REWARDS DENOM PROPOSAL - cmd := tr.target.ExecCommand( - tr.testConfig.chainConfigs[action.Chain].BinaryName, - "tx", "gov", "submit-proposal", proposalFile, - `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(tr.testConfig.chainConfigs[action.Chain].ChainId), - `--home`, tr.getValidatorHome(action.Chain, action.From), - `--gas`, `900000`, - `--node`, tr.getValidatorNode(action.Chain, action.From), - `--keyring-backend`, `test`, - `-y`, - ) - - if verbose { - fmt.Println("change rewards denom props cmd:", cmd.String()) - fmt.Println("change rewards denom props json:", jsonStr) - } - bz, err = cmd.CombinedOutput() - if err != nil { - log.Fatal("submit-proposal failed:", err, "\n", string(bz)) - } - - if verbose { - fmt.Println("change rewards denom props output:", string(bz)) - } - - // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) -} - -func (tr Chain) submitChangeRewardDenomsLegacyProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { - providerChain := tr.testConfig.chainConfigs[action.Chain] ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) prop := client.ChangeRewardDenomsProposalJSON{ Summary: "Change reward denoms", ChangeRewardDenomsProposal: types.ChangeRewardDenomsProposal{ Title: "Change reward denoms", Description: "Change reward denoms", - DenomsToAdd: action.Denoms, + DenomsToAdd: []string{action.Denom}, DenomsToRemove: []string{"stake"}, }, Deposit: fmt.Sprint(action.Deposit) + `stake`, @@ -2703,123 +2492,3 @@ func (tr TestConfig) AdvanceTimeForChain(chain ChainID, duration time.Duration) // wait for 1 block of the chain to get a block with the advanced timestamp tr.waitBlocks(chain, 1, time.Minute) } -<<<<<<< HEAD -======= - -func (tr Commands) AssignConsumerPubKey(action e2e.AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) { - assignKey := fmt.Sprintf( - `%s tx provider assign-consensus-key %s '%s' --from validator%s --chain-id %s --home %s --node %s --gas %s --keyring-backend test -y -o json`, - tr.chainConfigs[ChainID("provi")].BinaryName, - string(tr.chainConfigs[action.Chain].ConsumerId), - action.ConsumerPubkey, - action.Validator, - tr.chainConfigs[ChainID("provi")].ChainId, - home, - node, - gas, - ) - - cmd := tr.target.ExecCommand( - "/bin/bash", "-c", - assignKey, - ) - - if verbose { - fmt.Println("assignConsumerPubKey cmd:", cmd.String()) - } - - 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) -} ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 221f7c6037..1b3e0795b4 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -7,7 +7,6 @@ import ( "os/exec" "regexp" "strconv" - "strings" "time" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -329,113 +328,13 @@ func (tr TestConfig) getRewards(chain ChainID, modelState Rewards) Rewards { currentBlock = 1 } for k := range modelState.IsRewarded { -<<<<<<< HEAD receivedRewards[k] = tr.getReward(chain, k, nextBlock, modelState.IsNativeDenom) > tr.getReward(chain, k, currentBlock, modelState.IsNativeDenom) -======= - receivedRewards[k] = tr.target.GetReward(chain, k, nextBlock, modelState.Denom) > tr.target.GetReward(chain, k, currentBlock, modelState.Denom) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } - return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, Denom: modelState.Denom} + return Rewards{IsRewarded: receivedRewards, IsIncrementalReward: modelState.IsIncrementalReward, IsNativeDenom: modelState.IsNativeDenom} } -<<<<<<< HEAD func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { -======= -func (tr Chain) GetConsumerAddresses(chain ChainID, modelState map[ValidatorID]string) map[ValidatorID]string { - actualState := map[ValidatorID]string{} - for k := range modelState { - actualState[k] = tr.target.GetConsumerAddress(chain, k) - } - - return actualState -} - -func (tr Chain) GetProviderAddresses(chain ChainID, modelState map[ValidatorID]string) map[ValidatorID]string { - actualState := map[ValidatorID]string{} - for k := range modelState { - actualState[k] = tr.target.GetProviderAddressFromConsumer(chain, k) - } - - return actualState -} - -func (tr Chain) getValidatorNode(chain ChainID, validator ValidatorID) string { - // for CometMock, validatorNodes are all the same address as the query node (which is CometMocks address) - if tr.testConfig.useCometmock { - return tr.target.GetQueryNode(chain) - } - - return "tcp://" + tr.getValidatorIP(chain, validator) + ":26658" -} - -func (tr Chain) getValidatorIP(chain ChainID, validator ValidatorID) string { - return tr.testConfig.chainConfigs[chain].IpPrefix + "." + tr.testConfig.validatorConfigs[validator].IpSuffix -} - -func (tr Chain) getValidatorHome(chain ChainID, validator ValidatorID) string { - return `/` + string(chain) + `/validator` + fmt.Sprint(validator) -} - -func (tr Chain) curlJsonRPCRequest(method, params, address string) { - cmd_template := `curl -H 'Content-Type: application/json' -H 'Accept:application/json' --data '{"jsonrpc":"2.0","method":"%s","params":%s,"id":1}' %s` - - cmd := tr.target.ExecCommand("bash", "-c", fmt.Sprintf(cmd_template, method, params, address)) - - verbosity := false - e2e.ExecuteCommand(cmd, "curlJsonRPCRequest", verbosity) -} - -func uintPtr(i uint) *uint { - return &i -} - -func intPtr(i int) *int { - return &i -} - -type Commands struct { - containerConfig *ContainerConfig - validatorConfigs map[ValidatorID]ValidatorConfig - chainConfigs map[ChainID]ChainConfig - target e2e.PlatformDriver -} - -func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { - return tr.target.ExecCommand(name, arg...) -} - -func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { - return tr.target.ExecDetachedCommand(name, args...) -} - -func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { - return tr.target.GetTestScriptPath(isConsumer, script) -} - -func (tr Commands) GetBlockHeight(chain ChainID) uint { - binaryName := tr.chainConfigs[chain].BinaryName - bz, err := tr.target.ExecCommand(binaryName, - - "query", "tendermint-validator-set", - - `--node`, tr.GetQueryNode(chain), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) - blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) - if err != nil { - log.Fatal(err) - } - - return uint(blockHeight) -} - -func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) valCfg := tr.validatorConfigs[validator] delAddresss := valCfg.DelAddress if chain != ChainID("provi") { @@ -462,23 +361,12 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u log.Fatal(err, "\n", string(bz)) } - 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) - } + denomCondition := `total.#(denom!="stake").amount` + if isNativeDenom { + denomCondition = `total.#(denom=="stake").amount` } - fmt.Println("res", res) - - return res + return gjson.Get(string(bz), denomCondition).Float() } func (tr TestConfig) getBalance(chain ChainID, validator ValidatorID) uint { diff --git a/tests/e2e/state_rapid_test.go b/tests/e2e/state_rapid_test.go index 3416c0d347..3192662d27 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"), - // Denom: rapid.Str, - IsRewarded: rapid.MapOf(GetValidatorIDGen(), rapid.Bool()).Draw(t, "IsRewarded"), + IsNativeDenom: rapid.Bool().Draw(t, "IsNativeDenom"), + IsRewarded: rapid.MapOf(GetValidatorIDGen(), rapid.Bool()).Draw(t, "IsRewarded"), } }) } diff --git a/tests/e2e/steps_democracy.go b/tests/e2e/steps_democracy.go index 7c5e51d567..30288e7ba9 100644 --- a/tests/e2e/steps_democracy.go +++ b/tests/e2e/steps_democracy.go @@ -1,15 +1,6 @@ package main -<<<<<<< HEAD const consumerRewardDenom = "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" -======= -import gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - -var consumerRewardDenoms = []string{ - "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9", // transfer channel-1 - "ibc/D549749C93524DA1831A4B3C850DFC1BA9060261BEDFB224B3B0B4744CD77A70", // transfer channel-2 -} ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool) []Step { return []Step{ @@ -32,7 +23,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): false, }, IsIncrementalReward: true, - Denom: "stake", + IsNativeDenom: true, }, }, }, @@ -65,7 +56,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): true, }, IsIncrementalReward: true, - Denom: "stake", + IsNativeDenom: true, }, }, }, @@ -147,7 +138,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): false, }, IsIncrementalReward: false, - Denom: consumerRewardDenoms[0], + IsNativeDenom: false, }, // Check that the denom is not registered on provider chain RegisteredConsumerRewardDenoms: &[]string{}, @@ -156,12 +147,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool }, { Action: SubmitChangeRewardDenomsProposalAction{ -<<<<<<< HEAD Denom: consumerRewardDenom, -======= - Chain: ChainID("provi"), - Denoms: consumerRewardDenoms, ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) Deposit: 10000001, From: ValidatorID("bob"), }, @@ -182,11 +168,10 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool State: State{ ChainID("provi"): ChainState{ // Check that the denom is registered on provider chain - RegisteredConsumerRewardDenoms: &consumerRewardDenoms, + RegisteredConsumerRewardDenoms: &[]string{consumerRewardDenom}, }, }, }, - // Relay pending consumer rewards sent via the transfer channel-1 { Action: RelayRewardPacketsToProviderAction{ ConsumerChain: ChainID(consumerName), @@ -196,115 +181,8 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool }, State: State{ ChainID("provi"): ChainState{ - 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{ + // 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{ IsRewarded: map[ValidatorID]bool{ ValidatorID("alice"): expectRegisteredRewardDistribution, @@ -312,7 +190,7 @@ func stepsDemocracy(consumerName string, expectRegisteredRewardDistribution bool ValidatorID("carol"): expectRegisteredRewardDistribution, }, IsIncrementalReward: false, - Denom: consumerRewardDenoms[1], + IsNativeDenom: false, }, }, }, diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go deleted file mode 100644 index dab57b4eba..0000000000 --- a/tests/e2e/steps_inactive_vals.go +++ /dev/null @@ -1,973 +0,0 @@ -package main - -import ( - gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" -) - -// stepsInactiveValidatorsOnConsumer tests situations where validators that are *not* in the active set on the -// provider chain validate on the consumer chain. -// The provider chain is set to have at most *2* validators active in consensus, and there are 3 validators in total. -// high-level, this test does: -// - start the provider chain -// - start a consumer chain -// - check that non-consensus validators do not get slashed for downtime on the provider; and that they don't get rewards -// - check that active validators *do* get slashed for downtime on the provider, and don't get rewards while they are down -// - check that non-consensus validators *do* get jailed for consumer downtime on the provider -// - check that non-consensus validators *become* consensus validators when they have enough power -func stepsInactiveProviderValidators() []Step { - s := concatSteps( - []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power - ValidatorID("bob"): 200, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 100000000, - ValidatorID("bob"): 200000000, - ValidatorID("carol"): 300000000, - }, - Rewards: &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{ - ValidatorID("alice"): false, - ValidatorID("bob"): true, - ValidatorID("carol"): true, - }, - }, - }, - }, - }, - }, - setupOptInChain(), - []Step{ - // check that active-but-not-consensus validators do not get slashed for downtime - { - // alices provider node goes offline - Action: DowntimeSlashAction{ - Chain: ChainID("provi"), - Validator: ValidatorID("alice"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // still 0 consensus power - ValidatorID("bob"): 200, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 100000000, // but alice does not get jailed or slashed - ValidatorID("bob"): 200000000, - ValidatorID("carol"): 300000000, - }, - }, - }, - }, - // give carol more power so that she has enough power to validate if bob goes down - { - Action: DelegateTokensAction{ - Chain: ChainID("provi"), - From: ValidatorID("carol"), - To: ValidatorID("carol"), - Amount: 700000000, // carol needs to have more than 2/3rds of power(alice) + power(carol) + power(bob) to run both chains alone, so we stake some more to her - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, - ValidatorID("bob"): 200, - ValidatorID("carol"): 1000, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 100000000, - ValidatorID("bob"): 200000000, - ValidatorID("carol"): 1000000000, - }, - // check that bob and carol get rewards, but alice does not - Rewards: &Rewards{ - 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, - ValidatorID("carol"): true, - }, - }, - }, - }, - }, - // bob goes offline - { - Action: DowntimeSlashAction{ - Chain: ChainID("provi"), - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, // alice gets into the active set - ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 1000, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 100000000, - ValidatorID("bob"): 198000000, // 1% slash - ValidatorID("carol"): 1000000000, - }, - }, - }, - }, - { - // relay packets so that the consumer gets up to date with the provider - Action: RelayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID("consu"), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("provi"): ChainState{ - Rewards: &Rewards{ - 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 - ValidatorID("carol"): true, - }, - }, - }, - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 0, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // unjail bob - { - Action: UnjailValidatorAction{ - Provider: ChainID("provi"), - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus - ValidatorID("bob"): 198, // bob was slashed 1% - ValidatorID("carol"): 1000, - }, - // check that between two blocks now, alice does not get rewarded with the native denom - Rewards: &Rewards{ - 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, - ValidatorID("carol"): true, - }, - }, - }, - // bob is still at 0 power on the consumer chain - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 0, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // relay packets so that the consumer gets up to date with the provider - { - Action: RelayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID("consu"), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 198, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // alice goes offline on the consumer chain - { - Action: DowntimeSlashAction{ - Chain: ChainID("consu"), - Validator: ValidatorID("alice"), - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, // power not affected yet - ValidatorID("bob"): 198, - ValidatorID("carol"): 1000, - }, - }, - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum - ValidatorID("bob"): 198, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // relay the packets so that the provider chain knows about alice's downtime - { - Action: RelayPacketsAction{ - ChainA: ChainID("consu"), - ChainB: ChainID("provi"), - Port: "consumer", - Channel: 0, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too. - // we cannot test directly whether alice is jailed, but we will test this below - ValidatorID("bob"): 198, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // we need to double-check that alice is actually jailed, so we get bob jailed, too, which usually would mean alice gets into power - { - Action: DowntimeSlashAction{ - Chain: ChainID("provi"), - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is jailed - ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // relay the packets so that the consumer chain is in sync again - { - Action: RelayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID("consu"), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is jailed - ValidatorID("bob"): 0, // bob is jailed - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // unjail alice - { - Action: UnjailValidatorAction{ - Provider: ChainID("provi"), - Validator: ValidatorID("alice"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - // alice was not slashed because consumer downtime just jails without slashing tokens - ValidatorID("alice"): 100, // alice is back as an active consensus validator. - ValidatorID("bob"): 0, // bob is still jailed - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // unjail bob - { - Action: UnjailValidatorAction{ - Provider: ChainID("provi"), - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus - ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime - ValidatorID("carol"): 1000, - }, - }, - }, - }, - // relay the packets so that the consumer chain is in sync again - { - Action: RelayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID("consu"), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, // both alice and bob are validating the consumer - ValidatorID("bob"): 196, - ValidatorID("carol"): 1000, - }, - }, - }, - }, - }, - ) - - return s -} - -// Precondition: The provider chain is running. -// Postcondition: A consumer chain with Top N = 0 is running, including an up-and-running IBC connection to the provider. -// "alice", "bob", "carol" have opted in and are validating. -func setupOptInChain() []Step { - return concatSteps([]Step{ - { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 0, - AllowInactiveVals: true, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, - ValidatorID("carol"): {}, - }, - }, - }, - }, - }, - stepsOptInValidators("alice", "bob", "carol"), - []Step{ - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), - }, - }, - }, - }, - }, - { - // we start all the validators but only "alice" and "bob" have opted in and hence - // only "alice" and "bob" are validating blocks - Action: StartConsumerChainAction{ - ConsumerChain: ChainID("consu"), - ProviderChain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - // For consumers that're launching with the provider being on an earlier version - // of ICS before the soft opt-out threshold was introduced, we need to set the - // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the - // consumer binary doesn't panic. Sdk requires that all params are set to valid - // values from the genesis file. - GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 100, - ValidatorID("bob"): 200, - ValidatorID("carol"): 300, - }, - }, - }, - }, - { - Action: AddIbcConnectionAction{ - ChainA: ChainID("consu"), - ChainB: ChainID("provi"), - ClientA: 0, - ClientB: 0, - }, - State: State{}, - }, - { - Action: AddIbcChannelAction{ - ChainA: ChainID("consu"), - ChainB: ChainID("provi"), - ConnectionA: 0, - PortA: "consumer", - PortB: "provider", - Order: "ordered", - }, - State: State{}, - }, - }, - ) -} - -func stepsOptInValidators(validators ...ValidatorID) []Step { - s := make([]Step, 0) - for _, val := range validators { - // Οpt in all validators - s = append(s, Step{ - Action: OptInAction{ - Chain: ChainID("consu"), - Validator: val, - }, - State: State{ - ChainID("provi"): ChainState{}, - }, - }, - ) - } - return s -} - -// stepsInactiveProviderValidatorsGovernance validates that inactive validators -// are not included in the calculation of the quorum for governance proposals. -// It checks that when the quorum is met *among active validators*, -// the proposal can pass, even though the quorum would not be met if inactive validators -// would be counted. -func stepsInactiveProviderValidatorsGovernance() []Step { - s := concatSteps( - []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // max consensus validators is 1, so alice and bob should not be in power - ValidatorID("bob"): 0, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 290000000, - ValidatorID("bob"): 290000000, - ValidatorID("carol"): 300000000, - }, - }, - }, - }, - }, - []Step{ - // create a governance proposal - { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 51, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), - }, - }, - }, - }, - }, - // vote for it with carol - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("carol")}, - Vote: []string{"yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - // the proposal should have passed because carol voted for it. - // carol alone is enough to pass the quorum, because stake of the other validators is not counted - Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), - }, - }, - }, - }, - }, - }, - ) - - return s -} - -// stepsInactiveProviderValidatorsGovernanceBasecase is a sanity check to go along with -// stepsInactiveProviderValidatorsGovernance. It tests that with all validators being active, -// the proposal does not pass if it does not meet the quorum among validators. -func stepsInactiveProviderValidatorsGovernanceBasecase() []Step { - s := concatSteps( - []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 290, - ValidatorID("bob"): 290, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 290000000, - ValidatorID("bob"): 290000000, - ValidatorID("carol"): 300000000, - }, - }, - }, - }, - }, - []Step{ - // create a governance proposal - { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 51, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), - }, - }, - }, - }, - }, - // vote for it with carol - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("carol")}, - Vote: []string{"yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - // the proposal should *not* have passed because only carol voted for it, - // and carol is not enough to pass the quorum - Status: gov.ProposalStatus_PROPOSAL_STATUS_REJECTED.String(), - }, - }, - }, - }, - }, - }, - ) - - return s -} - -// stepsMinStake validates that a validator with less stake than the specified minStake parameter -// cannot validate the consumer chain. -func stepsMinStake() []Step { - return concatSteps( - []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 290, - ValidatorID("bob"): 290, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 290000000, - ValidatorID("bob"): 290000000, - ValidatorID("carol"): 300000000, - }, - }, - }, - }, - // create a governance proposal - { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 0, - MinStake: 300000000, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), - }, - }, - }, - }, - }, - }, - stepsOptInValidators("alice", "bob", "carol"), - []Step{ - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), - }, - }, - }, - }, - }, - { - // we start all the validators, but due to the min stake, only carol can validate - Action: StartConsumerChainAction{ - ConsumerChain: ChainID("consu"), - ProviderChain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - // For consumers that're launching with the provider being on an earlier version - // of ICS before the soft opt-out threshold was introduced, we need to set the - // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the - // consumer binary doesn't panic. Sdk requires that all params are set to valid - // values from the genesis file. - GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, - ValidatorID("bob"): 0, - ValidatorID("carol"): 300, // due to min stake of 300000000, only carol can validate - }, - }, - }, - }, - }, - ) -} - -// This test case validates that inactive validators are not included when computing -// the top N. -func stepsInactiveValsWithTopN() []Step { - return []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power - ValidatorID("bob"): 200, - ValidatorID("carol"): 300, - }, - StakedTokens: &map[ValidatorID]uint{ - ValidatorID("alice"): 100000000, - ValidatorID("bob"): 200000000, - ValidatorID("carol"): 300000000, - }, - Rewards: &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{ - ValidatorID("alice"): false, - ValidatorID("bob"): true, - ValidatorID("carol"): true, - }, - }, - }, - }, - }, - { - Action: SubmitConsumerAdditionProposalAction{ - Chain: ChainID("provi"), - From: ValidatorID("alice"), - Deposit: 10000001, - ConsumerChain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - TopN: 51, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD.String(), - }, - }, - }, - }, - }, - { - Action: VoteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, - Vote: []string{"yes", "yes"}, - PropNumber: 1, - }, - State: State{ - ChainID("provi"): ChainState{ - Proposals: &map[uint]Proposal{ - 1: ConsumerAdditionProposal{ - Deposit: 10000001, - Chain: ChainID("consu"), - SpawnTime: 0, - InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, - Status: gov.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), - }, - }, - HasToValidate: &map[ValidatorID][]ChainID{ - ValidatorID("alice"): {}, - ValidatorID("bob"): {}, // bob doesn't have to validate because he is not in the top N - ValidatorID("carol"): {"consu"}, - }, - }, - }, - }, - { - Action: StartConsumerChainAction{ - ConsumerChain: ChainID("consu"), - ProviderChain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, - }, - // For consumers that're launching with the provider being on an earlier version - // of ICS before the soft opt-out threshold was introduced, we need to set the - // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the - // consumer binary doesn't panic. Sdk requires that all params are set to valid - // values from the genesis file. - GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", - }, - State: State{ - ChainID("consu"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, // alice and bob are not in the top N, so aren't in the validator set - ValidatorID("bob"): 0, - ValidatorID("carol"): 300, - }, - }, - }, - }, - } -} - -// stepsInactiveValsMint tests the minting of tokens with inactive validators -// It checks that inactive validators are not counted when computing whether the -// inflation rate should go up or down. -func stepsInactiveValsMint() []Step { - // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded - return []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, - ValidatorID("bob"): 0, - ValidatorID("carol"): 29, // other validators are not in power since only 1 can be active - }, - InflationRateChange: intPtr(1), // inflation rate goes up because less than the goal is bonded, since only carol is active - }, - }, - }, - { - Action: DelegateTokensAction{ - Chain: ChainID("provi"), - From: ValidatorID("carol"), - To: ValidatorID("carol"), - Amount: 50000000, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 0, - ValidatorID("bob"): 0, - ValidatorID("carol"): 79, - }, - InflationRateChange: intPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal - }, - }, - }, - } -} - -// stepsMintBasecase tests the minting of tokens without inactive validators. -// This is done as a sanity check to complement stepsInactiveValsMint. -func stepsMintBasecase() []Step { - // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded - return []Step{ - { - Action: StartChainAction{ - Chain: ChainID("provi"), - Validators: []StartChainValidator{ - {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, - {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, - {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, - }, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 27, - ValidatorID("bob"): 28, - ValidatorID("carol"): 29, - }, - InflationRateChange: intPtr(-1), // inflation rate goes down because more than the goal is bonded - }, - }, - }, - { - Action: DelegateTokensAction{ - Chain: ChainID("provi"), - From: ValidatorID("carol"), - To: ValidatorID("carol"), - Amount: 50000000, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 27, - ValidatorID("bob"): 28, - ValidatorID("carol"): 79, - }, - InflationRateChange: intPtr(-1), // inflation rate *still* goes down - }, - }, - }, - } -} diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go index 094e0171fb..245d70020b 100644 --- a/tests/e2e/test_driver.go +++ b/tests/e2e/test_driver.go @@ -143,25 +143,7 @@ func (td *DefaultDriver) runAction(action interface{}) error { case OptInAction: td.testCfg.optIn(action, td.target, td.verbose) case OptOutAction: -<<<<<<< HEAD td.testCfg.optOut(action, td.target, td.verbose) -======= - target := td.getTargetDriver("provider") - target.optOut(action, td.verbose) - case SetConsumerCommissionRateAction: - target := td.getTargetDriver("provider") - target.setConsumerCommissionRate(action, td.verbose) - 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) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) 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 deleted file mode 100644 index 0fd7ada259..0000000000 --- a/tests/e2e/testlib/types.go +++ /dev/null @@ -1,366 +0,0 @@ -package e2e - -import ( - "encoding/json" - "fmt" - "os/exec" - "reflect" - "time" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" -) - -type ( - ChainID string - ConsumerID string - ValidatorID string -) - -type AssignConsumerPubKeyAction struct { - Chain ChainID - Validator ValidatorID - ConsumerPubkey string - // ReconfigureNode will change keys the node uses and restart - ReconfigureNode bool - // executing the action should raise an error - ExpectError bool - ExpectedError string -} - -type ChainCommands interface { - GetBlockHeight(chain ChainID) uint - GetBalance(chain ChainID, validator ValidatorID) uint - GetConsumerChains(chain ChainID) map[ChainID]bool - GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string - GetClientFrozenHeight(chain ChainID, clientID string) (RevisionNumber, RevisionHeight uint64) - GetHasToValidate(validator ValidatorID) []ChainID - GetIBCTransferParams(chain ChainID) IBCTransferParams - 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, denom string) float64 - GetRegisteredConsumerRewardDenoms(chain ChainID) []string - GetSlashMeter() int64 - GetPendingPacketQueueSize(chain ChainID) uint - GetProposedConsumerChains(chain ChainID) []string - GetQueryNode(chain ChainID) string - GetQueryNodeRPCAddress(chain ChainID) string - GetTrustedHeight(chain ChainID, clientID string, index int) (uint64, uint64) - GetValPower(chain ChainID, validator ValidatorID) uint - GetValStakedTokens(chain ChainID, validatorAddress string) uint - GetQueryNodeIP(chain ChainID) string - GetInflationRate(chain ChainID) float64 - GetConsumerCommissionRate(chain ChainID, validator ValidatorID) float64 - // Action commands - AssignConsumerPubKey(action AssignConsumerPubKeyAction, gas, home, node string, verbose bool) ([]byte, error) -} - -// TODO: replace ExecutionTarget with new TargetDriver interface -type PlatformDriver interface { - ExecCommand(name string, arg ...string) *exec.Cmd - // ExecDetachedCommand: when executed the command will be run in the background and call will return immediately - ExecDetachedCommand(name string, args ...string) *exec.Cmd - GetTestScriptPath(isConsumer bool, script string) string -} -type TargetDriver interface { - // ChainCommands - ChainCommands - PlatformDriver -} - -// TODO: this should not be here. mv 'Now' to a better suited type here and then move ContainerConfig back -type ContainerConfig struct { - ContainerName string - InstanceName string - CcvVersion string - Now time.Time -} - -// Attributes that are unique to a validator. Allows us to map (part of) -// the set of strings defined above to a set of viable validators -type ValidatorConfig struct { - // Seed phrase to generate a secp256k1 key used by the validator on the provider - Mnemonic string - // Validator account address on provider marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix - DelAddress string - // Validator account address on provider marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix - DelAddressOnConsumer string - // Validator operator address on provider marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix - ValoperAddress string - // Validator operator address on provider marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix - ValoperAddressOnConsumer string - // Validator consensus address on provider marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix. It matches the PrivValidatorKey below. - ValconsAddress string - // Validator consensus address on provider marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix. - ValconsAddressOnConsumer string - // Key used for consensus on provider - PrivValidatorKey string - NodeKey string - // Must be an integer greater than 0 and less than 253 - IpSuffix string - - // consumer chain key assignment data - // keys are used on a new node - - // Seed phrase to generate a secp256k1 key used by the validator on the consumer - ConsumerMnemonic string - // Validator account address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix - ConsumerDelAddress string - // Validator account address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix - ConsumerDelAddressOnProvider string - // Validator operator address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix - ConsumerValoperAddress string - // Validator operator address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix - ConsumerValoperAddressOnProvider string - // Validator consensus address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ConsumerAccountPrefix. It matches the PrivValidatorKey below. - ConsumerValconsAddress string - // Validator consensus address on consumer marshaled to string using Bech32 - // with Bech32Prefix = ProviderAccountPrefix. - ConsumerValconsAddressOnProvider string - ConsumerValPubKey string - // Key used for consensus on consumer - ConsumerPrivValidatorKey string - ConsumerNodeKey string - UseConsumerKey bool // if true the validator node will start with consumer key -} - -// Attributes that are unique to a chain. Allows us to map (part of) -// the set of strings defined above to a set of viable chains -type ChainConfig struct { - ChainId ChainID - ConsumerId ConsumerID - // The account prefix configured on the chain. For example, on the Hub, this is "cosmos" - AccountPrefix string - // Must be unique per chain - IpPrefix string - VotingWaitTime uint - // Any transformations to apply to the genesis file of all chains instantiated with this chain config, as a jq string. - // Example: ".app_state.gov.params.voting_period = \"5s\" | .app_state.slashing.params.signed_blocks_window = \"2\" | .app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\"" - GenesisChanges string - BinaryName string - - // binary to use after upgrade height - UpgradeBinary string -} - -type ( - // to have a ChainState object that does not have the overridden Marshal/Unmarshal method - ChainStateCopy ChainState - - // duplicated from the ChainState with a minor change to the Proposals field - ChainStateWithProposalTypes struct { - ChainStateCopy - Proposals *map[uint]ProposalAndType // the only thing changed from the real ChainState - } -) - -// stores a proposal as a raw json, together with its type -type ProposalAndType struct { - RawProposal json.RawMessage - Type string -} -type ChainState struct { - ValBalances *map[ValidatorID]uint - Proposals *map[uint]Proposal - ProposedConsumerChains *[]string - ValPowers *map[ValidatorID]uint - StakedTokens *map[ValidatorID]uint - IBCTransferParams *IBCTransferParams - Params *[]Param - Rewards *Rewards - ConsumerChains *map[ChainID]bool - AssignedKeys *map[ValidatorID]string - ProviderKeys *map[ValidatorID]string // validatorID: validator provider key - ConsumerPendingPacketQueueSize *uint // Only relevant to consumer chains - RegisteredConsumerRewardDenoms *[]string - ClientsFrozenHeights *map[string]clienttypes.Height - HasToValidate *map[ValidatorID][]ChainID // only relevant to provider chain - InflationRateChange *int // whether the inflation rate between two blocks changes negatively (any negative number), is equal (0), or changes positively (any positive number) - ConsumerCommissionRates *map[ValidatorID]float64 -} - -// custom marshal and unmarshal functions for the chainstate that convert proposals to/from the auxiliary type with type info - -// MarshalJSON transforms the ChainState into a ChainStateWithProposalTypes by adding type info to the proposals -func (c ChainState) MarshalJSON() ([]byte, error) { - chainStateCopy := ChainStateCopy(c) - chainStateWithProposalTypes := ChainStateWithProposalTypes{chainStateCopy, nil} - if c.Proposals != nil { - proposalsWithTypes := make(map[uint]ProposalAndType) - for k, v := range *c.Proposals { - rawMessage, err := json.Marshal(v) - if err != nil { - return nil, err - } - proposalsWithTypes[k] = ProposalAndType{rawMessage, reflect.TypeOf(v).String()} - } - chainStateWithProposalTypes.Proposals = &proposalsWithTypes - } - return json.Marshal(chainStateWithProposalTypes) -} - -// UnmarshalJSON unmarshals the ChainStateWithProposalTypes into a ChainState by removing the type info from the proposals and getting back standard proposals -func (c *ChainState) UnmarshalJSON(data []byte) error { - chainStateWithProposalTypes := ChainStateWithProposalTypes{} - err := json.Unmarshal(data, &chainStateWithProposalTypes) - if err != nil { - return err - } - - chainState := ChainState(chainStateWithProposalTypes.ChainStateCopy) - *c = chainState - - if chainStateWithProposalTypes.Proposals != nil { - proposals := make(map[uint]Proposal) - for k, v := range *chainStateWithProposalTypes.Proposals { - proposal, err := UnmarshalProposalWithType(v.RawProposal, v.Type) - if err != nil { - return err - } - proposals[k] = proposal - } - c.Proposals = &proposals - } - return nil -} - -// UnmarshalProposalWithType takes a JSON object and a proposal type and marshals into an object of the corresponding proposal. -func UnmarshalProposalWithType(inputMap json.RawMessage, proposalType string) (Proposal, error) { - var err error - switch proposalType { - case "main.TextProposal": - prop := TextProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } - case "main.ConsumerAdditionProposal": - prop := ConsumerAdditionProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } - case "main.UpgradeProposal": - prop := UpgradeProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } - case "main.ConsumerRemovalProposal": - prop := ConsumerRemovalProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } - case "main.IBCTransferParamsProposal": - prop := IBCTransferParamsProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } - default: - return nil, fmt.Errorf("%s is not a known proposal type", proposalType) - } - - return nil, err -} - -type Proposal interface { - isProposal() -} -type TextProposal struct { - Title string - Description string - Deposit uint - Status string -} - -func (p TextProposal) isProposal() {} - -type IBCTransferParamsProposal struct { - Title string - Deposit uint - Status string - Params IBCTransferParams -} - -func (ibct IBCTransferParamsProposal) isProposal() {} - -type ConsumerAdditionProposal struct { - Deposit uint - Chain ChainID - SpawnTime int - InitialHeight clienttypes.Height - Status string -} - -type UpgradeProposal struct { - Title string - Description string - UpgradeHeight uint64 - Type string - Deposit uint - Status string -} - -func (p UpgradeProposal) isProposal() {} - -func (p ConsumerAdditionProposal) isProposal() {} - -type ConsumerRemovalProposal struct { - Deposit uint - Chain ChainID - Status string -} - -func (p ConsumerRemovalProposal) isProposal() {} - -type ConsumerModificationProposal struct { - Deposit uint - Chain ChainID - Status string -} - -func (p ConsumerModificationProposal) isProposal() {} - -type Rewards struct { - IsRewarded map[ValidatorID]bool - // 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 - // 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 { - Deposit uint - Status string - Subspace string - Key string - Value string -} - -func (p ParamsProposal) isProposal() {} - -type Param struct { - Subspace string - Key string - Value string -} - -type IBCTransferParams struct { - SendEnabled bool `json:"send_enabled"` - ReceiveEnabled bool `json:"receive_enabled"` -} diff --git a/tests/e2e/v4/state.go b/tests/e2e/v4/state.go deleted file mode 100644 index f0ba1bca4b..0000000000 --- a/tests/e2e/v4/state.go +++ /dev/null @@ -1,648 +0,0 @@ -package v4 - -import ( - "bufio" - "fmt" - "log" - "os/exec" - "regexp" - "strconv" - "strings" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/kylelemons/godebug/pretty" - "github.com/tidwall/gjson" - "gopkg.in/yaml.v2" - - gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - - e2e "github.com/cosmos/interchain-security/v6/tests/e2e/testlib" -) - -type ( - ChainID = e2e.ChainID - ValidatorID = e2e.ValidatorID - ChainState = e2e.ChainState - Proposal = e2e.Proposal - Rewards = e2e.Rewards - TextProposal = e2e.TextProposal - UpgradeProposal = e2e.UpgradeProposal - ConsumerAdditionProposal = e2e.ConsumerAdditionProposal - ConsumerRemovalProposal = e2e.ConsumerRemovalProposal - IBCTransferParams = e2e.IBCTransferParams - IBCTransferParamsProposal = e2e.IBCTransferParamsProposal - Param = e2e.Param - ParamsProposal = e2e.ParamsProposal - ValidatorConfig = e2e.ValidatorConfig - ChainConfig = e2e.ChainConfig - ContainerConfig = e2e.ContainerConfig - TargetDriver = e2e.TargetDriver -) - -type State map[ChainID]ChainState - -type Commands struct { - ContainerConfig ContainerConfig // FIXME only needed for 'Now' time tracking - ValidatorConfigs map[ValidatorID]ValidatorConfig - ChainConfigs map[ChainID]ChainConfig - Target e2e.PlatformDriver -} - -func (tr Commands) ExecCommand(name string, arg ...string) *exec.Cmd { - return tr.Target.ExecCommand(name, arg...) -} - -func (tr Commands) ExecDetachedCommand(name string, args ...string) *exec.Cmd { - return tr.Target.ExecDetachedCommand(name, args...) -} - -func (tr Commands) GetTestScriptPath(isConsumer bool, script string) string { - return tr.Target.GetTestScriptPath(isConsumer, script) -} - -func (tr Commands) GetBlockHeight(chain ChainID) uint { - binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - - "query", "tendermint-validator-set", - - `--node`, tr.GetQueryNode(chain), - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - blockHeightRegex := regexp.MustCompile(`block_height: "(\d+)"`) - blockHeight, err := strconv.Atoi(blockHeightRegex.FindStringSubmatch(string(bz))[1]) - if err != nil { - log.Fatal(err) - } - - return uint(blockHeight) -} - -type ValPubKey struct { - Value string `yaml:"value"` -} - -type TmValidatorSetYaml struct { - Total string `yaml:"total"` - Validators []struct { - Address string `yaml:"address"` - VotingPower string `yaml:"voting_power"` - PubKey ValPubKey `yaml:"pub_key"` - } -} - -func (tr Commands) GetValPower(chain ChainID, validator ValidatorID) uint { - /* if *verbose { - log.Println("getting validator power for chain: ", chain, " validator: ", validator) - } - */ - binaryName := tr.ChainConfigs[chain].BinaryName - command := tr.Target.ExecCommand(binaryName, - - "query", "tendermint-validator-set", - - `--node`, tr.GetQueryNode(chain), - ) - bz, err := command.CombinedOutput() - if err != nil { - log.Fatalf("encountered an error when executing command '%s': %v, output: %s", command.String(), err, string(bz)) - } - - valset := TmValidatorSetYaml{} - err = yaml.Unmarshal(bz, &valset) - if err != nil { - log.Fatalf("yaml.Unmarshal returned an error while unmarshalling validator set: %v, input: %s", err, string(bz)) - } - - total, err := strconv.Atoi(valset.Total) - if err != nil { - log.Fatalf("v4: strconv.Atoi returned an error while converting total for validator set: %v, input: %s, validator set: %s, src: %s", - err, valset.Total, pretty.Sprint(valset), string(bz)) - } - - if total != len(valset.Validators) { - log.Fatalf("Total number of validators %v does not match number of validators in list %v. Probably a query pagination issue. Validator set: %v", - valset.Total, uint(len(valset.Validators)), pretty.Sprint(valset)) - } - - for _, val := range valset.Validators { - if chain == ChainID("provi") { - // use binary with Bech32Prefix set to ProviderAccountPrefix - if val.Address != tr.ValidatorConfigs[validator].ValconsAddress { - continue - } - } else { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if val.Address != tr.ValidatorConfigs[validator].ValconsAddressOnConsumer && - val.Address != tr.ValidatorConfigs[validator].ConsumerValconsAddress { - continue - } - } - - votingPower, err := strconv.Atoi(val.VotingPower) - if err != nil { - log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) - } - - return uint(votingPower) - } - - // Validator not in set, its validator power is zero. - return 0 -} - -func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight uint, denom string) float64 { - valCfg := tr.ValidatorConfigs[validator] - delAddresss := valCfg.DelAddress - if chain != ChainID("provi") { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if valCfg.UseConsumerKey { - delAddresss = valCfg.ConsumerDelAddress - } else { - // use the same address as on the provider but with different prefix - delAddresss = valCfg.DelAddressOnConsumer - } - } - - binaryName := tr.ChainConfigs[chain].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - "query", "distribution", "rewards", - delAddresss, - `--height`, fmt.Sprint(blockHeight), - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Println("running cmd: ", cmd) - log.Fatal("failed getting rewards: ", err, "\n", string(bz)) - } - - 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 res -} - -func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { - valCfg := tr.ValidatorConfigs[validator] - valDelAddress := valCfg.DelAddress - if chain != ChainID("provi") { - // use binary with Bech32Prefix set to ConsumerAccountPrefix - if valCfg.UseConsumerKey { - valDelAddress = valCfg.ConsumerDelAddress - } else { - // use the same address as on the provider but with different prefix - valDelAddress = valCfg.DelAddressOnConsumer - } - } - - binaryName := tr.ChainConfigs[chain].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - - "query", "bank", "balances", - valDelAddress, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal("getBalance() failed: ", cmd, ": ", err, "\n", string(bz)) - } - - amount := gjson.Get(string(bz), `balances.#(denom=="stake").amount`) - - return uint(amount.Uint()) -} - -// interchain-securityd query gov proposals -func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { - noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) - - binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - - "query", "gov", "proposal", - fmt.Sprint(proposal), - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - - prop := TextProposal{} - - if err != nil { - if noProposalRegex.Match(bz) { - return prop - } - - log.Fatal(err, "\n", string(bz)) - } - - propType := gjson.Get(string(bz), `messages.0.content.@type`).String() - deposit := gjson.Get(string(bz), `total_deposit.#(denom=="stake").amount`).Uint() - status := gjson.Get(string(bz), `status`).String() - - // This is a breaking change in the query output for proposals: bug in SDK?? - proposal_value, exists := gov.ProposalStatus_value[status] - if !exists { - panic("invalid proposal status value: " + status) - } - status = strconv.Itoa(int(proposal_value)) - - chainConfigs := tr.ChainConfigs - containerConfig := tr.ContainerConfig - - switch propType { - case "/cosmos.gov.v1beta1.TextProposal": - title := gjson.Get(string(bz), `content.title`).String() - description := gjson.Get(string(bz), `content.description`).String() - - return TextProposal{ - Deposit: uint(deposit), - Status: status, - Title: title, - Description: description, - } - case "/interchain_security.ccv.provider.v1.ConsumerAdditionProposal": - chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() - spawnTime := gjson.Get(string(bz), `messages.0.content.spawn_time`).Time().Sub(containerConfig.Now) - - var chain ChainID - for i, conf := range chainConfigs { - if string(conf.ChainId) == chainId { - chain = i - break - } - } - - return ConsumerAdditionProposal{ - Deposit: uint(deposit), - Status: status, - Chain: chain, - SpawnTime: int(spawnTime.Milliseconds()), - InitialHeight: clienttypes.Height{ - RevisionNumber: gjson.Get(string(bz), `messages.0.content.initial_height.revision_number`).Uint(), - RevisionHeight: gjson.Get(string(bz), `messages.0.content.initial_height.revision_height`).Uint(), - }, - } - case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": - height := gjson.Get(string(bz), `messages.0.content.plan.height`).Uint() - title := gjson.Get(string(bz), `messages.0.content.plan.name`).String() - return UpgradeProposal{ - Deposit: uint(deposit), - Status: status, - UpgradeHeight: height, - Title: title, - Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", - } - case "/interchain_security.ccv.provider.v1.ConsumerRemovalProposal": - chainId := gjson.Get(string(bz), `messages.0.content.chain_id`).String() - - var chain ChainID - for i, conf := range chainConfigs { - if string(conf.ChainId) == chainId { - chain = i - break - } - } - - return ConsumerRemovalProposal{ - Deposit: uint(deposit), - Status: status, - Chain: chain, - } - case "/cosmos.params.v1beta1.ParameterChangeProposal": - return ParamsProposal{ - Deposit: uint(deposit), - Status: status, - Subspace: gjson.Get(string(bz), `messages.0.content.changes.0.subspace`).String(), - Key: gjson.Get(string(bz), `messages.0.content.changes.0.key`).String(), - Value: gjson.Get(string(bz), `messages.0.content.changes.0.value`).String(), - } - } - - log.Fatal("unknown proposal type", string(bz)) - - return nil -} - -func (tr Commands) GetValStakedTokens(chain ChainID, valoperAddress string) uint { - binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - - "query", "staking", "validator", - valoperAddress, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - amount := gjson.Get(string(bz), `tokens`) - - return uint(amount.Uint()) -} - -func (tr Commands) GetParam(chain ChainID, param Param) string { - binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - "query", "params", "subspace", - param.Subspace, - param.Key, - - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - value := gjson.Get(string(bz), `value`) - - return value.String() -} - -// GetConsumerChains returns a list of consumer chains that're being secured by the provider chain, -// determined by querying the provider chain. -func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { - binaryName := tr.ChainConfigs[chain].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - "query", "provider", "list-consumer-chains", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - arr := gjson.Get(string(bz), "chains").Array() - chains := make(map[ChainID]bool) - for _, c := range arr { - id := c.Get("chain_id").String() - chains[ChainID(id)] = true - } - - return chains -} - -func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { - binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - - "query", "provider", "validator-consumer-key", - string(consumerChain), tr.ValidatorConfigs[validator].ValconsAddress, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - addr := gjson.Get(string(bz), "consumer_address").String() - return addr -} - -func (tr Commands) GetProviderAddressFromConsumer(consumerChain ChainID, validator ValidatorID) string { - binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - - "query", "provider", "validator-provider-key", - string(consumerChain), tr.ValidatorConfigs[validator].ConsumerValconsAddressOnProvider, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Println("error running ", cmd) - log.Fatal(err, "\n", string(bz)) - } - - addr := gjson.Get(string(bz), "provider_address").String() - return addr -} - -func (tr Commands) GetSlashMeter() int64 { - binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - - "query", "provider", "throttle-state", - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - slashMeter := gjson.Get(string(bz), "slash_meter") - return slashMeter.Int() -} - -func (tr Commands) GetRegisteredConsumerRewardDenoms(chain ChainID) []string { - binaryName := tr.ChainConfigs[chain].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - "query", "provider", "registered-consumer-reward-denoms", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - denoms := gjson.Get(string(bz), "denoms").Array() - rewardDenoms := make([]string, len(denoms)) - for i, d := range denoms { - rewardDenoms[i] = d.String() - } - - return rewardDenoms -} - -func (tr Commands) GetPendingPacketQueueSize(chain ChainID) uint { - binaryName := tr.ChainConfigs[chain].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - "query", "ccvconsumer", "throttle-state", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ) - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - if !gjson.ValidBytes(bz) { - panic("invalid json response from query ccvconsumer throttle-state: " + string(bz)) - } - - packetData := gjson.Get(string(bz), "packet_data_queue").Array() - return uint(len(packetData)) -} - -func (tr Commands) GetValidatorIP(chain ChainID, validator ValidatorID) string { - return tr.ChainConfigs[chain].IpPrefix + "." + tr.ValidatorConfigs[validator].IpSuffix -} - -// getQueryNode returns query node tcp address on chain. -func (tr Commands) GetQueryNode(chain ChainID) string { - return fmt.Sprintf("tcp://%s", tr.GetQueryNodeRPCAddress(chain)) -} - -func (tr Commands) GetQueryNodeRPCAddress(chain ChainID) string { - return fmt.Sprintf("%s:26658", tr.GetQueryNodeIP(chain)) -} - -// getQueryNodeIP returns query node IP for chain, -// ipSuffix is hardcoded to be 253 on all query nodes -// except for "sover" chain where there's only one node -func (tr Commands) GetQueryNodeIP(chain ChainID) string { - if chain == ChainID("sover") { - // return address of first and only validator - return fmt.Sprintf("%s.%s", - tr.ChainConfigs[chain].IpPrefix, - tr.ValidatorConfigs[ValidatorID("alice")].IpSuffix) - } - return fmt.Sprintf("%s.253", tr.ChainConfigs[chain].IpPrefix) -} - -// GetClientFrozenHeight returns the frozen height for a client with the given client ID -// by querying the hosting chain with the given chainID -func (tr Commands) GetClientFrozenHeight(chain ChainID, clientID string) (uint64, uint64) { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - // cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[ChainID("provi")].BinaryName, - binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName - cmd := tr.Target.ExecCommand(binaryName, - "query", "ibc", "client", "state", clientID, - `--node`, tr.GetQueryNode(ChainID("provi")), - `-o`, `json`, - ) - - bz, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - frozenHeight := gjson.Get(string(bz), "client_state.frozen_height") - - revHeight, err := strconv.Atoi(frozenHeight.Get("revision_height").String()) - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - revNumber, err := strconv.Atoi(frozenHeight.Get("revision_number").String()) - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - return uint64(revHeight), uint64(revNumber) -} - -func (tr Commands) GetTrustedHeight( - chain ChainID, - clientID string, - index int, -) (uint64, uint64) { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - // configureNodeCmd := exec.Command("docker", "exec", tc.testConfig.containerConfig.InstanceName, "hermes", - configureNodeCmd := tr.Target.ExecCommand("hermes", - "--json", "query", "client", "consensus", "--chain", string(chain), - `--client`, clientID, - ) - - cmdReader, err := configureNodeCmd.StdoutPipe() - if err != nil { - log.Fatal(err) - } - - configureNodeCmd.Stderr = configureNodeCmd.Stdout - - if err := configureNodeCmd.Start(); err != nil { - log.Fatal(err) - } - - scanner := bufio.NewScanner(cmdReader) - - var trustedHeight gjson.Result - // iterate on the relayer's response - // and parse the the command "result" - for scanner.Scan() { - out := scanner.Text() - if len(gjson.Get(out, "result").Array()) > 0 { - trustedHeight = gjson.Get(out, "result").Array()[index] - break - } - } - - revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) - if err != nil { - log.Fatal(err) - } - - revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) - if err != nil { - log.Fatal(err) - } - return uint64(revHeight), uint64(revNumber) -} - -func (tr Commands) GetProposedConsumerChains(chain ChainID) []string { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - // bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, - binaryName := tr.ChainConfigs[chain].BinaryName - bz, err := tr.Target.ExecCommand(binaryName, - "query", "provider", "list-proposed-consumer-chains", - `--node`, tr.GetQueryNode(chain), - `-o`, `json`, - ).CombinedOutput() - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - arr := gjson.Get(string(bz), "proposedChains").Array() - chains := []string{} - for _, c := range arr { - cid := c.Get("chainID").String() - chains = append(chains, cid) - } - - return chains -} - -// Breaking forward compatibility -func (tr Commands) GetIBCTransferParams(chain ChainID) IBCTransferParams { - panic("'GetIBCTransferParams' is not implemented in this version") -} - -func (tr Commands) GetHasToValidate(validator ValidatorID) []ChainID { - panic("'GetHasToValidate' is not implemented in this version") -} - -func (tr Commands) GetConsumerCommissionRate(chain ChainID, validator ValidatorID) float64 { - panic("'GetConsumerCommissionRate' is not implemented in this version") -} - -func (tr Commands) GetInflationRate( - chain ChainID, -) float64 { - panic("'GetInflationRate' is not implemented in this version") -} diff --git a/tests/mbt/driver/setup.go b/tests/mbt/driver/setup.go index 5cd4bfa121..9bf6459bf9 100644 --- a/tests/mbt/driver/setup.go +++ b/tests/mbt/driver/setup.go @@ -491,7 +491,6 @@ 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 92510ecb74..3cc5b69111 100644 --- a/x/ccv/consumer/keeper/distribution.go +++ b/x/ccv/consumer/keeper/distribution.go @@ -117,11 +117,6 @@ 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 @@ -138,7 +133,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: rewardMemo, + Memo: "consumer chain rewards distribution", } // validate MsgTransfer before calling Transfer() diff --git a/x/ccv/consumer/keeper/params.go b/x/ccv/consumer/keeper/params.go index 84d85f2098..8d74205b8a 100644 --- a/x/ccv/consumer/keeper/params.go +++ b/x/ccv/consumer/keeper/params.go @@ -136,8 +136,3 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration { k.paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &period) return period } - -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 801f96478d..536ae98236 100644 --- a/x/ccv/consumer/keeper/params_test.go +++ b/x/ccv/consumer/keeper/params_test.go @@ -31,7 +31,6 @@ 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) @@ -39,7 +38,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, "1") + 7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour) 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 deleted file mode 100644 index debfe832f6..0000000000 --- a/x/ccv/consumer/migrations/v3/legacy_params.go +++ /dev/null @@ -1,108 +0,0 @@ -package v3 - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - - ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" -) - -// Legacy: used for migration only! -// GetConsumerParamsLegacy returns the params for the consumer ccv module from legacy subspace -func GetConsumerParamsLegacy(ctx sdk.Context, paramSpace ccvtypes.LegacyParamSubspace) ccvtypes.ConsumerParams { - return ccvtypes.NewParams( - getEnabled(ctx, paramSpace), - getBlocksPerDistributionTransmission(ctx, paramSpace), - getDistributionTransmissionChannel(ctx, paramSpace), - getProviderFeePoolAddrStr(ctx, paramSpace), - getCCVTimeoutPeriod(ctx, paramSpace), - getTransferTimeoutPeriod(ctx, paramSpace), - getConsumerRedistributionFrac(ctx, paramSpace), - getHistoricalEntries(ctx, paramSpace), - getUnbondingPeriod(ctx, paramSpace), - getRewardDenoms(ctx, paramSpace), - getProviderRewardDenoms(ctx, paramSpace), - getRetryDelayPeriod(ctx, paramSpace), - "0", - ) -} - -// getEnabled returns the enabled flag for the consumer module -func getEnabled(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) bool { - var enabled bool - paramStore.Get(ctx, ccvtypes.KeyEnabled, &enabled) - return enabled -} - -func getBlocksPerDistributionTransmission(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) int64 { - var bpdt int64 - paramStore.Get(ctx, ccvtypes.KeyBlocksPerDistributionTransmission, &bpdt) - return bpdt -} - -func getDistributionTransmissionChannel(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { - var s string - paramStore.Get(ctx, ccvtypes.KeyDistributionTransmissionChannel, &s) - return s -} - -func getProviderFeePoolAddrStr(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { - var s string - paramStore.Get(ctx, ccvtypes.KeyProviderFeePoolAddrStr, &s) - return s -} - -// getCCVTimeoutPeriod returns the timeout period for sent ccv related ibc packets -func getCCVTimeoutPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { - var p time.Duration - paramStore.Get(ctx, ccvtypes.KeyCCVTimeoutPeriod, &p) - return p -} - -// getTransferTimeoutPeriod returns the timeout period for sent transfer related ibc packets -func getTransferTimeoutPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { - var p time.Duration - paramStore.Get(ctx, ccvtypes.KeyTransferTimeoutPeriod, &p) - return p -} - -// getConsumerRedistributionFrac returns the fraction of tokens allocated to the consumer redistribution -// address during distribution events. The fraction is a string representing a -// decimal number. For example "0.75" would represent 75%. -func getConsumerRedistributionFrac(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) string { - var str string - paramStore.Get(ctx, ccvtypes.KeyConsumerRedistributionFrac, &str) - return str -} - -// getHistoricalEntries returns the number of historical info entries to persist in store -func getHistoricalEntries(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) int64 { - var n int64 - paramStore.Get(ctx, ccvtypes.KeyHistoricalEntries, &n) - return n -} - -func getUnbondingPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { - var period time.Duration - paramStore.Get(ctx, ccvtypes.KeyConsumerUnbondingPeriod, &period) - return period -} - -func getRewardDenoms(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) []string { - var denoms []string - paramStore.Get(ctx, ccvtypes.KeyRewardDenoms, &denoms) - return denoms -} - -func getProviderRewardDenoms(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) []string { - var denoms []string - paramStore.Get(ctx, ccvtypes.KeyProviderRewardDenoms, &denoms) - return denoms -} - -func getRetryDelayPeriod(ctx sdk.Context, paramStore ccvtypes.LegacyParamSubspace) time.Duration { - var period time.Duration - paramStore.Get(ctx, ccvtypes.KeyRetryDelayPeriod, &period) - return period -} diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index 7e62574b3b..c8c61776ec 100644 --- a/x/ccv/consumer/types/genesis_test.go +++ b/x/ccv/consumer/types/genesis_test.go @@ -233,7 +233,6 @@ func TestValidateInitialGenesisState(t *testing.T) { []string{}, []string{}, ccv.DefaultRetryDelayPeriod, - "1", )), true, }, @@ -253,7 +252,6 @@ func TestValidateInitialGenesisState(t *testing.T) { []string{}, []string{}, ccv.DefaultRetryDelayPeriod, - "1", )), true, }, @@ -459,7 +457,6 @@ 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 e76422266d..8e81e2301c 100644 --- a/x/ccv/consumer/types/params_test.go +++ b/x/ccv/consumer/types/params_test.go @@ -11,8 +11,6 @@ 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 @@ -21,67 +19,59 @@ 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, consumerId), true, + ccvtypes.NewParams(true, 5, "", "", 1004, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, -5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "badchannel/", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", -5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 1004, -7, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "-0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "1.2", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "notFrac", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", -100, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, -24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"u"}, []string{}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{"a"}, 2*time.Hour), 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, consumerId), false, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, -2*time.Hour), 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, 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, + ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, 0), false, }, } diff --git a/x/ccv/provider/ibc_middleware.go b/x/ccv/provider/ibc_middleware.go index 1dedd695be..ef0837ae5b 100644 --- a/x/ccv/provider/ibc_middleware.go +++ b/x/ccv/provider/ibc_middleware.go @@ -12,14 +12,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" -<<<<<<< HEAD "github.com/cosmos/interchain-security/v4/x/ccv/provider/keeper" "github.com/cosmos/interchain-security/v4/x/ccv/provider/types" -======= - "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" ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) var _ porttypes.Middleware = &IBCMiddleware{} @@ -127,15 +121,12 @@ func (im IBCMiddleware) OnRecvPacket( // that the packet data is valid and can be safely // deserialized without checking errors. if ack.Success() { -<<<<<<< HEAD // execute the middleware logic only if the sender is a consumer chain consumerID, err := im.keeper.IdentifyConsumerChainIDFromIBCPacket(ctx, packet) if err != nil { return ack } -======= ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // extract the coin info received from the packet data var data ibctransfertypes.FungibleTokenPacketData _ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data) @@ -146,68 +137,8 @@ func (im IBCMiddleware) OnRecvPacket( return ack } -<<<<<<< HEAD coinAmt, _ := math.NewIntFromString(data.Amount) coinDenom := GetProviderDenom(data.Denom, packet) -======= - 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( - "cannot get consumer chain id in transfer middleware", - "consumerId", consumerId, - "packet", packet.String(), - "fungibleTokenPacketData", data.String(), - "error", err.Error(), - ) - return ack - } - - logger.Info( - "received ICS rewards from consumer chain", - "consumerId", consumerId, - "chainId", chainId, - "denom", coinDenom, - "amount", data.Amount, - ) - - // initialize an empty slice to store event attributes - eventAttributes := []sdk.Attribute{} - - // add event attributes - eventAttributes = append(eventAttributes, []sdk.Attribute{ - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeConsumerId, consumerId), - sdk.NewAttribute(types.AttributeConsumerChainId, chainId), - sdk.NewAttribute(types.AttributeRewardDenom, coinDenom), - sdk.NewAttribute(types.AttributeRewardAmount, data.Amount), - }...) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // verify that the coin's denom is a whitelisted consumer denom, // and if so, adds it to the consumer chain rewards allocation, diff --git a/x/ccv/provider/keeper/consumer_lifecycle.go b/x/ccv/provider/keeper/consumer_lifecycle.go deleted file mode 100644 index c578df4ff2..0000000000 --- a/x/ccv/provider/keeper/consumer_lifecycle.go +++ /dev/null @@ -1,663 +0,0 @@ -package keeper - -import ( - "fmt" - "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" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - 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/v6/x/ccv/provider/types" - ccv "github.com/cosmos/interchain-security/v6/x/ccv/types" -) - -// PrepareConsumerForLaunch prepares to move the launch of a consumer chain from the previous spawn time to spawn time. -// Previous spawn time can correspond to its zero value if the validator was not previously set for launch. -func (k Keeper) PrepareConsumerForLaunch(ctx sdk.Context, consumerId string, previousSpawnTime, spawnTime time.Time) error { - if !previousSpawnTime.IsZero() { - // if this is not the first initialization and hence `previousSpawnTime` does not contain the zero value of `Time` - // remove the consumer id from the previous spawn time - err := k.RemoveConsumerToBeLaunched(ctx, consumerId, previousSpawnTime) - if err != nil { - return err - } - } - return k.AppendConsumerToBeLaunched(ctx, consumerId, spawnTime) -} - -// InitializeConsumer tries to move a consumer with `consumerId` to the initialized phase. -// If successful, it returns the spawn time and true. -func (k Keeper) InitializeConsumer(ctx sdk.Context, consumerId string) (time.Time, bool) { - // a chain needs to be in the registered or initialized phase - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.CONSUMER_PHASE_REGISTERED && phase != types.CONSUMER_PHASE_INITIALIZED { - return time.Time{}, false - } - - initializationParameters, err := k.GetConsumerInitializationParameters(ctx, consumerId) - if err != nil { - return time.Time{}, false - } - - // the spawn time needs to be positive - if initializationParameters.SpawnTime.IsZero() { - return time.Time{}, false - } - - k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_INITIALIZED) - - return initializationParameters.SpawnTime, true -} - -// BeginBlockLaunchConsumers launches initialized consumers chains for which the spawn time has passed -func (k Keeper) BeginBlockLaunchConsumers(ctx sdk.Context) error { - bondedValidators := []stakingtypes.Validator{} - activeValidators := []stakingtypes.Validator{} - - consumerIds, err := k.ConsumeIdsFromTimeQueue( - ctx, - types.SpawnTimeToConsumerIdsKeyPrefix(), - k.GetConsumersToBeLaunched, - k.DeleteAllConsumersToBeLaunched, - k.AppendConsumerToBeLaunched, - 200, - ) - if err != nil { - return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "getting consumers ready to laumch: %s", err.Error()) - } - if len(consumerIds) > 0 { - // get the bonded validators from the staking module - bondedValidators, err = k.GetLastBondedValidators(ctx) - if err != nil { - return fmt.Errorf("getting last bonded validators: %w", err) - } - // get the provider active validators - activeValidators, err = k.GetLastProviderConsensusActiveValidators(ctx) - if err != nil { - return fmt.Errorf("getting last provider active validators: %w", err) - } - } - - for _, consumerId := range consumerIds { - cachedCtx, writeFn := ctx.CacheContext() - err = k.LaunchConsumer(cachedCtx, bondedValidators, activeValidators, consumerId) - if err != nil { - ctx.Logger().Error("could not launch chain", - "consumerId", consumerId, - "error", err) - - // reset spawn time to zero so that owner can try again later - initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) - if err != nil { - return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, - "getting initialization parameters, consumerId(%s): %s", consumerId, err.Error()) - } - initializationRecord.SpawnTime = time.Time{} - err = k.SetConsumerInitializationParameters(ctx, consumerId, initializationRecord) - if err != nil { - return fmt.Errorf("setting consumer initialization parameters, consumerId(%s): %w", consumerId, err) - } - // also set the phase to registered - k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_REGISTERED) - - continue - } - - writeFn() - } - return nil -} - -// ConsumeIdsFromTimeQueue returns from a time queue the consumer ids for which the associated time passed. -// The number of ids return is limited to 'limit'. The ids returned are removed from the time queue. -func (k Keeper) ConsumeIdsFromTimeQueue( - ctx sdk.Context, - timeQueueKeyPrefix byte, - getIds func(sdk.Context, time.Time) (types.ConsumerIds, error), - deleteAllIds func(sdk.Context, time.Time), - appendId func(sdk.Context, string, time.Time) error, - limit int, -) ([]string, error) { - store := ctx.KVStore(k.storeKey) - - result := []string{} - nextTime := []string{} - timestampsToDelete := []time.Time{} - - iterator := storetypes.KVStorePrefixIterator(store, []byte{timeQueueKeyPrefix}) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - if len(result) >= limit { - break - } - ts, err := types.ParseTime(timeQueueKeyPrefix, iterator.Key()) - if err != nil { - return result, fmt.Errorf("parsing removal time: %w", err) - } - if ts.After(ctx.BlockTime()) { - break - } - - consumerIds, err := getIds(ctx, ts) - if err != nil { - return result, - fmt.Errorf("getting consumers ids, ts(%s): %w", ts.String(), err) - } - - timestampsToDelete = append(timestampsToDelete, ts) - - availableSlots := limit - len(result) - if availableSlots >= len(consumerIds.Ids) { - // consumer all the ids - result = append(result, consumerIds.Ids...) - } else { - // consume only availableSlots - result = append(result, consumerIds.Ids[:availableSlots]...) - // and leave the others for next time - nextTime = consumerIds.Ids[availableSlots:] - break - } - } - - // remove consumers to prevent handling them twice - for i, ts := range timestampsToDelete { - deleteAllIds(ctx, ts) - if i == len(timestampsToDelete)-1 { - // for the last ts consumed, store back the ids for later - for _, consumerId := range nextTime { - err := appendId(ctx, consumerId, ts) - if err != nil { - return result, - fmt.Errorf("failed to append consumer id, consumerId(%s), ts(%s): %w", - consumerId, ts.String(), err) - } - } - } - } - - return result, nil -} - -// LaunchConsumer launches the chain with the provided consumer id by creating the consumer client and the respective -// consumer genesis file -// -// TODO add unit test for LaunchConsumer -func (k Keeper) LaunchConsumer( - ctx sdk.Context, - bondedValidators []stakingtypes.Validator, - activeValidators []stakingtypes.Validator, - consumerId string, -) error { - // compute consumer initial validator set - initialValUpdates, err := k.ComputeConsumerNextValSet(ctx, bondedValidators, activeValidators, consumerId, []types.ConsensusValidator{}) - if err != nil { - return fmt.Errorf("computing consumer next validator set, consumerId(%s): %w", consumerId, err) - } - if len(initialValUpdates) == 0 { - return fmt.Errorf("cannot launch consumer with no validator opted in, consumerId(%s)", consumerId) - } - - // create consumer genesis - genesisState, err := k.MakeConsumerGenesis(ctx, consumerId, initialValUpdates) - if err != nil { - return fmt.Errorf("creating consumer genesis state, consumerId(%s): %w", consumerId, err) - } - err = k.SetConsumerGenesis(ctx, consumerId, genesisState) - if err != nil { - return fmt.Errorf("setting consumer genesis state, consumerId(%s): %w", consumerId, err) - } - - // compute the hash of the consumer initial validator updates - updatesAsValSet, err := tmtypes.PB2TM.ValidatorUpdates(initialValUpdates) - if err != nil { - return fmt.Errorf("unable to create initial validator set from initial validator updates: %w", err) - } - valsetHash := tmtypes.NewValidatorSet(updatesAsValSet).Hash() - - // create the consumer client and the genesis - err = k.CreateConsumerClient(ctx, consumerId, valsetHash) - if err != nil { - return fmt.Errorf("crating consumer client, consumerId(%s): %w", consumerId, err) - } - - k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_LAUNCHED) - - k.Logger(ctx).Info("consumer successfully launched", - "consumerId", consumerId, - "valset size", len(initialValUpdates), - "valsetHash", string(valsetHash), - ) - - return nil -} - -// CreateConsumerClient will create the CCV client for the given consumer chain. The CCV channel must be built -// on top of the CCV client to ensure connection with the right consumer chain. -func (k Keeper) CreateConsumerClient( - ctx sdk.Context, - consumerId string, - valsetHash []byte, -) error { - initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) - if err != nil { - return err - } - - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.CONSUMER_PHASE_INITIALIZED { - return errorsmod.Wrapf(types.ErrInvalidPhase, - "cannot create client for consumer chain that is not in the Initialized phase but in phase %d: %s", phase, consumerId) - } - - chainId, err := k.GetConsumerChainId(ctx, consumerId) - if err != nil { - return err - } - - // Set minimum height for equivocation evidence from this consumer chain - k.SetEquivocationEvidenceMinHeight(ctx, consumerId, initializationRecord.InitialHeight.RevisionHeight) - - // Consumers start out with the unbonding period from the initialization parameters - consumerUnbondingPeriod := initializationRecord.UnbondingPeriod - - // Create client state by getting template client from initialization parameters - clientState := k.GetTemplateClient(ctx) - clientState.ChainId = chainId - clientState.LatestHeight = initializationRecord.InitialHeight - - trustPeriod, err := ccv.CalculateTrustPeriod(consumerUnbondingPeriod, k.GetTrustingPeriodFraction(ctx)) - if err != nil { - return err - } - clientState.TrustingPeriod = trustPeriod - clientState.UnbondingPeriod = consumerUnbondingPeriod - - // Create consensus state - consensusState := ibctmtypes.NewConsensusState( - ctx.BlockTime(), - commitmenttypes.NewMerkleRoot([]byte(ibctmtypes.SentinelRoot)), - valsetHash, - ) - - clientID, err := k.clientKeeper.CreateClient(ctx, clientState, consensusState) - if err != nil { - return err - } - k.SetConsumerClientId(ctx, consumerId, clientID) - - k.Logger(ctx).Info("consumer client created", - "consumer id", consumerId, - "client id", clientID, - ) - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeConsumerClientCreated, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeConsumerId, consumerId), - sdk.NewAttribute(types.AttributeConsumerChainId, chainId), - sdk.NewAttribute(clienttypes.AttributeKeyClientID, clientID), - sdk.NewAttribute(types.AttributeInitialHeight, initializationRecord.InitialHeight.String()), - sdk.NewAttribute(types.AttributeTrustingPeriod, clientState.TrustingPeriod.String()), - sdk.NewAttribute(types.AttributeUnbondingPeriod, clientState.UnbondingPeriod.String()), - sdk.NewAttribute(types.AttributeValsetHash, string(valsetHash)), - ), - ) - - return nil -} - -// MakeConsumerGenesis returns the created consumer genesis state for consumer chain `consumerId`, -// as well as the validator hash of the initial validator set of the consumer chain -func (k Keeper) MakeConsumerGenesis( - ctx sdk.Context, - consumerId string, - initialValidatorUpdates []abci.ValidatorUpdate, -) (gen ccv.ConsumerGenesisState, err error) { - initializationRecord, err := k.GetConsumerInitializationParameters(ctx, consumerId) - if err != nil { - return gen, errorsmod.Wrapf(ccv.ErrInvalidConsumerState, - "getting initialization parameters, consumerId(%s): %s", consumerId, err.Error()) - } - // note that providerFeePoolAddrStr is sent to the consumer during the IBC Channel handshake; - // see HandshakeMetadata in OnChanOpenTry on the provider-side, and OnChanOpenAck on the consumer-side - consumerGenesisParams := ccv.NewParams( - true, - initializationRecord.BlocksPerDistributionTransmission, - initializationRecord.DistributionTransmissionChannel, - "", // providerFeePoolAddrStr, - initializationRecord.CcvTimeoutPeriod, - initializationRecord.TransferTimeoutPeriod, - initializationRecord.ConsumerRedistributionFraction, - initializationRecord.HistoricalEntries, - initializationRecord.UnbondingPeriod, - []string{}, - []string{}, - ccv.DefaultRetryDelayPeriod, - consumerId, - ) - - // create provider client state and consensus state for the consumer to be able - // to create a provider client - - providerUnbondingPeriod, err := k.stakingKeeper.UnbondingTime(ctx) - if err != nil { - return gen, errorsmod.Wrapf(types.ErrNoUnbondingTime, "unbonding time not found: %s", err) - } - height := clienttypes.GetSelfHeight(ctx) - - clientState := k.GetTemplateClient(ctx) - // this is the counter party chain ID for the consumer - clientState.ChainId = ctx.ChainID() - // this is the latest height the client was updated at, i.e., - // the height of the latest consensus state (see below) - clientState.LatestHeight = height - trustPeriod, err := ccv.CalculateTrustPeriod(providerUnbondingPeriod, k.GetTrustingPeriodFraction(ctx)) - if err != nil { - return gen, errorsmod.Wrapf(sdkerrors.ErrInvalidHeight, "error %s calculating trusting_period for: %s", err, height) - } - clientState.TrustingPeriod = trustPeriod - clientState.UnbondingPeriod = providerUnbondingPeriod - - consState, err := k.clientKeeper.GetSelfConsensusState(ctx, height) - if err != nil { - return gen, errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "error %s getting self consensus state for: %s", err, height) - } - - gen = *ccv.NewInitialConsumerGenesisState( - clientState, - consState.(*ibctmtypes.ConsensusState), - initialValidatorUpdates, - consumerGenesisParams, - ) - return gen, nil -} - -// StopAndPrepareForConsumerRemoval sets the phase of the chain to stopped and prepares to get the state of the -// chain removed after unbonding period elapses -func (k Keeper) StopAndPrepareForConsumerRemoval(ctx sdk.Context, consumerId string) error { - // The phase of the chain is immediately set to stopped, albeit its state is removed later (see below). - // Setting the phase here helps in not considering this chain when we look at launched chains (e.g., in `QueueVSCPackets) - k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_STOPPED) - - // state of this chain is removed once UnbondingPeriod elapses - unbondingPeriod, err := k.stakingKeeper.UnbondingTime(ctx) - if err != nil { - return err - } - removalTime := ctx.BlockTime().Add(unbondingPeriod) - - if err := k.SetConsumerRemovalTime(ctx, consumerId, removalTime); err != nil { - return fmt.Errorf("cannot set removal time (%s): %s", removalTime.String(), err.Error()) - } - if err := k.AppendConsumerToBeRemoved(ctx, consumerId, removalTime); err != nil { - return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "cannot set consumer to be removed: %s", err.Error()) - } - - return nil -} - -// BeginBlockRemoveConsumers removes stopped consumer chain for which the removal time has passed -func (k Keeper) BeginBlockRemoveConsumers(ctx sdk.Context) error { - consumerIds, err := k.ConsumeIdsFromTimeQueue( - ctx, - types.RemovalTimeToConsumerIdsKeyPrefix(), - k.GetConsumersToBeRemoved, - k.DeleteAllConsumersToBeRemoved, - k.AppendConsumerToBeRemoved, - 200, - ) - if err != nil { - return errorsmod.Wrapf(ccv.ErrInvalidConsumerState, "getting consumers ready to stop: %s", err.Error()) - } - for _, consumerId := range consumerIds { - // delete consumer chain in a cached context to abort deletion in case of errors - cachedCtx, writeFn := ctx.CacheContext() - err = k.DeleteConsumerChain(cachedCtx, consumerId) - if err != nil { - k.Logger(ctx).Error("consumer chain could not be removed", - "consumerId", consumerId, - "error", err.Error()) - continue - } - - writeFn() - } - return nil -} - -// DeleteConsumerChain cleans up the state of the given consumer chain -func (k Keeper) DeleteConsumerChain(ctx sdk.Context, consumerId string) (err error) { - phase := k.GetConsumerPhase(ctx, consumerId) - if phase != types.CONSUMER_PHASE_STOPPED { - return fmt.Errorf("cannot delete non-stopped chain: %s", consumerId) - } - - // clean up states - k.DeleteConsumerClientId(ctx, consumerId) - k.DeleteConsumerGenesis(ctx, consumerId) - // Note: this call panics if the key assignment state is invalid - k.DeleteKeyAssignments(ctx, consumerId) - k.DeleteMinimumPowerInTopN(ctx, consumerId) - k.DeleteEquivocationEvidenceMinHeight(ctx, consumerId) - - // close channel and delete the mappings between chain ID and channel ID - if channelID, found := k.GetConsumerIdToChannelId(ctx, consumerId); found { - // Close the channel for the given channel ID on the condition - // that the channel exists and isn't already in the CLOSED state - channel, found := k.channelKeeper.GetChannel(ctx, ccv.ProviderPortID, channelID) - if found && channel.State != channeltypes.CLOSED { - err := k.chanCloseInit(ctx, channelID) - if err != nil { - k.Logger(ctx).Error("channel to consumer chain could not be closed", - "consumerId", consumerId, - "channelID", channelID, - "error", err.Error(), - ) - } - } - k.DeleteConsumerIdToChannelId(ctx, consumerId) - k.DeleteChannelIdToConsumerId(ctx, channelID) - } - - // delete consumer commission rate - provAddrs := k.GetAllCommissionRateValidators(ctx, consumerId) - for _, addr := range provAddrs { - k.DeleteConsumerCommissionRate(ctx, consumerId, addr) - } - - k.DeleteInitChainHeight(ctx, consumerId) - k.DeleteSlashAcks(ctx, consumerId) - k.DeletePendingVSCPackets(ctx, consumerId) - - k.DeleteAllowlist(ctx, consumerId) - k.DeleteDenylist(ctx, consumerId) - k.DeleteAllOptedIn(ctx, consumerId) - k.DeleteConsumerValSet(ctx, consumerId) - - k.DeleteConsumerRewardsAllocation(ctx, consumerId) - k.DeleteConsumerRemovalTime(ctx, consumerId) - - // TODO (PERMISSIONLESS) add newly-added state to be deleted - - // Note that we do not delete ConsumerIdToChainIdKey and ConsumerIdToPhase, as well - // as consumer metadata, initialization and power-shaping parameters. - // This is to enable block explorers and front ends to show information of - // consumer chains that were removed without needing an archive node. - - k.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_DELETED) - k.Logger(ctx).Info("consumer chain deleted from provider", "consumerId", consumerId) - - return nil -} - -// -// Setters and Getters -// - -// GetConsumerRemovalTime returns the removal time associated with the to-be-removed chain with consumer id -func (k Keeper) GetConsumerRemovalTime(ctx sdk.Context, consumerId string) (time.Time, error) { - store := ctx.KVStore(k.storeKey) - buf := store.Get(types.ConsumerIdToRemovalTimeKey(consumerId)) - if buf == nil { - return time.Time{}, fmt.Errorf("failed to retrieve removal time for consumer id (%s)", consumerId) - } - var time time.Time - if err := time.UnmarshalBinary(buf); err != nil { - return time, fmt.Errorf("failed to unmarshal removal time for consumer id (%s): %w", consumerId, err) - } - return time, nil -} - -// SetConsumerRemovalTime sets the removal time associated with this consumer id -func (k Keeper) SetConsumerRemovalTime(ctx sdk.Context, consumerId string, removalTime time.Time) error { - store := ctx.KVStore(k.storeKey) - buf, err := removalTime.MarshalBinary() - if err != nil { - return fmt.Errorf("failed to marshal removal time (%+v) for consumer id (%s): %w", removalTime, consumerId, err) - } - store.Set(types.ConsumerIdToRemovalTimeKey(consumerId), buf) - return nil -} - -// DeleteConsumerRemovalTime deletes the removal time associated with this consumer id -func (k Keeper) DeleteConsumerRemovalTime(ctx sdk.Context, consumerId string) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.ConsumerIdToRemovalTimeKey(consumerId)) -} - -// getConsumerIdsBasedOnTime returns all the consumer ids stored under this specific `key(time)` -func (k Keeper) getConsumerIdsBasedOnTime(ctx sdk.Context, key func(time.Time) []byte, time time.Time) (types.ConsumerIds, error) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(key(time)) - if bz == nil { - return types.ConsumerIds{}, nil - } - - var consumerIds types.ConsumerIds - - if err := consumerIds.Unmarshal(bz); err != nil { - return types.ConsumerIds{}, fmt.Errorf("failed to unmarshal consumer ids: %w", err) - } - return consumerIds, nil -} - -// appendConsumerIdOnTime appends the consumer id on all the other consumer ids under `key(time)` -func (k Keeper) appendConsumerIdOnTime(ctx sdk.Context, consumerId string, key func(time.Time) []byte, time time.Time) error { - store := ctx.KVStore(k.storeKey) - - consumers, err := k.getConsumerIdsBasedOnTime(ctx, key, time) - if err != nil { - return err - } - - consumersWithAppend := types.ConsumerIds{ - Ids: append(consumers.Ids, consumerId), - } - - bz, err := consumersWithAppend.Marshal() - if err != nil { - return err - } - - store.Set(key(time), bz) - return nil -} - -// removeConsumerIdFromTime removes consumer id stored under `key(time)` -func (k Keeper) removeConsumerIdFromTime(ctx sdk.Context, consumerId string, key func(time.Time) []byte, time time.Time) error { - store := ctx.KVStore(k.storeKey) - - consumers, err := k.getConsumerIdsBasedOnTime(ctx, key, time) - if err != nil { - return err - } - - if len(consumers.Ids) == 0 { - return fmt.Errorf("no consumer ids found for this time: %s", time.String()) - } - - // find the index of the consumer we want to remove - index := -1 - for i := 0; i < len(consumers.Ids); i++ { - if consumers.Ids[i] == consumerId { - index = i - break - } - } - - if index == -1 { - return fmt.Errorf("failed to find consumer id (%s)", consumerId) - } - - if len(consumers.Ids) == 1 { - store.Delete(key(time)) - return nil - } - - consumersWithRemoval := types.ConsumerIds{ - Ids: append(consumers.Ids[:index], consumers.Ids[index+1:]...), - } - - bz, err := consumersWithRemoval.Marshal() - if err != nil { - return err - } - - store.Set(key(time), bz) - return nil -} - -// GetConsumersToBeLaunched returns all the consumer ids of chains stored under this spawn time -func (k Keeper) GetConsumersToBeLaunched(ctx sdk.Context, spawnTime time.Time) (types.ConsumerIds, error) { - return k.getConsumerIdsBasedOnTime(ctx, types.SpawnTimeToConsumerIdsKey, spawnTime) -} - -// AppendConsumerToBeLaunched appends the provider consumer id for the given spawn time -func (k Keeper) AppendConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { - return k.appendConsumerIdOnTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) -} - -// RemoveConsumerToBeLaunched removes consumer id from if stored for this specific spawn time -func (k Keeper) RemoveConsumerToBeLaunched(ctx sdk.Context, consumerId string, spawnTime time.Time) error { - return k.removeConsumerIdFromTime(ctx, consumerId, types.SpawnTimeToConsumerIdsKey, spawnTime) -} - -// DeleteAllConsumersToBeLaunched deletes all consumer to be launched at this specific spawn time -func (k Keeper) DeleteAllConsumersToBeLaunched(ctx sdk.Context, spawnTime time.Time) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.SpawnTimeToConsumerIdsKey(spawnTime)) -} - -// GetConsumersToBeRemoved returns all the consumer ids of chains stored under this removal time -func (k Keeper) GetConsumersToBeRemoved(ctx sdk.Context, removalTime time.Time) (types.ConsumerIds, error) { - return k.getConsumerIdsBasedOnTime(ctx, types.RemovalTimeToConsumerIdsKey, removalTime) -} - -// AppendConsumerToBeRemoved appends the provider consumer id for the given removal time -func (k Keeper) AppendConsumerToBeRemoved(ctx sdk.Context, consumerId string, removalTime time.Time) error { - return k.appendConsumerIdOnTime(ctx, consumerId, types.RemovalTimeToConsumerIdsKey, removalTime) -} - -// RemoveConsumerToBeRemoved removes consumer id from the given removal time -func (k Keeper) RemoveConsumerToBeRemoved(ctx sdk.Context, consumerId string, removalTime time.Time) error { - return k.removeConsumerIdFromTime(ctx, consumerId, types.RemovalTimeToConsumerIdsKey, removalTime) -} - -// DeleteAllConsumersToBeRemoved deletes all consumer to be removed at this specific removal time -func (k Keeper) DeleteAllConsumersToBeRemoved(ctx sdk.Context, removalTime time.Time) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.RemovalTimeToConsumerIdsKey(removalTime)) -} diff --git a/x/ccv/provider/keeper/consumer_lifecycle_test.go b/x/ccv/provider/keeper/consumer_lifecycle_test.go deleted file mode 100644 index 6b258ae003..0000000000 --- a/x/ccv/provider/keeper/consumer_lifecycle_test.go +++ /dev/null @@ -1,1046 +0,0 @@ -package keeper_test - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v8/testing" - _go "github.com/cosmos/ics23/go" - "github.com/golang/mock/gomock" - "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" - tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - - cryptotestutil "github.com/cosmos/interchain-security/v6/testutil/crypto" - testkeeper "github.com/cosmos/interchain-security/v6/testutil/keeper" - providerkeeper "github.com/cosmos/interchain-security/v6/x/ccv/provider/keeper" - providertypes "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" - ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types" -) - -func TestPrepareConsumerForLaunch(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - spawnTime := time.Now().UTC() - err := providerKeeper.PrepareConsumerForLaunch(ctx, CONSUMER_ID, time.Time{}, spawnTime) - require.NoError(t, err) - - consumers, err := providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, providertypes.ConsumerIds{Ids: []string{CONSUMER_ID}}, consumers) - - nextSpawnTime := spawnTime.Add(time.Hour) - err = providerKeeper.PrepareConsumerForLaunch(ctx, CONSUMER_ID, spawnTime, nextSpawnTime) - require.NoError(t, err) - - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Empty(t, consumers) - - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, nextSpawnTime) - require.NoError(t, err) - require.Equal(t, providertypes.ConsumerIds{Ids: []string{CONSUMER_ID}}, consumers) -} - -func TestInitializeConsumer(t *testing.T) { - now := time.Now().UTC() - consumerId := "13" - - testCases := []struct { - name string - spawnTime time.Time - setup func(*providerkeeper.Keeper, sdk.Context, time.Time) - expInitialized bool - }{ - { - name: "valid", - spawnTime: now, - setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { - pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) - err := pk.SetConsumerInitializationParameters(ctx, consumerId, - providertypes.ConsumerInitializationParameters{ - SpawnTime: spawnTime, - }) - require.NoError(t, err) - }, - expInitialized: true, - }, - { - name: "invalid: no phase", - spawnTime: now, - setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { - }, - expInitialized: false, - }, - { - name: "invalid: wrong phase", - spawnTime: now, - setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { - pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_LAUNCHED) - err := pk.SetConsumerInitializationParameters(ctx, consumerId, - providertypes.ConsumerInitializationParameters{ - SpawnTime: spawnTime, - }) - require.NoError(t, err) - }, - expInitialized: false, - }, - { - name: "invalid: no init params", - spawnTime: now, - setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { - pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) - }, - expInitialized: false, - }, - { - name: "invalid: zero spawn time", - spawnTime: now, - setup: func(pk *providerkeeper.Keeper, ctx sdk.Context, spawnTime time.Time) { - pk.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_REGISTERED) - err := pk.SetConsumerInitializationParameters(ctx, consumerId, - providertypes.ConsumerInitializationParameters{ - SpawnTime: time.Time{}, - }) - require.NoError(t, err) - }, - expInitialized: false, - }, - } - - for _, tc := range testCases { - pk, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - tc.setup(&pk, ctx, tc.spawnTime) - - spawnTime, initialized := pk.InitializeConsumer(ctx, consumerId) - require.Equal(t, tc.expInitialized, initialized, tc.name) - if initialized { - require.Equal(t, tc.spawnTime, spawnTime, tc.name) - require.Equal(t, providertypes.CONSUMER_PHASE_INITIALIZED, pk.GetConsumerPhase(ctx, consumerId)) - } - } -} - -// TestBeginBlockInit directly tests BeginBlockLaunchConsumers against the spec using helpers defined above. -func TestBeginBlockLaunchConsumers(t *testing.T) { - now := time.Now().UTC() - - keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - providerKeeper.SetParams(ctx, providertypes.DefaultParams()) - defer ctrl.Finish() - ctx = ctx.WithBlockTime(now) - - // initialize registration, initialization, and update records - chainIds := []string{"chain0", "chain1", "chain2", "chain3", "chain4"} - - initializationParameters := []providertypes.ConsumerInitializationParameters{ - { - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{}, - BinaryHash: []byte{}, - SpawnTime: now.Add(-time.Hour * 2).UTC(), - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - { - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{}, - BinaryHash: []byte{}, - SpawnTime: now.Add(-time.Hour).UTC(), - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - { - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{}, - BinaryHash: []byte{}, - SpawnTime: now.Add(time.Hour).UTC(), - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - { - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{}, - BinaryHash: []byte{}, - SpawnTime: now.Add(-time.Hour).UTC(), - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - { - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{}, - BinaryHash: []byte{}, - SpawnTime: now.Add(-time.Minute).UTC(), - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - } - powerShapingParameters := []providertypes.PowerShapingParameters{ - { - Top_N: 50, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - }, - { - Top_N: 50, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - }, - { - Top_N: 50, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - }, - { - Top_N: 0, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - }, - { - Top_N: 0, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: []string{}, - Denylist: []string{}, - }, - } - - // set up all the records - for i, chainId := range chainIds { - providerKeeper.SetConsumerChainId(ctx, fmt.Sprintf("%d", i), chainId) - } - - for i, r := range initializationParameters { - err := providerKeeper.SetConsumerInitializationParameters(ctx, fmt.Sprintf("%d", i), r) - require.NoError(t, err) - // set up the chains in their initialized phase, hence they could launch - providerKeeper.SetConsumerPhase(ctx, fmt.Sprintf("%d", i), providertypes.CONSUMER_PHASE_INITIALIZED) - err = providerKeeper.AppendConsumerToBeLaunched(ctx, fmt.Sprintf("%d", i), r.SpawnTime) - require.NoError(t, err) - } - for i, r := range powerShapingParameters { - err := providerKeeper.SetConsumerPowerShapingParameters(ctx, fmt.Sprintf("%d", i), r) - require.NoError(t, err) - } - - // opt in a sample validator so the chain's proposal can successfully execute - validator := cryptotestutil.NewCryptoIdentityFromIntSeed(0).SDKStakingValidator() - consAddr, _ := validator.GetConsAddr() - testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 1, []stakingtypes.Validator{validator}, -1) // -1 to allow any number of calls - - valAddr, _ := sdk.ValAddressFromBech32(validator.GetOperator()) - mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), valAddr).Return(int64(1), nil).AnyTimes() - - providerKeeper.SetOptedIn(ctx, "3", providertypes.NewProviderConsAddress(consAddr)) - - // Expect genesis and client creation for only the first, second, and fifth chains (spawn time already passed and valid) - expectedCalls := testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour) - expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain0", clienttypes.NewHeight(3, 4))...) - expectedCalls = append(expectedCalls, testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour)...) - expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain1", clienttypes.NewHeight(3, 4))...) - expectedCalls = append(expectedCalls, testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, time.Hour)...) - expectedCalls = append(expectedCalls, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, "chain3", clienttypes.NewHeight(3, 4))...) - - gomock.InOrder(expectedCalls...) - - err := providerKeeper.BeginBlockLaunchConsumers(ctx) - require.NoError(t, err) - - // first chain was successfully launched - phase := providerKeeper.GetConsumerPhase(ctx, "0") - require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) - _, found := providerKeeper.GetConsumerGenesis(ctx, "0") - require.True(t, found) - - // second chain was successfully launched - phase = providerKeeper.GetConsumerPhase(ctx, "1") - require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) - _, found = providerKeeper.GetConsumerGenesis(ctx, "1") - require.True(t, found) - - // third chain was not launched because its spawn time has not passed - phase = providerKeeper.GetConsumerPhase(ctx, "2") - require.Equal(t, providertypes.CONSUMER_PHASE_INITIALIZED, phase) - _, found = providerKeeper.GetConsumerGenesis(ctx, "2") - require.False(t, found) - - // fourth chain corresponds to an Opt-In chain with one opted-in validator and hence the chain gets - // successfully executed - phase = providerKeeper.GetConsumerPhase(ctx, "3") - require.Equal(t, providertypes.CONSUMER_PHASE_LAUNCHED, phase) - _, found = providerKeeper.GetConsumerGenesis(ctx, "3") - require.True(t, found) - - // fifth chain corresponds to an Opt-In chain with no opted-in validators and hence the - // chain launch is NOT successful - phase = providerKeeper.GetConsumerPhase(ctx, "4") - require.Equal(t, providertypes.CONSUMER_PHASE_REGISTERED, phase) - _, found = providerKeeper.GetConsumerGenesis(ctx, "4") - require.False(t, found) -} - -func TestConsumeIdsFromTimeQueue(t *testing.T) { - expectedConsumerIds := []string{"1", "2", "3", "4"} - timestamps := []time.Time{time.Unix(10, 0), time.Unix(20, 0), time.Unix(30, 0)} - - testCases := []struct { - name string - ts time.Time - limit int - expOutcome func(sdk.Context, []string, func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) - }{ - { - name: "timestamp too early", - ts: time.Unix(9, 999999999), - limit: 3, - expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { - require.Empty(t, ids) - }, - }, - { - name: "first timestamp", - ts: timestamps[0], - limit: 2, - expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { - require.Equal(t, expectedConsumerIds[0:2], ids) - - // check that all consumers where removed - consumerIds, err := getIds(ctx, timestamps[0]) - require.NoError(t, err) - require.Empty(t, consumerIds) - }, - }, - { - name: "first timestamp, with limit", - ts: timestamps[0], - limit: 1, - expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { - require.Equal(t, expectedConsumerIds[0:1], ids) - - // second consumer remained - ret, err := getIds(ctx, timestamps[0]) - require.NoError(t, err) - require.Equal(t, providertypes.ConsumerIds{ - Ids: []string{expectedConsumerIds[1]}, - }, ret) - }, - }, - { - name: "second timestamp", - ts: timestamps[1], - limit: 3, - expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { - require.Equal(t, expectedConsumerIds[0:3], ids) - - // check that all consumers where removed - ret, err := getIds(ctx, timestamps[0]) - require.NoError(t, err) - require.Empty(t, ret) - ret, err = getIds(ctx, timestamps[1]) - require.NoError(t, err) - require.Empty(t, ret) - }, - }, - { - name: "third timestamp, with limit", - ts: timestamps[1], - limit: 3, - expOutcome: func(ctx sdk.Context, ids []string, getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error)) { - require.Equal(t, expectedConsumerIds[0:3], ids) - - // 4th consumer remained - ret, err := getIds(ctx, timestamps[0]) - require.NoError(t, err) - require.Empty(t, ret) - ret, err = getIds(ctx, timestamps[1]) - require.NoError(t, err) - require.Empty(t, ret) - ret, err = getIds(ctx, timestamps[2]) - require.NoError(t, err) - require.Equal(t, providertypes.ConsumerIds{ - Ids: []string{expectedConsumerIds[3]}, - }, ret) - }, - }, - } - - // test for consumers to be launched - for _, tc := range testCases { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - callCases := []struct { - timeQueueKeyPrefix byte - getIds func(sdk.Context, time.Time) (providertypes.ConsumerIds, error) - deleteAllIds func(sdk.Context, time.Time) - appendId func(sdk.Context, string, time.Time) error - }{ - { - timeQueueKeyPrefix: providertypes.SpawnTimeToConsumerIdsKeyPrefix(), - getIds: providerKeeper.GetConsumersToBeLaunched, - deleteAllIds: providerKeeper.DeleteAllConsumersToBeLaunched, - appendId: providerKeeper.AppendConsumerToBeLaunched, - }, - { - timeQueueKeyPrefix: providertypes.RemovalTimeToConsumerIdsKeyPrefix(), - getIds: providerKeeper.GetConsumersToBeRemoved, - deleteAllIds: providerKeeper.DeleteAllConsumersToBeRemoved, - appendId: providerKeeper.AppendConsumerToBeRemoved, - }, - } - for _, cc := range callCases { - err := cc.appendId(ctx, expectedConsumerIds[0], timestamps[0]) - require.NoError(t, err) - err = cc.appendId(ctx, expectedConsumerIds[1], timestamps[0]) - require.NoError(t, err) - err = cc.appendId(ctx, expectedConsumerIds[2], timestamps[1]) - require.NoError(t, err) - err = cc.appendId(ctx, expectedConsumerIds[3], timestamps[2]) - require.NoError(t, err) - - ctx = ctx.WithBlockTime(tc.ts) - - consumerIds, err := providerKeeper.ConsumeIdsFromTimeQueue( - ctx, - cc.timeQueueKeyPrefix, - cc.getIds, - cc.deleteAllIds, - cc.appendId, - tc.limit, - ) - require.NoError(t, err) - - tc.expOutcome(ctx, consumerIds, cc.getIds) - } - } -} - -func TestCreateConsumerClient(t *testing.T) { - type testCase struct { - description string - // Any state-mutating setup on keeper and expected mock calls, specific to this test case - setup func(*providerkeeper.Keeper, sdk.Context, *testkeeper.MockedKeepers) - // Whether a client should be created - expClientCreated bool - } - tests := []testCase{ - { - description: "No state mutation, new client should be created", - setup: func(providerKeeper *providerkeeper.Keeper, ctx sdk.Context, mocks *testkeeper.MockedKeepers) { - providerKeeper.SetConsumerPhase(ctx, CONSUMER_ID, providertypes.CONSUMER_PHASE_INITIALIZED) - - // Valid client creation is asserted with mock expectations here - gomock.InOrder( - testkeeper.GetMocksForCreateConsumerClient(ctx, mocks, CONSUMER_CHAIN_ID, clienttypes.NewHeight(4, 5))..., - ) - }, - expClientCreated: true, - }, - { - description: "chain for this consumer id has already launched, and hence client was created, NO new one is created", - setup: func(providerKeeper *providerkeeper.Keeper, ctx sdk.Context, mocks *testkeeper.MockedKeepers) { - providerKeeper.SetConsumerPhase(ctx, CONSUMER_ID, providertypes.CONSUMER_PHASE_LAUNCHED) - - // Expect none of the client creation related calls to happen - mocks.MockClientKeeper.EXPECT().CreateClient(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - }, - expClientCreated: false, - }, - } - - for _, tc := range tests { - // Common setup - keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - providerKeeper.SetParams(ctx, providertypes.DefaultParams()) - - // Test specific setup - tc.setup(&providerKeeper, ctx, &mocks) - - // Call method with same arbitrary values as defined above in mock expectations. - providerKeeper.SetConsumerChainId(ctx, CONSUMER_ID, CONSUMER_CHAIN_ID) - err := providerKeeper.SetConsumerInitializationParameters(ctx, CONSUMER_ID, testkeeper.GetTestInitializationParameters()) - require.NoError(t, err) - - err = providerKeeper.CreateConsumerClient(ctx, CONSUMER_ID, []byte{}) - if tc.expClientCreated { - require.NoError(t, err) - clientId, found := providerKeeper.GetConsumerClientId(ctx, CONSUMER_ID) - require.True(t, found) - require.Equal(t, "clientID", clientId) - } else { - require.Error(t, err) - } - - // Assert mock calls from setup functions - ctrl.Finish() - } -} - -// TestMakeConsumerGenesis tests the MakeConsumerGenesis keeper method. -// An expected genesis state is hardcoded in json, unmarshaled, and compared -// against an actual consumer genesis state constructed by a provider keeper. -func TestMakeConsumerGenesis(t *testing.T) { - keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - defer ctrl.Finish() - - moduleParams := providertypes.Params{ - TemplateClient: &ibctmtypes.ClientState{ - TrustLevel: ibctmtypes.DefaultTrustLevel, - MaxClockDrift: 10000000000, - ProofSpecs: []*_go.ProofSpec{ - { - LeafSpec: &_go.LeafOp{ - Hash: _go.HashOp_SHA256, - PrehashKey: _go.HashOp_NO_HASH, - PrehashValue: _go.HashOp_SHA256, - Length: _go.LengthOp_VAR_PROTO, - Prefix: []byte{0x00}, - }, - InnerSpec: &_go.InnerSpec{ - ChildOrder: []int32{0, 1}, - ChildSize: 33, - MinPrefixLength: 4, - MaxPrefixLength: 12, - Hash: _go.HashOp_SHA256, - }, - MaxDepth: 0, - MinDepth: 0, - }, - { - LeafSpec: &_go.LeafOp{ - Hash: _go.HashOp_SHA256, - PrehashKey: _go.HashOp_NO_HASH, - PrehashValue: _go.HashOp_SHA256, - Length: _go.LengthOp_VAR_PROTO, - Prefix: []byte{0x00}, - }, - InnerSpec: &_go.InnerSpec{ - ChildOrder: []int32{0, 1}, - ChildSize: 32, - MinPrefixLength: 1, - MaxPrefixLength: 1, - Hash: _go.HashOp_SHA256, - }, - MaxDepth: 0, - }, - }, - UpgradePath: []string{"upgrade", "upgradedIBCState"}, - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - }, - // Note these are unused provider parameters for this test, and not actually asserted against - // They must be populated with reasonable values to satisfy SetParams though. - TrustingPeriodFraction: providertypes.DefaultTrustingPeriodFraction, - CcvTimeoutPeriod: ccvtypes.DefaultCCVTimeoutPeriod, - SlashMeterReplenishPeriod: providertypes.DefaultSlashMeterReplenishPeriod, - SlashMeterReplenishFraction: providertypes.DefaultSlashMeterReplenishFraction, - ConsumerRewardDenomRegistrationFee: sdk.Coin{ - Denom: "stake", - Amount: math.NewInt(1000000), - }, - BlocksPerEpoch: 600, - NumberOfEpochsToStartReceivingRewards: 24, - } - providerKeeper.SetParams(ctx, moduleParams) - - // matches params from jsonString - ccvTimeoutPeriod := time.Duration(2419200000000000) - transferTimeoutPeriod := time.Duration(3600000000000) - consumerUnbondingPeriod := time.Duration(1728000000000000) - providerUnbondingPeriod := time.Duration(1814400000000000) - trustingPeriod := time.Duration(1197504000000000) - providerChainId := "provider-1" - providerRevisionNumber := uint64(1) - providerRevisionHeight := int64(5) - - initializationParameters := providertypes.ConsumerInitializationParameters{ - BlocksPerDistributionTransmission: 1000, - CcvTimeoutPeriod: ccvTimeoutPeriod, - TransferTimeoutPeriod: transferTimeoutPeriod, - ConsumerRedistributionFraction: "0.75", - HistoricalEntries: 10000, - UnbondingPeriod: consumerUnbondingPeriod, - } - - // - // Other setup not covered by custom template client state - // - ctx = ctx.WithChainID(providerChainId) // consumerId is obtained from ctx - ctx = ctx.WithBlockHeight(providerRevisionHeight) // RevisionHeight obtained from ctx - gomock.InOrder(testkeeper.GetMocksForMakeConsumerGenesis(ctx, &mocks, providerUnbondingPeriod)...) - - providerKeeper.SetConsumerChainId(ctx, CONSUMER_ID, CONSUMER_CHAIN_ID) - err := providerKeeper.SetConsumerInitializationParameters(ctx, CONSUMER_ID, initializationParameters) - require.NoError(t, err) - - _, pks, _ := ibctesting.GenerateKeys(t, 2) - var ppks [2]tmprotocrypto.PublicKey - for i, pk := range pks { - ppks[i], _ = cryptocodec.ToCmtProtoPublicKey(pk) - } - initialValUpdates := []abci.ValidatorUpdate{ - {PubKey: ppks[0], Power: 1}, - {PubKey: ppks[1], Power: 2}, - } - - actualGenesis, err := providerKeeper.MakeConsumerGenesis(ctx, CONSUMER_ID, initialValUpdates) - require.NoError(t, err) - - // JSON string with tabs, newlines and spaces for readability - jsonString := fmt.Sprintf(`{ - "params": { - "enabled": true, - "blocks_per_distribution_transmission": %d, - "ccv_timeout_period": %d, - "transfer_timeout_period": %d, - "consumer_redistribution_fraction": "%s", - "historical_entries": %d, - "unbonding_period": %d, - "soft_opt_out_threshold": "0", - "reward_denoms": [], - "provider_reward_denoms": [], - "retry_delay_period": %d, - "consumer_id": "%s" - }, - "new_chain": true, - "provider" : { - "client_state": { - "chain_id": "%s", - "trust_level": { - "numerator": 1, - "denominator": 3 - }, - "trusting_period": %d, - "unbonding_period": %d, - "max_clock_drift": %d, - "frozen_height": {}, - "latest_height": { - "revision_number": %d, - "revision_height": %d - }, - "proof_specs": [ - { - "leaf_spec": { - "hash": 1, - "prehash_value": 1, - "length": 1, - "prefix": "AA==" - }, - "inner_spec": { - "child_order": [0, 1], - "child_size": 33, - "min_prefix_length": 4, - "max_prefix_length": 12, - "hash": 1 - } - }, - { - "leaf_spec": { - "hash": 1, - "prehash_value": 1, - "length": 1, - "prefix": "AA==" - }, - "inner_spec": { - "child_order": [0, 1], - "child_size": 32, - "min_prefix_length": 1, - "max_prefix_length": 1, - "hash": 1 - } - } - ], - "upgrade_path": ["upgrade", "upgradedIBCState"], - "allow_update_after_expiry": true, - "allow_update_after_misbehaviour": true - }, - "consensus_state": { - "timestamp": "2020-01-02T00:00:10Z", - "root": { - "hash": "LpGpeyQVLUo9HpdsgJr12NP2eCICspcULiWa5u9udOA=" - }, - "next_validators_hash": "E30CE736441FB9101FADDAF7E578ABBE6DFDB67207112350A9A904D554E1F5BE" - }, - "initial_val_set": [{}] - } - }`, - initializationParameters.BlocksPerDistributionTransmission, - ccvTimeoutPeriod.Nanoseconds(), - transferTimeoutPeriod.Nanoseconds(), - initializationParameters.ConsumerRedistributionFraction, - initializationParameters.HistoricalEntries, - consumerUnbondingPeriod.Nanoseconds(), - ccvtypes.DefaultRetryDelayPeriod.Nanoseconds(), - CONSUMER_ID, - providerChainId, - trustingPeriod.Nanoseconds(), - providerUnbondingPeriod.Nanoseconds(), - providertypes.DefaultMaxClockDrift.Nanoseconds(), - providerRevisionNumber, - providerRevisionHeight, - ) - - var expectedGenesis ccvtypes.ConsumerGenesisState - err = json.Unmarshal([]byte(jsonString), &expectedGenesis) // ignores tabs, newlines and spaces - require.NoError(t, err) - expectedGenesis.Provider.InitialValSet = initialValUpdates - - // Zeroing out different fields that are challenging to mock - actualGenesis.Provider.ConsensusState = &ibctmtypes.ConsensusState{} - expectedGenesis.Provider.ConsensusState = &ibctmtypes.ConsensusState{} - - require.Equal(t, expectedGenesis, actualGenesis, "consumer chain genesis created incorrectly") -} - -func TestBeginBlockStopConsumers(t *testing.T) { - now := time.Now().UTC() - - keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - providerKeeper.SetParams(ctx, providertypes.DefaultParams()) - defer ctrl.Finish() - ctx = ctx.WithBlockTime(now) - - chainIds := []string{"chain1", "chain2", "chain3"} - consumerIds := []string{"consumerId1", "consumerId2", "consumerId3"} - err := providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[0], now.Add(-time.Hour)) - require.NoError(t, err) - err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[0], now.Add(-time.Hour)) - require.NoError(t, err) - err = providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[1], now) - require.NoError(t, err) - err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[1], now) - require.NoError(t, err) - err = providerKeeper.SetConsumerRemovalTime(ctx, consumerIds[2], now.Add(time.Hour)) - require.NoError(t, err) - err = providerKeeper.AppendConsumerToBeRemoved(ctx, consumerIds[2], now.Add(time.Hour)) - require.NoError(t, err) - - // - // Mock expectations - // - expectations := []*gomock.Call{} - for i := range consumerIds { - chainId := chainIds[i] - // A consumer chain is setup corresponding to each consumerId, making these mocks necessary - expectations = append(expectations, testkeeper.GetMocksForCreateConsumerClient(ctx, &mocks, - chainId, clienttypes.NewHeight(2, 3))...) - expectations = append(expectations, testkeeper.GetMocksForSetConsumerChain(ctx, &mocks, chainId)...) - } - // Only first two consumer chains should be stopped - expectations = append(expectations, testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) - expectations = append(expectations, testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) - - gomock.InOrder(expectations...) - - // - // Remaining setup - // - for i, consumerId := range consumerIds { - // Setup a valid consumer chain for each consumerId - initializationRecord := testkeeper.GetTestInitializationParameters() - initializationRecord.InitialHeight = clienttypes.NewHeight(2, 3) - registrationRecord := testkeeper.GetTestConsumerMetadata() - - providerKeeper.SetConsumerChainId(ctx, consumerId, chainIds[i]) - err = providerKeeper.SetConsumerMetadata(ctx, consumerId, registrationRecord) - require.NoError(t, err) - err = providerKeeper.SetConsumerInitializationParameters(ctx, consumerId, initializationRecord) - require.NoError(t, err) - err = providerKeeper.SetConsumerPowerShapingParameters(ctx, consumerId, testkeeper.GetTestPowerShapingParameters()) - require.NoError(t, err) - providerKeeper.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_INITIALIZED) - providerKeeper.SetConsumerClientId(ctx, consumerId, "clientID") - - err = providerKeeper.CreateConsumerClient(ctx, consumerId, []byte{}) - require.NoError(t, err) - err = providerKeeper.SetConsumerChain(ctx, "channelID") - require.NoError(t, err) - - // the chain is considered to be stopped and ready for deletion (i.e., `StopAndPrepareForConsumerRemoval` is called) - providerKeeper.SetConsumerPhase(ctx, consumerId, providertypes.CONSUMER_PHASE_STOPPED) - } - - // - // Test execution - // - - err = providerKeeper.BeginBlockRemoveConsumers(ctx) - require.NoError(t, err) - - // Only the 3rd (final) proposal is still stored as pending - phase := providerKeeper.GetConsumerPhase(ctx, consumerIds[0]) - require.Equal(t, providertypes.CONSUMER_PHASE_DELETED, phase) - phase = providerKeeper.GetConsumerPhase(ctx, consumerIds[1]) - require.Equal(t, providertypes.CONSUMER_PHASE_DELETED, phase) - // third chain had a removal time in the future and hence did not get deleted - phase = providerKeeper.GetConsumerPhase(ctx, consumerIds[2]) - require.Equal(t, providertypes.CONSUMER_PHASE_STOPPED, phase) -} - -// Tests the DeleteConsumerChain method against the spec, -// with more granularity than what's covered in TestHandleLegacyConsumerRemovalProposal, or integration tests. -// See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-stcc1 -// Spec tag: [CCV-PCF-STCC.1] -func TestStopConsumerChain(t *testing.T) { - type testCase struct { - description string - // State-mutating setup specific to this test case - setup func(sdk.Context, *providerkeeper.Keeper, testkeeper.MockedKeepers) - // Whether we should expect the method to return an error - expErr bool - } - - consumerId := "0" - - tests := []testCase{ - { - description: "proposal dropped, client doesn't exist", - setup: func(ctx sdk.Context, providerKeeper *providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { - // No mocks, meaning no external keeper methods are allowed to be called. - }, - expErr: true, - }, - { - description: "valid stop of consumer chain, all mock calls hit", - setup: func(ctx sdk.Context, providerKeeper *providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { - testkeeper.SetupForDeleteConsumerChain(t, ctx, providerKeeper, mocks, consumerId) - - // set consumer minimum equivocation height - providerKeeper.SetEquivocationEvidenceMinHeight(ctx, consumerId, 1) - - // assert mocks for expected calls to `DeleteConsumerChain` when closing the underlying channel - gomock.InOrder(testkeeper.GetMocksForDeleteConsumerChain(ctx, &mocks)...) - }, - expErr: false, - }, - } - - for _, tc := range tests { - - // Common setup - keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - providerKeeper.SetParams(ctx, providertypes.DefaultParams()) - - // Setup specific to test case - tc.setup(ctx, &providerKeeper, mocks) - - err := providerKeeper.DeleteConsumerChain(ctx, consumerId) - - if tc.expErr { - require.Error(t, err, t) - } else { - require.NoError(t, err) - } - - testkeeper.TestProviderStateIsCleanedAfterConsumerChainIsDeleted(t, ctx, providerKeeper, consumerId, "channelID", tc.expErr) - - ctrl.Finish() - } -} - -// -// Setters and Getters -// - -// TestConsumerRemovalTime tests the getter, setter, and deletion of the consumer id to removal times methods -func TestConsumerRemovalTime(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - _, err := providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) - require.Error(t, err) - - expectedRemovalTime := time.Unix(1234, 56789) - providerKeeper.SetConsumerRemovalTime(ctx, CONSUMER_ID, expectedRemovalTime) - actualRemovalTime, err := providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) - require.NoError(t, err) - require.Equal(t, actualRemovalTime, expectedRemovalTime) - - providerKeeper.DeleteConsumerRemovalTime(ctx, CONSUMER_ID) - _, err = providerKeeper.GetConsumerRemovalTime(ctx, CONSUMER_ID) - require.Error(t, err) -} - -// TestConsumersToBeLaunched tests `AppendConsumerToBeLaunched`, `GetConsumersToBeLaunched`, and `RemoveConsumerToBeLaunched` -func TestConsumersToBeLaunched(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - spawnTime := time.Now() - err := providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId1", spawnTime) - require.NoError(t, err) - consumers, err := providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1"}, consumers.Ids) - - err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId2", spawnTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) - - err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId3", spawnTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) - - err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId2", spawnTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) - - // also add consumer ids under a different spawn time and verify everything under the original spawn time is still there - spawnTimePlusOneHour := spawnTime.Add(time.Hour) - err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) - require.NoError(t, err) - require.Equal(t, []string{"consumerId4"}, consumers.Ids) - - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) - - // start removing all consumers from `spawnTime` - err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId3", spawnTime) - require.NoError(t, err) - err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId1", spawnTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Empty(t, consumers.Ids) - - // remove from `spawnTimePlusOneHour` - err = providerKeeper.RemoveConsumerToBeLaunched(ctx, "consumerId4", spawnTimePlusOneHour) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTimePlusOneHour) - require.NoError(t, err) - require.Empty(t, consumers.Ids) - - // add another consumer for `spawnTime` - err = providerKeeper.AppendConsumerToBeLaunched(ctx, "consumerId5", spawnTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeLaunched(ctx, spawnTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId5"}, consumers.Ids) -} - -// TestConsumersToBeRemoved tests `AppendConsumerToBeRemoved`, `GetConsumersToBeRemoved`, and `RemoveConsumerToBeRemoved` -func TestConsumersToBeRemoved(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - removalTime := time.Now() - err := providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId1", removalTime) - require.NoError(t, err) - consumers, err := providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1"}, consumers.Ids) - - err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId2", removalTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId2"}, consumers.Ids) - - err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId3", removalTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, consumers.Ids) - - err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId2", removalTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) - - // also add consumer ids under a different removal time and verify everything under the original removal time is still there - removalTimePlusOneHour := removalTime.Add(time.Hour) - err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId4", removalTimePlusOneHour) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTimePlusOneHour) - require.NoError(t, err) - require.Equal(t, []string{"consumerId4"}, consumers.Ids) - - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId1", "consumerId3"}, consumers.Ids) - - // start removing all consumers from `removalTime` - err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId3", removalTime) - require.NoError(t, err) - err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId1", removalTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Empty(t, consumers.Ids) - - // remove from `removalTimePlusOneHour` - err = providerKeeper.RemoveConsumerToBeRemoved(ctx, "consumerId4", removalTimePlusOneHour) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTimePlusOneHour) - require.NoError(t, err) - require.Empty(t, consumers.Ids) - - // add another consumer for `removalTime` - err = providerKeeper.AppendConsumerToBeRemoved(ctx, "consumerId5", removalTime) - require.NoError(t, err) - consumers, err = providerKeeper.GetConsumersToBeRemoved(ctx, removalTime) - require.NoError(t, err) - require.Equal(t, []string{"consumerId5"}, consumers.Ids) -} diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index 4fce69c17b..b1a5b36dcd 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -24,14 +24,8 @@ func (k Keeper) QueryConsumerGenesis(c context.Context, req *types.QueryConsumer return nil, status.Errorf(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD if req.ChainId == "" { return nil, status.Errorf(codes.InvalidArgument, "invalid request: chain id cannot be empty") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } gen, ok := k.GetConsumerGenesis(ctx, req.ChainId) @@ -152,14 +146,6 @@ func (k Keeper) QueryValidatorConsumerAddr(goCtx context.Context, req *types.Que ctx := sdk.UnwrapSDKContext(goCtx) -<<<<<<< HEAD -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) providerAddrTmp, err := sdk.ConsAddressFromBech32(req.ProviderAddress) if err != nil { return nil, err @@ -241,7 +227,6 @@ func (k Keeper) QueryProposedConsumerChainIDs(goCtx context.Context, req *types. return nil, status.Error(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD ctx := sdk.UnwrapSDKContext(goCtx) chains := k.GetAllProposedConsumerChainIDs(ctx) @@ -258,11 +243,6 @@ func (k Keeper) QueryAllPairsValConAddrByConsumerChainID(goCtx context.Context, if req.ChainId == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } // list of pairs valconsensus addr @@ -301,15 +281,9 @@ func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req * return nil, status.Error(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } optedInVals := []string{} @@ -334,15 +308,9 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC return nil, status.Error(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } ctx := sdk.UnwrapSDKContext(goCtx) @@ -445,15 +413,9 @@ func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req return nil, status.Error(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD consumerChainID := req.ChainId if consumerChainID == "" { return nil, status.Error(codes.InvalidArgument, "empty chainId") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress) @@ -492,20 +454,8 @@ func (k Keeper) QueryOldestUnconfirmedVsc(goCtx context.Context, req *types.Quer return nil, status.Errorf(codes.InvalidArgument, "empty request") } -<<<<<<< HEAD if req.ChainId == "" { return nil, status.Errorf(codes.InvalidArgument, "invalid request: chain id cannot be empty") -======= - consumerId := req.ConsumerId - if err := ccvtypes.ValidateConsumerId(consumerId); err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - ctx := sdk.UnwrapSDKContext(goCtx) - - chainId, err := k.GetConsumerChainId(ctx, consumerId) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "cannot retrieve chain id for consumer id: %s", consumerId) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } if _, consumerRegistered := k.GetConsumerClientId(ctx, req.ChainId); !consumerRegistered { diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index cb5c403fc1..93e88397c4 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -6,7 +6,6 @@ import ( // Provider sentinel errors var ( -<<<<<<< HEAD ErrInvalidConsumerAdditionProposal = errorsmod.Register(ModuleName, 1, "invalid consumer addition proposal") ErrInvalidConsumerRemovalProp = errorsmod.Register(ModuleName, 2, "invalid consumer removal proposal") ErrUnknownConsumerChainId = errorsmod.Register(ModuleName, 3, "no consumer chain with this chain id") @@ -29,39 +28,4 @@ var ( ErrCannotOptOutFromTopN = errorsmod.Register(ModuleName, 20, "cannot opt out from a Top N chain") ErrNoUnconfirmedVSCPacket = errorsmod.Register(ModuleName, 21, "no unconfirmed vsc packet for this chain id") ErrInvalidConsumerModificationProposal = errorsmod.Register(ModuleName, 22, "invalid consumer modification proposal") -======= - ErrUnknownConsumerId = errorsmod.Register(ModuleName, 3, "no consumer chain with this consumer id") - ErrUnknownConsumerChannelId = errorsmod.Register(ModuleName, 4, "no consumer chain with this channel 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") - ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 16, "ccv channel is not built on correct client") - ErrCannotOptOutFromTopN = errorsmod.Register(ModuleName, 20, "cannot opt out from a Top N chain") - ErrNoUnbondingTime = errorsmod.Register(ModuleName, 23, "provider unbonding time not found") - ErrUnauthorized = errorsmod.Register(ModuleName, 25, "unauthorized") - ErrInvalidPhase = errorsmod.Register(ModuleName, 27, "cannot perform action in the current phase of consumer chain") - ErrInvalidConsumerMetadata = errorsmod.Register(ModuleName, 28, "invalid consumer metadata") - ErrInvalidPowerShapingParameters = errorsmod.Register(ModuleName, 29, "invalid power shaping parameters") - ErrInvalidConsumerInitializationParameters = errorsmod.Register(ModuleName, 30, "invalid consumer initialization parameters") - ErrCannotUpdateMinimumPowerInTopN = errorsmod.Register(ModuleName, 31, "cannot update minimum power in Top N") - ErrNoConsumerGenesis = errorsmod.Register(ModuleName, 33, "missing consumer genesis") - ErrInvalidConsumerGenesis = errorsmod.Register(ModuleName, 34, "invalid consumer genesis") - ErrNoConsumerId = errorsmod.Register(ModuleName, 35, "missing consumer id") - ErrAlreadyOptedIn = errorsmod.Register(ModuleName, 36, "already opted in to a chain with the same chain id") - ErrNoOwnerAddress = errorsmod.Register(ModuleName, 37, "missing owner address") - ErrInvalidNewOwnerAddress = errorsmod.Register(ModuleName, 38, "invalid new owner address") - ErrInvalidTransformToTopN = errorsmod.Register(ModuleName, 39, "invalid transform to Top N chain") - ErrInvalidTransformToOptIn = errorsmod.Register(ModuleName, 40, "invalid transform to Opt In chain") - ErrCannotCreateTopNChain = errorsmod.Register(ModuleName, 41, "cannot create Top N chain outside permissionlessly") - ErrInvalidRemovalTime = errorsmod.Register(ModuleName, 43, "invalid removal time") - ErrInvalidMsgCreateConsumer = errorsmod.Register(ModuleName, 44, "invalid create consumer message") - ErrInvalidMsgUpdateConsumer = errorsmod.Register(ModuleName, 45, "invalid update consumer message") - ErrInvalidMsgAssignConsumerKey = errorsmod.Register(ModuleName, 46, "invalid assign consumer key message") - ErrInvalidMsgSubmitConsumerMisbehaviour = errorsmod.Register(ModuleName, 47, "invalid submit consumer misbehaviour message") - ErrInvalidMsgSubmitConsumerDoubleVoting = errorsmod.Register(ModuleName, 48, "invalid submit consumer double voting message") - ErrInvalidMsgOptIn = errorsmod.Register(ModuleName, 49, "invalid opt in message") - ErrInvalidMsgOptOut = errorsmod.Register(ModuleName, 50, "invalid opt out message") - ErrInvalidMsgSetConsumerCommissionRate = errorsmod.Register(ModuleName, 51, "invalid set consumer commission rate message") - ErrInvalidMsgChangeRewardDenoms = errorsmod.Register(ModuleName, 52, "invalid change reward denoms message") ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index a8843ceee3..b80e608785 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -80,17 +80,11 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { if strings.TrimSpace(msg.ChainId) == "" { return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") } -<<<<<<< HEAD // It is possible to assign keys for consumer chains that are not yet approved. // This can only be done by a signing validator, but it is still sensible // to limit the chainID size to prevent abuse. if 128 < len(msg.ChainId) { return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") -======= - - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ConsumerId: %s", err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { @@ -100,319 +94,11 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { return ErrInvalidConsumerConsensusPubKey } if _, _, err := ParseConsumerKeyFromJson(msg.ConsumerKey); err != nil { -<<<<<<< HEAD return ErrInvalidConsumerConsensusPubKey -======= - return errorsmod.Wrapf(ErrInvalidMsgAssignConsumerKey, "ConsumerKey: %s", err.Error()) - } - - return nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg *MsgChangeRewardDenoms) ValidateBasic() error { - emptyDenomsToAdd := len(msg.DenomsToAdd) == 0 - emptyDenomsToRemove := len(msg.DenomsToRemove) == 0 - // Return error if both sets are empty or nil - if emptyDenomsToAdd && emptyDenomsToRemove { - return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "both DenomsToAdd and DenomsToRemove are empty") - } - - denomMap := map[string]struct{}{} - for _, denom := range msg.DenomsToAdd { - // validate the denom - if !sdk.NewCoin(denom, math.NewInt(1)).IsValid() { - return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "DenomsToAdd: invalid denom(%s)", denom) - } - denomMap[denom] = struct{}{} - } - for _, denom := range msg.DenomsToRemove { - // validate the denom - if !sdk.NewCoin(denom, math.NewInt(1)).IsValid() { - return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, "DenomsToRemove: invalid denom(%s)", denom) - } - // denom cannot be in both sets - if _, found := denomMap[denom]; found { - return errorsmod.Wrapf(ErrInvalidMsgChangeRewardDenoms, - "denom(%s) cannot be both added and removed", denom) - } - } - - return nil -} - -func NewMsgSubmitConsumerMisbehaviour( - consumerId string, - submitter sdk.AccAddress, - misbehaviour *ibctmtypes.Misbehaviour, -) (*MsgSubmitConsumerMisbehaviour, error) { - return &MsgSubmitConsumerMisbehaviour{ - Submitter: submitter.String(), - Misbehaviour: misbehaviour, - ConsumerId: consumerId, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerMisbehaviour, "ConsumerId: %s", err.Error()) - } - - if err := msg.Misbehaviour.ValidateBasic(); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerMisbehaviour, "Misbehaviour: %s", err.Error()) ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) - } - return nil -} - -<<<<<<< HEAD -======= -func NewMsgSubmitConsumerDoubleVoting( - consumerId string, - submitter sdk.AccAddress, - ev *tmtypes.DuplicateVoteEvidence, - header *ibctmtypes.Header, -) (*MsgSubmitConsumerDoubleVoting, error) { - return &MsgSubmitConsumerDoubleVoting{ - Submitter: submitter.String(), - DuplicateVoteEvidence: ev, - InfractionBlockHeader: header, - ConsumerId: consumerId, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { - if dve, err := cmttypes.DuplicateVoteEvidenceFromProto(msg.DuplicateVoteEvidence); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "DuplicateVoteEvidence: %s", err.Error()) - } else { - if err = dve.ValidateBasic(); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "DuplicateVoteEvidence: %s", err.Error()) - } - } - - if err := ValidateHeaderForConsumerDoubleVoting(msg.InfractionBlockHeader); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ValidateTendermintHeader: %s", err.Error()) - } - - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSubmitConsumerDoubleVoting, "ConsumerId: %s", err.Error()) - } - - return nil -} - -// NewMsgOptIn creates a new NewMsgOptIn instance. -func NewMsgOptIn(consumerId string, providerValidatorAddress sdk.ValAddress, consumerConsensusPubKey, signer string) (*MsgOptIn, error) { - return &MsgOptIn{ - ConsumerId: consumerId, - ProviderAddr: providerValidatorAddress.String(), - ConsumerKey: consumerConsensusPubKey, - Signer: signer, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgOptIn) ValidateBasic() error { - if err := validateDeprecatedChainId(msg.ChainId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ChainId: %s", err.Error()) - } - - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ConsumerId: %s", err.Error()) - } - - if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ProviderAddr: %s", err.Error()) - } - - if msg.ConsumerKey != "" { - if _, _, err := ParseConsumerKeyFromJson(msg.ConsumerKey); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptIn, "ConsumerKey: %s", err.Error()) - } - } - return nil -} - -// NewMsgOptOut creates a new NewMsgOptIn instance. -func NewMsgOptOut(consumerId string, providerValidatorAddress sdk.ValAddress, signer string) (*MsgOptOut, error) { - return &MsgOptOut{ - ConsumerId: consumerId, - ProviderAddr: providerValidatorAddress.String(), - Signer: signer, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgOptOut) ValidateBasic() error { - if err := validateDeprecatedChainId(msg.ChainId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ChainId: %s", err.Error()) - } - - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ConsumerId: %s", err.Error()) - } - - if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgOptOut, "ProviderAddr: %s", err.Error()) - } - - return nil -} - -// NewMsgSetConsumerCommissionRate creates a new MsgSetConsumerCommissionRate msg instance. -func NewMsgSetConsumerCommissionRate( - consumerId string, - commission math.LegacyDec, - providerValidatorAddress sdk.ValAddress, - signer string, -) *MsgSetConsumerCommissionRate { - return &MsgSetConsumerCommissionRate{ - ConsumerId: consumerId, - Rate: commission, - ProviderAddr: providerValidatorAddress.String(), - Signer: signer, - } -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgSetConsumerCommissionRate) ValidateBasic() error { - if err := validateDeprecatedChainId(msg.ChainId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ChainId: %s", err.Error()) - } - - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ConsumerId: %s", err.Error()) } - - if err := validateProviderAddress(msg.ProviderAddr, msg.Signer); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "ProviderAddr: %s", err.Error()) - } - - if msg.Rate.IsNegative() || msg.Rate.GT(math.LegacyOneDec()) { - return errorsmod.Wrapf(ErrInvalidMsgSetConsumerCommissionRate, "consumer commission rate should be in the range [0, 1]") - } - return nil } -// NewMsgCreateConsumer creates a new MsgCreateConsumer instance -func NewMsgCreateConsumer(submitter, chainId string, metadata ConsumerMetadata, - initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters, -) (*MsgCreateConsumer, error) { - return &MsgCreateConsumer{ - Submitter: submitter, - ChainId: chainId, - Metadata: metadata, - InitializationParameters: initializationParameters, - PowerShapingParameters: powerShapingParameters, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgCreateConsumer) ValidateBasic() error { - if err := ValidateStringField("ChainId", msg.ChainId, cmttypes.MaxChainIDLen); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "ChainId: %s", err.Error()) - } - - // With permissionless ICS, we can have multiple consumer chains with the exact same chain id. - // However, as we already have the Neutron and Stride Top N chains running, as a first step we would like to - // prevent permissionless chains from re-using the chain ids of Neutron and Stride. Note that this is just a - // preliminary measure that will be removed later on as part of: - // TODO (#2242): find a better way of ignoring past misbehaviors - if msg.ChainId == "neutron-1" || msg.ChainId == "stride-1" { - return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, - "cannot reuse chain ids of existing Neutron and Stride Top N consumer chains") - } - - if err := ValidateConsumerMetadata(msg.Metadata); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "Metadata: %s", err.Error()) - } - - if msg.InitializationParameters != nil { - if err := ValidateInitializationParameters(*msg.InitializationParameters); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "InitializationParameters: %s", err.Error()) - } - } - - if msg.PowerShapingParameters != nil { - if msg.PowerShapingParameters.Top_N != 0 { - return fmt.Errorf("cannot create a Top N chain through `MsgCreateConsumer`; " + - "first create the chain and then use `MsgUpdateConsumer` to make the chain Top N") - } - if err := ValidatePowerShapingParameters(*msg.PowerShapingParameters); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgCreateConsumer, "PowerShapingParameters: %s", err.Error()) - } - } - - return nil -} - -// NewMsgUpdateConsumer creates a new MsgUpdateConsumer instance -func NewMsgUpdateConsumer(owner, consumerId, ownerAddress string, metadata *ConsumerMetadata, - initializationParameters *ConsumerInitializationParameters, powerShapingParameters *PowerShapingParameters, -) (*MsgUpdateConsumer, error) { - return &MsgUpdateConsumer{ - Owner: owner, - ConsumerId: consumerId, - NewOwnerAddress: ownerAddress, - Metadata: metadata, - InitializationParameters: initializationParameters, - PowerShapingParameters: powerShapingParameters, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgUpdateConsumer) ValidateBasic() error { - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "ConsumerId: %s", err.Error()) - } - - // Note that NewOwnerAddress is validated when handling the message in UpdateConsumer - - if msg.Metadata != nil { - if err := ValidateConsumerMetadata(*msg.Metadata); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "Metadata: %s", err.Error()) - } - } - - if msg.InitializationParameters != nil { - if err := ValidateInitializationParameters(*msg.InitializationParameters); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "InitializationParameters: %s", err.Error()) - } - } - - if msg.PowerShapingParameters != nil { - if err := ValidatePowerShapingParameters(*msg.PowerShapingParameters); err != nil { - return errorsmod.Wrapf(ErrInvalidMsgUpdateConsumer, "PowerShapingParameters: %s", err.Error()) - } - } - - return nil -} - -// NewMsgRemoveConsumer creates a new MsgRemoveConsumer instance -func NewMsgRemoveConsumer(owner, consumerId string) (*MsgRemoveConsumer, error) { - return &MsgRemoveConsumer{ - Owner: owner, - ConsumerId: consumerId, - }, nil -} - -// ValidateBasic implements the sdk.HasValidateBasic interface. -func (msg MsgRemoveConsumer) ValidateBasic() error { - if err := ccvtypes.ValidateConsumerId(msg.ConsumerId); err != nil { - return err - } - return nil -} - -// -// Validation methods -// - ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) // ParseConsumerKeyFromJson parses the consumer key from a JSON string, // this replaces deserializing a protobuf any. func ParseConsumerKeyFromJson(jsonStr string) (pkType, key string, err error) { @@ -508,7 +194,6 @@ func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { return nil } -<<<<<<< HEAD // Type implements the sdk.Msg interface. func (msg MsgSubmitConsumerDoubleVoting) GetSignBytes() []byte { bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) @@ -532,18 +217,6 @@ func NewMsgOptIn(chainID string, providerValidatorAddress sdk.ValAddress, consum ProviderAddr: providerValidatorAddress.String(), ConsumerKey: consumerConsensusPubKey, }, nil -======= -// ValidateStringField validates that a string `field` satisfies the following properties: -// - is not empty -// - has at most `maxLength` characters -func ValidateStringField(nameOfTheField, field string, maxLength int) error { - if strings.TrimSpace(field) == "" { - return fmt.Errorf("%s cannot be empty", nameOfTheField) - } else if len(field) > maxLength { - return fmt.Errorf("%s is too long; got: %d, max: %d", nameOfTheField, len(field), maxLength) - } - return nil ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) } // Route implements the sdk.Msg interface. diff --git a/x/ccv/provider/types/msg_test.go b/x/ccv/provider/types/msg_test.go deleted file mode 100644 index 6867343b65..0000000000 --- a/x/ccv/provider/types/msg_test.go +++ /dev/null @@ -1,649 +0,0 @@ -package types_test - -import ( - "testing" - "time" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - - cryptoutil "github.com/cosmos/interchain-security/v6/testutil/crypto" - "github.com/cosmos/interchain-security/v6/x/ccv/provider/types" -) - -func TestValidateStringField(t *testing.T) { - testCases := []struct { - name string - field string - maxLength int - valid bool - }{ - { - name: "invalid: empty", - field: "", - maxLength: 5, - valid: false, - }, - { - name: "invalid: too long", - field: "this field is too long", - maxLength: 5, - valid: false, - }, - { - name: "valid", - field: "valid", - maxLength: 5, - valid: true, - }, - } - - for _, tc := range testCases { - err := types.ValidateStringField(tc.name, tc.field, tc.maxLength) - if tc.valid { - require.NoError(t, err, tc.name) - } else { - require.Error(t, err, tc.name) - } - } -} - -func TestTruncateString(t *testing.T) { - testCases := []struct { - str string - maxLength int - expStr string - }{ - {"drink", 3, "dri"}, - {"drink", 6, "drink"}, - {"drink", 0, ""}, - {"drink", -1, ""}, - {"drink", 100, "drink"}, - {"pub", 100, "pub"}, - {"こんにちは", 3, "こんに"}, - } - - for _, tc := range testCases { - truncated := types.TruncateString(tc.str, tc.maxLength) - require.Equal(t, tc.expStr, truncated) - } -} - -func TestValidateConsumerMetadata(t *testing.T) { - generateLongString := func(length int) string { - result := make([]byte, length) - for i := range result { - result[i] = byte('a') - } - return string(result) - } - - testCases := []struct { - name string - metadata types.ConsumerMetadata - valid bool - }{ - { - name: "valid", - metadata: types.ConsumerMetadata{ - Name: "name", - Description: "description", - Metadata: "metadata", - }, - valid: true, - }, - { - name: "valid with long strings", - metadata: types.ConsumerMetadata{ - Name: generateLongString(types.MaxNameLength), - Description: generateLongString(types.MaxDescriptionLength), - Metadata: generateLongString(types.MaxMetadataLength), - }, - valid: true, - }, - { - name: "invalid name", - metadata: types.ConsumerMetadata{ - Name: generateLongString(types.MaxNameLength + 1), - Description: "description", - Metadata: "metadata", - }, - valid: false, - }, - { - name: "invalid description", - metadata: types.ConsumerMetadata{ - Name: "name", - Description: generateLongString(types.MaxDescriptionLength + 1), - Metadata: "metadata", - }, - valid: false, - }, - { - name: "invalid metadata", - metadata: types.ConsumerMetadata{ - Name: "name", - Description: "description", - Metadata: generateLongString(types.MaxMetadataLength + 1), - }, - valid: false, - }, - } - - for _, tc := range testCases { - err := types.ValidateConsumerMetadata(tc.metadata) - if tc.valid { - require.NoError(t, err, tc.name) - } else { - require.Error(t, err, tc.name) - } - } -} - -func TestValidateInitializationParameters(t *testing.T) { - now := time.Now().UTC() - coolStr := "Cosmos Hub is the best place to launch a chain. Interchain Security is awesome." - tooLongHash := []byte(coolStr) - - testCases := []struct { - name string - params types.ConsumerInitializationParameters - valid bool - }{ - { - name: "valid", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: true, - }, - { - name: "invalid - zero height", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.ZeroHeight(), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid - hash too long", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: tooLongHash, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid - zero spawn time", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: time.Time{}, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: true, - }, - { - name: "invalid - zero duration", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: 0, - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid -- ConsumerRedistributionFraction > 1", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "1.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid -- ConsumerRedistributionFraction wrong format", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: coolStr, - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid - BlocksPerDistributionTransmission zero", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 0, - HistoricalEntries: 10000, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid - HistoricalEntries zero", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 0, - DistributionTransmissionChannel: "", - }, - valid: false, - }, - { - name: "invalid - DistributionTransmissionChannel too long", - params: types.ConsumerInitializationParameters{ - InitialHeight: clienttypes.NewHeight(3, 4), - GenesisHash: []byte{0x01}, - BinaryHash: []byte{0x01}, - SpawnTime: now, - UnbondingPeriod: time.Duration(100000000000), - CcvTimeoutPeriod: time.Duration(100000000000), - TransferTimeoutPeriod: time.Duration(100000000000), - ConsumerRedistributionFraction: "0.75", - BlocksPerDistributionTransmission: 10, - HistoricalEntries: 10000, - DistributionTransmissionChannel: coolStr, - }, - valid: false, - }, - } - - for _, tc := range testCases { - err := types.ValidateInitializationParameters(tc.params) - if tc.valid { - require.NoError(t, err, tc.name) - } else { - require.Error(t, err, tc.name) - } - } -} - -func TestValidateConsAddressList(t *testing.T) { - consAddr1 := "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" - consAddr2 := "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39" - invalidConsAddr := "cosmosvalcons1nx7n5uh0ztxsynn4sje6ey" - - testCases := []struct { - name string - list []string - maxLength int - valid bool - }{ - { - name: "valid - empty list", - list: []string{}, - maxLength: 10, - valid: true, - }, - { - name: "valid - non-empty list", - list: []string{consAddr1, consAddr2}, - maxLength: 10, - valid: true, - }, - { - name: "invalid - address with wrong format", - list: []string{invalidConsAddr}, - maxLength: 10, - valid: false, - }, - { - name: "invalid - empty address", - list: []string{""}, - maxLength: 10, - valid: false, - }, - { - name: "invalid - list length", - list: []string{consAddr1, consAddr2}, - maxLength: 1, - valid: false, - }, - } - - for _, tc := range testCases { - err := types.ValidateConsAddressList(tc.list, tc.maxLength) - if tc.valid { - require.NoError(t, err, tc.name) - } else { - require.Error(t, err, tc.name) - } - } -} - -func TestValidateByteSlice(t *testing.T) { - testCases := []struct { - name string - slice []byte - maxLength int - valid bool - }{ - { - name: "valid: empty", - slice: []byte{}, - maxLength: 5, - valid: true, - }, - { - name: "invalid: too long", - slice: []byte{0x01, 0x02}, - maxLength: 1, - valid: false, - }, - { - name: "valid", - slice: []byte{0x01, 0x02}, - maxLength: 5, - valid: true, - }, - } - - for _, tc := range testCases { - err := types.ValidateByteSlice(tc.slice, tc.maxLength) - if tc.valid { - require.NoError(t, err, tc.name) - } else { - require.Error(t, err, tc.name) - } - } -} - -func TestMsgCreateConsumerValidateBasic(t *testing.T) { - testCases := []struct { - name string - chainId string - powerShapingParameters *types.PowerShapingParameters - expPass bool - }{ - { - "empty chain id", - "", - nil, // no power-shaping parameters - false, - }, - { - "empty chain id after trimming", - " ", - nil, // no power-shaping parameters - false, - }, - { - "neutron chain id that cannot be reused", - "neutron-1", - nil, // no power-shaping parameters - false, - }, - { - "stride chain id that cannot be reused", - "stride-1", - nil, // no power-shaping parameters - false, - }, - { - "valid chain id", - "somechain-1", - nil, // no power-shaping parameters - true, - }, - { - "valid chain id and invalid power-shaping parameters", - "somechain-1", - &types.PowerShapingParameters{Top_N: 51}, // TopN cannot be > 0 in MsgCreateConsumer - false, - }, - } - - for _, tc := range testCases { - validConsumerMetadata := types.ConsumerMetadata{Name: "name", Description: "description", Metadata: "metadata"} - msg, err := types.NewMsgCreateConsumer("submitter", tc.chainId, validConsumerMetadata, nil, tc.powerShapingParameters) - require.NoError(t, err) - err = msg.ValidateBasic() - if tc.expPass { - require.NoError(t, err, "valid case: %s should not return error. got %w", tc.name, err) - } else { - require.Error(t, err, "invalid case: '%s' must return error but got none", tc.name) - } - } -} - -func TestMsgUpdateConsumerValidateBasic(t *testing.T) { - consAddr1 := "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" - consAddr2 := "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39" - consAddr3 := "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe" - - testCases := []struct { - name string - powerShapingParameters types.PowerShapingParameters - expPass bool - }{ - { - "success", - types.PowerShapingParameters{ - Top_N: 50, - ValidatorsPowerCap: 100, - ValidatorSetCap: 34, - Allowlist: []string{consAddr1}, - Denylist: nil, - MinStake: 0, - AllowInactiveVals: false, - }, - true, - }, - { - "top N is invalid", - types.PowerShapingParameters{ - Top_N: 10, - ValidatorsPowerCap: 0, - ValidatorSetCap: 0, - Allowlist: nil, - Denylist: nil, - }, - false, - }, - { - "validators power cap is invalid", - types.PowerShapingParameters{ - Top_N: 50, - ValidatorsPowerCap: 101, - ValidatorSetCap: 0, - Allowlist: nil, - Denylist: nil, - MinStake: 0, - AllowInactiveVals: false, - }, - false, - }, - { - "valid proposal", - types.PowerShapingParameters{ - Top_N: 54, - ValidatorsPowerCap: 92, - ValidatorSetCap: 0, - Allowlist: []string{consAddr1}, - Denylist: []string{consAddr2, consAddr3}, - MinStake: 0, - AllowInactiveVals: false, - }, - true, - }, - } - - for _, tc := range testCases { - // TODO (PERMISSIONLESS) add more tests - msg, _ := types.NewMsgUpdateConsumer("", "0", "cosmos1p3ucd3ptpw902fluyjzhq3ffgq4ntddac9sa3s", nil, nil, &tc.powerShapingParameters) - err := msg.ValidateBasic() - if tc.expPass { - require.NoError(t, err, "valid case: %s should not return error. got %w", tc.name, err) - } else { - require.Error(t, err, "invalid case: '%s' must return error but got none", tc.name) - } - } -} - -func TestMsgAssignConsumerKeyValidateBasic(t *testing.T) { - cId1 := cryptoutil.NewCryptoIdentityFromIntSeed(35443543534) - cId2 := cryptoutil.NewCryptoIdentityFromIntSeed(65465464564) - - valOpAddr1 := cId1.SDKValOpAddress() - acc1 := sdk.AccAddress(valOpAddr1.Bytes()).String() - acc2 := sdk.AccAddress(cId2.SDKValOpAddress().Bytes()).String() - - longChainId := "abcdefghijklmnopqrstuvwxyz" - for i := 0; i < 3; i++ { - longChainId += longChainId - } - - testCases := []struct { - name string - chainId string - providerAddr string - signer string - consumerKey string - consumerId string - expErr bool - }{ - { - name: "invalid: chainId non-empty", - chainId: "chainId", - expErr: true, - }, - { - name: "invalid: consumerId empty", - consumerId: "", - expErr: true, - }, - { - name: "invalid: consumerId is not a number", - consumerId: "consumerId", - expErr: true, - }, - { - name: "invalid: provider address is empty", - consumerId: "1", - expErr: true, - }, - { - name: "invalid: provider address is invalid", - consumerId: "1", - providerAddr: "some address", - expErr: true, - }, - { - name: "invalid: provider address != submitter address", - consumerId: "1", - providerAddr: valOpAddr1.String(), - signer: acc2, - expErr: true, - }, - { - name: "invalid: consumer pubkey empty", - consumerId: "1", - providerAddr: valOpAddr1.String(), - signer: acc1, - expErr: true, - }, - { - name: "valid", - consumerId: "1", - providerAddr: valOpAddr1.String(), - signer: acc1, - consumerKey: "{\"@type\": \"/cosmos.crypto.ed25519.PubKey\", \"key\": \"e3BehnEIlGUAnJYn9V8gBXuMh4tXO8xxlxyXD1APGyk=\"}", - expErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - msg := types.MsgAssignConsumerKey{ - ChainId: tc.chainId, - ConsumerKey: tc.consumerKey, - ProviderAddr: tc.providerAddr, - Signer: tc.signer, - ConsumerId: tc.consumerId, - } - - err := msg.ValidateBasic() - if tc.expErr { - require.Error(t, err, tc.name) - } else { - require.NoError(t, err, tc.name) - } - }) - } -} diff --git a/x/ccv/types/errors.go b/x/ccv/types/errors.go index d155c17c1a..e492984e48 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -22,10 +22,4 @@ var ( ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 14, "consumer chain already exists") ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 15, "consumer chain not found") ErrInvalidDoubleVotingEvidence = errorsmod.Register(ModuleName, 16, "invalid consumer double voting evidence") -<<<<<<< HEAD -======= - 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") ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) ) diff --git a/x/ccv/types/params.go b/x/ccv/types/params.go index ee2446845f..5759ce28f9 100644 --- a/x/ccv/types/params.go +++ b/x/ccv/types/params.go @@ -68,7 +68,6 @@ 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, @@ -85,7 +84,6 @@ func NewParams(enabled bool, blocksPerDistributionTransmission int64, RewardDenoms: rewardDenoms, ProviderRewardDenoms: providerRewardDenoms, RetryDelayPeriod: retryDelayPeriod, - ConsumerId: consumerId, } } @@ -106,7 +104,6 @@ func DefaultParams() ConsumerParams { rewardDenoms, provideRewardDenoms, DefaultRetryDelayPeriod, - "0", ) } @@ -148,9 +145,6 @@ 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 32b3c515b2..92d7105d41 100644 --- a/x/ccv/types/shared_consumer.pb.go +++ b/x/ccv/types/shared_consumer.pb.go @@ -75,8 +75,6 @@ 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{} } @@ -204,13 +202,6 @@ 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 { @@ -350,7 +341,6 @@ func init() { } var fileDescriptor_d0a8be0efc64dfbc = []byte{ -<<<<<<< HEAD // 817 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0x41, 0x6f, 0xe4, 0x34, 0x14, 0x6e, 0x3a, 0x4b, 0x77, 0xea, 0x69, 0xb7, 0x8b, 0x29, 0x4b, 0xe8, 0x4a, 0xd3, 0x6c, 0xe1, @@ -403,61 +393,6 @@ var fileDescriptor_d0a8be0efc64dfbc = []byte{ 0x7d, 0xb7, 0x3f, 0x17, 0x36, 0x2d, 0x93, 0x90, 0xa9, 0x3c, 0x62, 0xca, 0xe4, 0xca, 0x44, 0x37, 0x67, 0xf1, 0xf4, 0xfa, 0xe5, 0xa8, 0xf6, 0xa3, 0x1f, 0xdd, 0xf3, 0xe1, 0x06, 0x7f, 0xb2, 0xe2, 0x2e, 0xd5, 0xa7, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x67, 0x6a, 0xf6, 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, ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) 0x00, } @@ -481,13 +416,6 @@ 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 @@ -765,10 +693,6 @@ 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 } @@ -1228,38 +1152,6 @@ 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 312b877024..566737c0b9 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -2,18 +2,9 @@ package types import ( fmt "fmt" - "strconv" - "strings" "time" -<<<<<<< HEAD ibchost "github.com/cosmos/ibc-go/v7/modules/core/24-host" -======= - ibchost "github.com/cosmos/ibc-go/v8/modules/core/24-host" - - errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" ->>>>>>> 0d782959 (feat!: add memo to IBC transfers of ICS rewards (#2290)) sdktypes "github.com/cosmos/cosmos-sdk/types" ) @@ -121,18 +112,3 @@ 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 deleted file mode 100644 index 9d2bbfce00..0000000000 --- a/x/ccv/types/shared_params_test.go +++ /dev/null @@ -1,23 +0,0 @@ -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 ec04399380..9c22522b74 100644 --- a/x/ccv/types/wire.go +++ b/x/ccv/types/wire.go @@ -1,7 +1,6 @@ package types import ( - "encoding/json" "fmt" errorsmod "cosmossdk.io/errors" @@ -195,54 +194,3 @@ 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 a704fa3d2c..ab6692912e 100644 --- a/x/ccv/types/wire_test.go +++ b/x/ccv/types/wire_test.go @@ -223,17 +223,3 @@ 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) -} From f168580607951d2fa4d397371f52c5ce885550ab Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 27 Sep 2024 19:20:21 +0200 Subject: [PATCH 9/9] fix genesis test --- x/ccv/provider/keeper/proposal_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index 18257a508a..1f87b6e509 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -880,7 +880,8 @@ func TestMakeConsumerGenesis(t *testing.T) { "soft_opt_out_threshold": "0", "reward_denoms": [], "provider_reward_denoms": [], - "retry_delay_period": 3600000000000 + "retry_delay_period": 3600000000000, + "consumer_id": "0" }, "new_chain": true, "provider" : {