diff --git a/.changelog/unreleased/bug-fixes/consumer/1549-soft-opt-out.md b/.changelog/unreleased/bug-fixes/consumer/1549-soft-opt-out.md new file mode 100644 index 0000000000..6fa3d674cf --- /dev/null +++ b/.changelog/unreleased/bug-fixes/consumer/1549-soft-opt-out.md @@ -0,0 +1,3 @@ +- Avoid jailing validators immediately once they can no longer opt-out from + validating consumer chains. + ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/consumer/1549-soft-opt-out.md b/.changelog/unreleased/state-breaking/consumer/1549-soft-opt-out.md new file mode 100644 index 0000000000..6fa3d674cf --- /dev/null +++ b/.changelog/unreleased/state-breaking/consumer/1549-soft-opt-out.md @@ -0,0 +1,3 @@ +- Avoid jailing validators immediately once they can no longer opt-out from + validating consumer chains. + ([\#1549](https://github.com/cosmos/interchain-security/pull/1549)) \ No newline at end of file diff --git a/proto/interchain_security/ccv/consumer/v1/consumer.proto b/proto/interchain_security/ccv/consumer/v1/consumer.proto index b386bc2e9a..d3e5e4ca46 100644 --- a/proto/interchain_security/ccv/consumer/v1/consumer.proto +++ b/proto/interchain_security/ccv/consumer/v1/consumer.proto @@ -27,6 +27,7 @@ message CrossChainValidator { (cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\"" ]; + bool opted_out = 4; } // A record storing the state of a slash packet sent to the provider chain diff --git a/tests/integration/common.go b/tests/integration/common.go index 413cdaa810..bb49addd5b 100644 --- a/tests/integration/common.go +++ b/tests/integration/common.go @@ -596,21 +596,26 @@ func (suite *CCVTestSuite) GetConsumerEndpointClientAndConsState( } // setupValidatorPowers delegates from the sender account to give all -// validators on the provider chain 1000 power. -func (s *CCVTestSuite) setupValidatorPowers() { +// validators on the provider chain the given voting powers. +func (s *CCVTestSuite) setupValidatorPowers(powers []int64) { delAddr := s.providerChain.SenderAccount.GetAddress() + s.Require().Equal(len(powers), len(s.providerChain.Vals.Validators)) for idx := range s.providerChain.Vals.Validators { - delegateByIdx(s, delAddr, sdk.NewInt(999999999), idx) + bondAmt := sdk.NewInt(powers[idx]).Mul(sdk.DefaultPowerReduction) + bondAmt = bondAmt.Sub(sdk.NewInt(1)) // 1 token is bonded during the initial setup + delegateByIdx(s, delAddr, bondAmt, idx) } s.providerChain.NextBlock() stakingKeeper := s.providerApp.GetTestStakingKeeper() - for _, val := range s.providerChain.Vals.Validators { - power := stakingKeeper.GetLastValidatorPower(s.providerCtx(), sdk.ValAddress(val.Address)) - s.Require().Equal(int64(1000), power) + expectedTotalPower := int64(0) + for idx, val := range s.providerChain.Vals.Validators { + actualPower := stakingKeeper.GetLastValidatorPower(s.providerCtx(), sdk.ValAddress(val.Address)) + s.Require().Equal(powers[idx], actualPower) + expectedTotalPower += powers[idx] } - s.Require().Equal(int64(4000), stakingKeeper.GetLastTotalPower(s.providerCtx()).Int64()) + s.Require().Equal(expectedTotalPower, stakingKeeper.GetLastTotalPower(s.providerCtx()).Int64()) } // mustGetStakingValFromTmVal returns the staking validator from the current state of the staking keeper, diff --git a/tests/integration/slashing.go b/tests/integration/slashing.go index a25e9f2540..46918a38ba 100644 --- a/tests/integration/slashing.go +++ b/tests/integration/slashing.go @@ -443,7 +443,7 @@ func (suite *CCVTestSuite) TestValidatorDowntime() { ctx, ccv.ConsumerPortID, channelID) suite.Require().True(ok) - // Sign 100 blocks + // Sign 100 blocks (default value for slashing.SignedBlocksWindow param). valPower := int64(1) height, signedBlocksWindow := int64(0), consumerSlashingKeeper.SignedBlocksWindow(ctx) for ; height < signedBlocksWindow; height++ { diff --git a/tests/integration/soft_opt_out.go b/tests/integration/soft_opt_out.go new file mode 100644 index 0000000000..0483161133 --- /dev/null +++ b/tests/integration/soft_opt_out.go @@ -0,0 +1,247 @@ +package integration + +import ( + "bytes" + "sort" + + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + consumerKeeper "github.com/cosmos/interchain-security/v3/x/ccv/consumer/keeper" + ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" +) + +// TestSoftOptOut tests the soft opt-out feature +// - if a validator in the top 95% doesn't sign 50 blocks on the consumer, a SlashPacket is sent to the provider +// - if a validator in the bottom 5% doesn't sign 50 blocks on the consumer, a SlashPacket is NOT sent to the provider +// - if a validator in the bottom 5% doesn't sign 49 blocks on the consumer, +// then it moves to the top 95% and doesn't sign one more block, a SlashPacket is NOT sent to the provider +func (suite *CCVTestSuite) TestSoftOptOut() { + var votes []abci.VoteInfo + + testCases := []struct { + name string + downtimeFunc func(*consumerKeeper.Keeper, *slashingkeeper.Keeper, []byte, int) + targetValidator int + expJailed bool + expSlashPacket bool + }{ + { + "downtime top 95%", + func(ck *consumerKeeper.Keeper, sk *slashingkeeper.Keeper, valAddr []byte, valIdx int) { + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].SignedLastBlock = false + } + } + blocksToDowntime := sk.SignedBlocksWindow(suite.consumerCtx()) - sk.MinSignedPerWindow(suite.consumerCtx()) + 1 + slashingBeginBlocker(suite, votes, blocksToDowntime) + }, + 0, + true, + true, + }, + { + "downtime bottom 5%", + func(ck *consumerKeeper.Keeper, sk *slashingkeeper.Keeper, valAddr []byte, valIdx int) { + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].SignedLastBlock = false + } + } + blocksToDowntime := sk.SignedBlocksWindow(suite.consumerCtx()) - sk.MinSignedPerWindow(suite.consumerCtx()) + 1 + slashingBeginBlocker(suite, votes, blocksToDowntime) + }, + 3, + true, + false, + }, + { + "downtime bottom 5% first and then top 95%, but not enough", + func(ck *consumerKeeper.Keeper, sk *slashingkeeper.Keeper, valAddr []byte, valIdx int) { + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].SignedLastBlock = false + } + } + blocksToDowntime := sk.SignedBlocksWindow(suite.consumerCtx()) - sk.MinSignedPerWindow(suite.consumerCtx()) + slashingBeginBlocker(suite, votes, blocksToDowntime) + + // Increase the power of this validator (to bring it in the top 95%) + delAddr := suite.providerChain.SenderAccount.GetAddress() + bondAmt := sdk.NewInt(100).Mul(sdk.DefaultPowerReduction) + delegateByIdx(suite, delAddr, bondAmt, valIdx) + + suite.providerChain.NextBlock() + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) + + // Update validator from store + val, found := ck.GetCCValidator(suite.consumerCtx(), valAddr) + suite.Require().True(found) + smallestNonOptOutPower := ck.GetSmallestNonOptOutPower(suite.consumerCtx()) + suite.Require().Equal(val.Power, smallestNonOptOutPower) + + // Let the validator continue not signing, but not enough to get jailed + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].Validator.Power = val.Power + } + } + slashingBeginBlocker(suite, votes, 10) + }, + 2, + false, + false, + }, + { + "donwtime bottom 5% first and then top 95% until jailed", + func(ck *consumerKeeper.Keeper, sk *slashingkeeper.Keeper, valAddr []byte, valIdx int) { + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].SignedLastBlock = false + } + } + blocksToDowntime := sk.SignedBlocksWindow(suite.consumerCtx()) - sk.MinSignedPerWindow(suite.consumerCtx()) + slashingBeginBlocker(suite, votes, blocksToDowntime) + + // Increase the power of this validator (to bring it in the top 95%) + delAddr := suite.providerChain.SenderAccount.GetAddress() + bondAmt := sdk.NewInt(100).Mul(sdk.DefaultPowerReduction) + delegateByIdx(suite, delAddr, bondAmt, valIdx) + + suite.providerChain.NextBlock() + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) + + // Update validator from store + val, found := ck.GetCCValidator(suite.consumerCtx(), valAddr) + suite.Require().True(found) + smallestNonOptOutPower := ck.GetSmallestNonOptOutPower(suite.consumerCtx()) + suite.Require().Equal(val.Power, smallestNonOptOutPower) + + // Let the validator continue not signing until it gets jailed. + // Due to the starting height being just updated, the signed blocked window needs to pass. + for i, voteInfo := range votes { + if bytes.Equal(voteInfo.Validator.Address, valAddr) { + votes[i].Validator.Power = val.Power + } + } + slashingBeginBlocker(suite, votes, sk.SignedBlocksWindow(suite.consumerCtx())+1) + }, + 2, + true, + true, + }, + } + + for i, tc := range testCases { + // initial setup + suite.SetupCCVChannel(suite.path) + + consumerKeeper := suite.consumerApp.GetConsumerKeeper() + consumerSlashingKeeper := suite.consumerApp.GetTestSlashingKeeper() + + // Setup validator power s.t. the bottom 5% is non-empty + validatorPowers := []int64{1000, 500, 50, 10} + suite.setupValidatorPowers(validatorPowers) + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(suite, suite.providerChain, suite.path, ccv.ProviderPortID, suite.path.EndpointB.ChannelID, 1) + + // Check that the third validator is the first in the top 95% + smallestNonOptOutPower := consumerKeeper.GetSmallestNonOptOutPower(suite.consumerCtx()) + suite.Require().Equal(validatorPowers[1], smallestNonOptOutPower, "test: "+tc.name) + + // Get the list of all CCV validators + vals := consumerKeeper.GetAllCCValidator(suite.consumerCtx()) + // Note that GetAllCCValidator is iterating over a map so the result need to be sorted + sort.Slice(vals, func(i, j int) bool { + if vals[i].Power != vals[j].Power { + return vals[i].Power > vals[j].Power + } + return bytes.Compare(vals[i].Address, vals[j].Address) > 0 + }) + + // Let everyone sign the first 100 blocks (default value for slahing.SignedBlocksWindow param). + // This populates the signingInfo of the slashing module so that + // the check for starting height passes. + votes = []abci.VoteInfo{} + for _, val := range vals { + votes = append(votes, abci.VoteInfo{ + Validator: abci.Validator{Address: val.Address, Power: val.Power}, + SignedLastBlock: true, + }) + } + slashingBeginBlocker(suite, votes, consumerSlashingKeeper.SignedBlocksWindow(suite.consumerCtx())) + + // Downtime infraction + sk := consumerSlashingKeeper.(slashingkeeper.Keeper) + tc.downtimeFunc(&consumerKeeper, &sk, vals[tc.targetValidator].Address, tc.targetValidator) + + // Check the signing info for target validator + consAddr := sdk.ConsAddress(vals[tc.targetValidator].Address) + info, _ := consumerSlashingKeeper.GetValidatorSigningInfo(suite.consumerCtx(), consAddr) + if tc.expJailed { + // expect increased jail time + suite.Require().True( + info.JailedUntil.Equal(suite.consumerCtx().BlockTime().Add(consumerSlashingKeeper.DowntimeJailDuration(suite.consumerCtx()))), + "test: "+tc.name+"; did not update validator jailed until signing info", + ) + // expect missed block counters reset + suite.Require().Zero(info.MissedBlocksCounter, "test: "+tc.name+"; did not reset validator missed block counter") + suite.Require().Zero(info.IndexOffset, "test: "+tc.name) + consumerSlashingKeeper.IterateValidatorMissedBlockBitArray(suite.consumerCtx(), consAddr, func(_ int64, missed bool) bool { + suite.Require().True(missed, "test: "+tc.name) + return false + }) + } else { + suite.Require().True( + // expect not increased jail time + info.JailedUntil.Before(suite.consumerCtx().BlockTime()), + "test: "+tc.name+"; validator jailed until signing info was updated", + ) + suite.Require().Positive(info.IndexOffset, "test: "+tc.name) + } + + pendingPackets := consumerKeeper.GetPendingPackets(suite.consumerCtx()) + if tc.expSlashPacket { + // Check that slash packet is queued + suite.Require().NotEmpty(pendingPackets, "test: "+tc.name+"; pending packets empty") + suite.Require().Len(pendingPackets, 1, "test: "+tc.name+"; pending packets len should be 1 is %d", len(pendingPackets)) + cp := pendingPackets[0] + suite.Require().Equal(ccv.SlashPacket, cp.Type, "test: "+tc.name) + sp := cp.GetSlashPacketData() + suite.Require().Equal(stakingtypes.Infraction_INFRACTION_DOWNTIME, sp.Infraction, "test: "+tc.name) + suite.Require().Equal(vals[tc.targetValidator].Address, sp.Validator.Address, "test: "+tc.name) + } else { + suite.Require().Empty(pendingPackets, "test: "+tc.name+"; pending packets non-empty") + } + + if i+1 < len(testCases) { + // reset suite + suite.SetupTest() + } + } +} + +// slashingBeginBlocker is a mock for the slashing BeginBlocker. +// It applies the votes for a sequence of blocks +func slashingBeginBlocker(s *CCVTestSuite, votes []abci.VoteInfo, blocks int64) { + consumerSlashingKeeper := s.consumerApp.GetTestSlashingKeeper() + currentHeight := s.consumerCtx().BlockHeight() + for s.consumerCtx().BlockHeight() < currentHeight+blocks { + for _, voteInfo := range votes { + consumerSlashingKeeper.HandleValidatorSignature( + s.consumerCtx(), + voteInfo.Validator.Address, + voteInfo.Validator.Power, + voteInfo.SignedLastBlock, + ) + } + s.consumerChain.NextBlock() + } +} diff --git a/tests/integration/throttle.go b/tests/integration/throttle.go index 5ff538dbc4..749fbce095 100644 --- a/tests/integration/throttle.go +++ b/tests/integration/throttle.go @@ -64,7 +64,7 @@ func (s *CCVTestSuite) TestBasicSlashPacketThrottling() { s.SetupTest() s.SetupAllCCVChannels() - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) providerStakingKeeper := s.providerApp.GetTestStakingKeeper() @@ -186,7 +186,7 @@ func (s *CCVTestSuite) TestBasicSlashPacketThrottling() { func (s *CCVTestSuite) TestMultiConsumerSlashPacketThrottling() { // Setup test s.SetupAllCCVChannels() - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) var ( timeoutHeight = clienttypes.Height{} @@ -307,7 +307,7 @@ func (s *CCVTestSuite) TestPacketSpam() { s.SetupAllCCVChannels() // Setup validator powers to be 25%, 25%, 25%, 25% - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) // Explicitly set params, initialize slash meter providerKeeper := s.providerApp.GetProviderKeeper() @@ -373,7 +373,7 @@ func (s *CCVTestSuite) TestDoubleSignDoesNotAffectThrottling() { s.SetupAllCCVChannels() // Setup validator powers to be 25%, 25%, 25%, 25% - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) // Explicitly set params, initialize slash meter providerKeeper := s.providerApp.GetProviderKeeper() @@ -518,7 +518,7 @@ func (s *CCVTestSuite) TestSlashMeterAllowanceChanges() { // At first, allowance is based on 4 vals all with 1 power, min allowance is in effect. s.Require().Equal(int64(1), providerKeeper.GetSlashMeterAllowance(s.providerCtx()).Int64()) - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) // Now all 4 validators have 1000 power (4000 total power) so allowance should be: // default replenish frac * 4000 = 200 @@ -541,7 +541,7 @@ func (s CCVTestSuite) TestSlashAllValidators() { //nolint:govet // this is a tes s.SetupAllCCVChannels() // Setup 4 validators with 25% of the total power each. - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) providerKeeper := s.providerApp.GetProviderKeeper() diff --git a/tests/integration/throttle_retry.go b/tests/integration/throttle_retry.go index 4b445d5d25..9e03dbb306 100644 --- a/tests/integration/throttle_retry.go +++ b/tests/integration/throttle_retry.go @@ -14,7 +14,7 @@ import ( func (s *CCVTestSuite) TestSlashRetries() { s.SetupAllCCVChannels() s.SendEmptyVSCPacket() // Establish ccv channel - s.setupValidatorPowers() + s.setupValidatorPowers([]int64{1000, 1000, 1000, 1000}) // // Provider setup diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index dc1ae22543..1070083990 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -149,6 +149,14 @@ func TestCISBeforeCCVEstablished(t *testing.T) { runCCVTestByName(t, "TestCISBeforeCCVEstablished") } +// +// Soft opt out tests +// + +func TestSoftOptOut(t *testing.T) { + runCCVTestByName(t, "TestSoftOptOut") +} + // // Stop consumer tests // diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 02b4749e43..5f9d9b2694 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -473,6 +473,18 @@ func (mr *MockSlashingKeeperMockRecorder) GetValidatorSigningInfo(ctx, address i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorSigningInfo", reflect.TypeOf((*MockSlashingKeeper)(nil).GetValidatorSigningInfo), ctx, address) } +// SetValidatorSigningInfo mocks base method. +func (m *MockSlashingKeeper) SetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress, info types3.ValidatorSigningInfo) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValidatorSigningInfo", ctx, address, info) +} + +// SetValidatorSigningInfo indicates an expected call of SetValidatorSigningInfo. +func (mr *MockSlashingKeeperMockRecorder) SetValidatorSigningInfo(ctx, address interface{}, info interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorSigningInfo", reflect.TypeOf((*MockSlashingKeeper)(nil).SetValidatorSigningInfo), ctx, address, info) +} + // IsTombstoned mocks base method. func (m *MockSlashingKeeper) IsTombstoned(arg0 types0.Context, arg1 types0.ConsAddress) bool { m.ctrl.T.Helper() diff --git a/x/ccv/consumer/keeper/soft_opt_out.go b/x/ccv/consumer/keeper/soft_opt_out.go index 09b8a9be79..0519b05d3f 100644 --- a/x/ccv/consumer/keeper/soft_opt_out.go +++ b/x/ccv/consumer/keeper/soft_opt_out.go @@ -9,6 +9,15 @@ import ( "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" ) +// BeginBlockSoftOptOut executes BeginBlock logic for the Soft Opt-Out sub-protocol +func (k Keeper) BeginBlockSoftOptOut(ctx sdk.Context) { + // Update smallest validator power that cannot opt out. + k.UpdateSmallestNonOptOutPower(ctx) + + // Update the SigningInfo structs of the Slashing module + k.UpdateSlashingSigningInfo(ctx) +} + // SetSmallestNonOptOutPower sets the smallest validator power that cannot soft opt out. func (k Keeper) SetSmallestNonOptOutPower(ctx sdk.Context, power uint64) { store := ctx.KVStore(k.storeKey) @@ -73,3 +82,37 @@ func (k Keeper) GetSmallestNonOptOutPower(ctx sdk.Context) int64 { } return int64(binary.BigEndian.Uint64(bz)) } + +func (k Keeper) UpdateSlashingSigningInfo(ctx sdk.Context) { + smallestNonOptOutPower := k.GetSmallestNonOptOutPower(ctx) + + // Update SigningInfo for opted out validators + valset := k.GetAllCCValidator(ctx) + // Note that we don't need to sort the valset as GetAllCCValidator + // uses KVStorePrefixIterator that iterates over all the keys with + // a certain prefix in ascending order + for _, val := range valset { + consAddr := sdk.ConsAddress(val.Address) + signingInfo, found := k.slashingKeeper.GetValidatorSigningInfo(ctx, consAddr) + if !found { + continue + } + if val.Power < smallestNonOptOutPower { + // validator CAN opt-out from validating on consumer chains + if val.OptedOut == false { + // previously the validator couldn't opt-out + val.OptedOut = true + } + } else { + // validator CANNOT opt-out from validating on consumer chains + if val.OptedOut == true { + // previously the validator could opt-out + signingInfo.StartHeight = ctx.BlockHeight() + val.OptedOut = false + } + } + + k.slashingKeeper.SetValidatorSigningInfo(ctx, consAddr, signingInfo) + k.SetCCValidator(ctx, val) + } +} diff --git a/x/ccv/consumer/module.go b/x/ccv/consumer/module.go index f063075211..5ccf4d4f8f 100644 --- a/x/ccv/consumer/module.go +++ b/x/ccv/consumer/module.go @@ -138,8 +138,8 @@ func (AppModule) ConsensusVersion() uint64 { // Set the VSC ID for the subsequent block to the same value as the current block // Panic if the provider's channel was established and then closed func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - // Update smallest validator power that cannot opt out. - am.keeper.UpdateSmallestNonOptOutPower(ctx) + // Execute BeginBlock logic for the Soft Opt-Out sub-protocol + am.keeper.BeginBlockSoftOptOut(ctx) channelID, found := am.keeper.GetProviderChannel(ctx) if found && am.keeper.IsChannelClosed(ctx, channelID) { diff --git a/x/ccv/consumer/types/consumer.pb.go b/x/ccv/consumer/types/consumer.pb.go index 93a3c32a83..a28a3a1c2e 100644 --- a/x/ccv/consumer/types/consumer.pb.go +++ b/x/ccv/consumer/types/consumer.pb.go @@ -39,7 +39,8 @@ type CrossChainValidator struct { Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` // pubkey is the consensus public key of the validator, as a Protobuf Any. - Pubkey *types.Any `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty" yaml:"consensus_pubkey"` + Pubkey *types.Any `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty" yaml:"consensus_pubkey"` + OptedOut bool `protobuf:"varint,4,opt,name=opted_out,json=optedOut,proto3" json:"opted_out,omitempty"` } func (m *CrossChainValidator) Reset() { *m = CrossChainValidator{} } @@ -96,6 +97,13 @@ func (m *CrossChainValidator) GetPubkey() *types.Any { return nil } +func (m *CrossChainValidator) GetOptedOut() bool { + if m != nil { + return m.OptedOut + } + return false +} + // A record storing the state of a slash packet sent to the provider chain // which may bounce back and forth until handled by the provider. // @@ -162,33 +170,35 @@ func init() { } var fileDescriptor_5b27a82b276e7f93 = []byte{ - // 413 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcd, 0x8e, 0xd3, 0x30, - 0x10, 0xc7, 0x6b, 0x56, 0x2c, 0xc5, 0x45, 0x08, 0x85, 0x4a, 0x74, 0x7b, 0x48, 0xab, 0x70, 0xc9, - 0x65, 0x6d, 0x6d, 0x7b, 0x43, 0xe2, 0xb0, 0xdd, 0x23, 0x07, 0x50, 0x40, 0x20, 0x71, 0x89, 0x1c, - 0xc7, 0xa4, 0x16, 0x89, 0x27, 0xb2, 0x9d, 0x2c, 0xe6, 0x29, 0xf6, 0x31, 0x78, 0x00, 0x1e, 0x62, - 0xc5, 0x69, 0x8f, 0x9c, 0x0a, 0x6a, 0xdf, 0x80, 0x27, 0x40, 0xf9, 0x68, 0x11, 0xb0, 0xb7, 0xf9, - 0xf0, 0x7f, 0xe6, 0x37, 0xe3, 0xc1, 0x0b, 0xa9, 0xac, 0xd0, 0x7c, 0xcd, 0xa4, 0x8a, 0x8d, 0xe0, - 0x95, 0x96, 0xd6, 0x51, 0xce, 0x6b, 0xca, 0x41, 0x99, 0xaa, 0x10, 0x9a, 0xd6, 0x67, 0x07, 0x9b, - 0x94, 0x1a, 0x2c, 0x78, 0x4f, 0x6f, 0xd1, 0x10, 0xce, 0x6b, 0x72, 0x78, 0x57, 0x9f, 0x4d, 0x4f, - 0x32, 0x80, 0x2c, 0x17, 0xb4, 0x95, 0x24, 0xd5, 0x07, 0xca, 0x94, 0xeb, 0xf4, 0xd3, 0x71, 0x06, - 0x19, 0xb4, 0x26, 0x6d, 0xac, 0x3e, 0x7a, 0xc2, 0xc1, 0x14, 0x60, 0xe2, 0x2e, 0xd1, 0x39, 0x7d, - 0x6a, 0xf6, 0x6f, 0x2d, 0x2b, 0x0b, 0x61, 0x2c, 0x2b, 0xca, 0xee, 0x41, 0xf0, 0x05, 0xe1, 0xc7, - 0x17, 0x1a, 0x8c, 0xb9, 0x68, 0xa0, 0xde, 0xb2, 0x5c, 0xa6, 0xcc, 0x82, 0xf6, 0x26, 0xf8, 0x1e, - 0x4b, 0x53, 0x2d, 0x8c, 0x99, 0xa0, 0x39, 0x0a, 0x1f, 0x44, 0x7b, 0xd7, 0x1b, 0xe3, 0xbb, 0x25, - 0x5c, 0x0a, 0x3d, 0xb9, 0x33, 0x47, 0xe1, 0x51, 0xd4, 0x39, 0x1e, 0xc3, 0xc7, 0x65, 0x95, 0x7c, - 0x14, 0x6e, 0x72, 0x34, 0x47, 0xe1, 0x68, 0x31, 0x26, 0x5d, 0x67, 0xb2, 0xef, 0x4c, 0xce, 0x95, - 0x5b, 0x2d, 0x7f, 0x6d, 0x66, 0x4f, 0x1c, 0x2b, 0xf2, 0x67, 0x41, 0x33, 0xb1, 0x50, 0xa6, 0x32, - 0x71, 0xa7, 0x0b, 0xbe, 0x7d, 0x3d, 0x1d, 0xf7, 0xec, 0x5c, 0xbb, 0xd2, 0x02, 0x79, 0x55, 0x25, - 0x2f, 0x84, 0x8b, 0xfa, 0xc2, 0xc1, 0x67, 0x3c, 0x7a, 0x9d, 0x33, 0xb3, 0x8e, 0x04, 0x07, 0x9d, - 0x7a, 0x21, 0x7e, 0x74, 0xc9, 0xa4, 0x95, 0x2a, 0x8b, 0x41, 0xc5, 0x5a, 0x94, 0xb9, 0x6b, 0x51, - 0x87, 0xd1, 0xc3, 0x3e, 0xfe, 0x52, 0x45, 0x4d, 0xd4, 0x3b, 0xc7, 0xf7, 0x8d, 0x50, 0x69, 0xdc, - 0xcc, 0xde, 0x52, 0x8f, 0x16, 0xd3, 0xff, 0xf0, 0xde, 0xec, 0x17, 0xb3, 0x1a, 0x5e, 0x6f, 0x66, - 0x83, 0xab, 0x1f, 0x33, 0x14, 0x0d, 0x1b, 0x59, 0x93, 0x58, 0xbd, 0xbb, 0xde, 0xfa, 0xe8, 0x66, - 0xeb, 0xa3, 0x9f, 0x5b, 0x1f, 0x5d, 0xed, 0xfc, 0xc1, 0xcd, 0xce, 0x1f, 0x7c, 0xdf, 0xf9, 0x83, - 0xf7, 0xcf, 0x33, 0x69, 0xd7, 0x55, 0x42, 0x38, 0x14, 0xfd, 0xea, 0xe9, 0x9f, 0x4f, 0x3e, 0x3d, - 0x1c, 0x46, 0xbd, 0xa4, 0x9f, 0xfe, 0xbe, 0x0e, 0xeb, 0x4a, 0x61, 0x92, 0xe3, 0x16, 0x60, 0xf9, - 0x3b, 0x00, 0x00, 0xff, 0xff, 0x43, 0xb2, 0x00, 0x6a, 0x4e, 0x02, 0x00, 0x00, + // 433 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xce, 0x52, 0x28, 0xee, 0x06, 0x21, 0x64, 0x22, 0xe1, 0x06, 0xc9, 0x89, 0xc2, 0xc5, 0x97, + 0xda, 0x6a, 0x72, 0x43, 0xe2, 0xd0, 0xf4, 0xc8, 0xa1, 0xc8, 0x20, 0x90, 0xb8, 0x58, 0xeb, 0xf5, + 0xe2, 0x58, 0xd8, 0x3b, 0xab, 0xfd, 0x71, 0x59, 0x9e, 0xa2, 0x0f, 0xc3, 0x2b, 0x20, 0x55, 0x9c, + 0x7a, 0xe4, 0x14, 0x50, 0xf2, 0x06, 0x3c, 0x01, 0xf2, 0x4f, 0x82, 0x80, 0xde, 0x66, 0xbe, 0xd9, + 0x6f, 0xe6, 0x9b, 0xd9, 0x0f, 0xcf, 0x0b, 0xae, 0x99, 0xa4, 0x2b, 0x52, 0xf0, 0x44, 0x31, 0x6a, + 0x64, 0xa1, 0x6d, 0x44, 0x69, 0x1d, 0x51, 0xe0, 0xca, 0x54, 0x4c, 0x46, 0xf5, 0xe9, 0x3e, 0x0e, + 0x85, 0x04, 0x0d, 0xee, 0xb3, 0x5b, 0x38, 0x21, 0xa5, 0x75, 0xb8, 0x7f, 0x57, 0x9f, 0x8e, 0x8f, + 0x73, 0x80, 0xbc, 0x64, 0x51, 0x4b, 0x49, 0xcd, 0x87, 0x88, 0x70, 0xdb, 0xf1, 0xc7, 0xa3, 0x1c, + 0x72, 0x68, 0xc3, 0xa8, 0x89, 0x7a, 0xf4, 0x98, 0x82, 0xaa, 0x40, 0x25, 0x5d, 0xa1, 0x4b, 0xfa, + 0xd2, 0xe4, 0xdf, 0x5e, 0xba, 0xa8, 0x98, 0xd2, 0xa4, 0x12, 0xdd, 0x83, 0xd9, 0x57, 0x84, 0x1f, + 0x9f, 0x4b, 0x50, 0xea, 0xbc, 0x11, 0xf5, 0x96, 0x94, 0x45, 0x46, 0x34, 0x48, 0xd7, 0xc3, 0xf7, + 0x49, 0x96, 0x49, 0xa6, 0x94, 0x87, 0xa6, 0x28, 0x78, 0x10, 0xef, 0x52, 0x77, 0x84, 0xef, 0x09, + 0xb8, 0x64, 0xd2, 0xbb, 0x33, 0x45, 0xc1, 0x41, 0xdc, 0x25, 0x2e, 0xc1, 0x87, 0xc2, 0xa4, 0x1f, + 0x99, 0xf5, 0x0e, 0xa6, 0x28, 0x18, 0xce, 0x47, 0x61, 0x37, 0x39, 0xdc, 0x4d, 0x0e, 0xcf, 0xb8, + 0x5d, 0x2e, 0x7e, 0xad, 0x27, 0x4f, 0x2c, 0xa9, 0xca, 0xe7, 0xb3, 0x66, 0x63, 0xc6, 0x95, 0x51, + 0x49, 0xc7, 0x9b, 0x7d, 0xfb, 0x72, 0x32, 0xea, 0xb5, 0x53, 0x69, 0x85, 0x86, 0xf0, 0x95, 0x49, + 0x5f, 0x32, 0x1b, 0xf7, 0x8d, 0xdd, 0xa7, 0xf8, 0x08, 0x84, 0x66, 0x59, 0x02, 0x46, 0x7b, 0x77, + 0xa7, 0x28, 0x70, 0x62, 0xa7, 0x05, 0x2e, 0x8c, 0x9e, 0x7d, 0xc6, 0xc3, 0xd7, 0x25, 0x51, 0xab, + 0x98, 0x51, 0x90, 0x99, 0x1b, 0xe0, 0x47, 0x97, 0xa4, 0xd0, 0x05, 0xcf, 0x13, 0xe0, 0x89, 0x64, + 0xa2, 0xb4, 0xed, 0x1e, 0x4e, 0xfc, 0xb0, 0xc7, 0x2f, 0x78, 0xdc, 0xa0, 0xee, 0x19, 0x3e, 0x52, + 0x8c, 0x67, 0x49, 0x73, 0x98, 0x76, 0xa5, 0xe1, 0x7c, 0xfc, 0x9f, 0xf6, 0x37, 0xbb, 0xab, 0x2d, + 0x9d, 0xeb, 0xf5, 0x64, 0x70, 0xf5, 0x63, 0x82, 0x62, 0xa7, 0xa1, 0x35, 0x85, 0xe5, 0xbb, 0xeb, + 0x8d, 0x8f, 0x6e, 0x36, 0x3e, 0xfa, 0xb9, 0xf1, 0xd1, 0xd5, 0xd6, 0x1f, 0xdc, 0x6c, 0xfd, 0xc1, + 0xf7, 0xad, 0x3f, 0x78, 0xff, 0x22, 0x2f, 0xf4, 0xca, 0xa4, 0x21, 0x85, 0xaa, 0xff, 0x97, 0xe8, + 0x8f, 0x03, 0x4e, 0xf6, 0xae, 0xa9, 0x17, 0xd1, 0xa7, 0xbf, 0xad, 0xa3, 0xad, 0x60, 0x2a, 0x3d, + 0x6c, 0x05, 0x2c, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0x18, 0x58, 0x87, 0xab, 0x6b, 0x02, 0x00, + 0x00, } func (m *CrossChainValidator) Marshal() (dAtA []byte, err error) { @@ -211,6 +221,16 @@ func (m *CrossChainValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.OptedOut { + i-- + if m.OptedOut { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } if m.Pubkey != nil { { size, err := m.Pubkey.MarshalToSizedBuffer(dAtA[:i]) @@ -307,6 +327,9 @@ func (m *CrossChainValidator) Size() (n int) { l = m.Pubkey.Size() n += 1 + l + sovConsumer(uint64(l)) } + if m.OptedOut { + n += 2 + } return n } @@ -448,6 +471,26 @@ func (m *CrossChainValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OptedOut", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConsumer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.OptedOut = bool(v != 0) default: iNdEx = preIndex skippy, err := skipConsumer(dAtA[iNdEx:]) diff --git a/x/ccv/consumer/types/validator.go b/x/ccv/consumer/types/validator.go index b134dde078..f1f9fa260b 100644 --- a/x/ccv/consumer/types/validator.go +++ b/x/ccv/consumer/types/validator.go @@ -15,9 +15,10 @@ func NewCCValidator(address []byte, power int64, pubKey cryptotypes.PubKey) (Cro } return CrossChainValidator{ - Address: address, - Power: power, - Pubkey: pkAny, + Address: address, + Power: power, + Pubkey: pkAny, + OptedOut: false, }, nil } diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 83b50bd173..a2ef7ab465 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -59,6 +59,7 @@ type StakingKeeper interface { type SlashingKeeper interface { JailUntil(sdk.Context, sdk.ConsAddress, time.Time) // called from provider keeper only GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info slashingtypes.ValidatorSigningInfo, found bool) + SetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) DowntimeJailDuration(sdk.Context) time.Duration SlashFractionDowntime(sdk.Context) sdk.Dec SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec)