diff --git a/tests/e2e/misbehaviour.go b/tests/e2e/misbehaviour.go index e330a9d081..5c6347dd5c 100644 --- a/tests/e2e/misbehaviour.go +++ b/tests/e2e/misbehaviour.go @@ -1,14 +1,12 @@ package e2e import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" - ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" - ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" tmtypes "github.com/tendermint/tendermint/types" ) @@ -76,412 +74,14 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { } -// mostly based on TestCheckMisbehaviourAndUpdateState in ibc-go/modules/core/02-client/keeper/client_test.go -func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { +func (s *CCVTestSuite) TestConstructLigthClientEvidence() { - s.SetupCCVChannel(s.path) - // required to have the consumer client revision height greater than 0 - s.SendEmptyVSCPacket() - - consumerConsState, ok := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) - s.Require().True(ok) - - clientHeight := s.consumerChain.LastHeader.TrustedHeight - clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) - clientSigners := s.consumerChain.Signers - - altPrivVal := ibctestingmock.NewPV() - altPubKey, err := altPrivVal.GetPubKey() - s.Require().NoError(err) - altVal := tmtypes.NewValidator(altPubKey, 4) - - altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - altSigners := make(map[string]tmtypes.PrivValidator, 1) - altSigners[altValset.Validators[0].Address.String()] = altPrivVal - - altTime := s.providerCtx().BlockTime().Add(time.Minute) - heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) - heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) - - testCases := []struct { - name string - misbehaviour func() *ibctmtypes.Misbehaviour - malleate func() - expPass bool - }{ - { - "misbehaviour height is at same height as trusted height", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "invalid chain ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - mb.Header1.Header.ChainID = "wrongchainid" - return mb - - }, - func() {}, - false, - }, - { - "invalid client ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: "wrongclientid", - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - return mb - - }, - func() {}, - false, - }, { - "different trusted height shouldn't pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "trusting period misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "time misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+5), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "both later height should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() { - - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - // store trusted consensus state for Header2 - intermediateConsState := &ibctmtypes.ConsensusState{ - Timestamp: altTime, - NextValidatorsHash: clientTMValset.Hash(), - } - - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) - - clientState.LatestHeight = heightPlus3 - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - true, - }, - { - "trusted ConsensusState1 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "trusted ConsensusState2 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "client state not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() {}, - false, - }, { - "client already is not active - client is frozen", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() { - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - false, - }, - { - "misbehaviour check failed", //TODO: verify as client is already frozen - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - altValset, - altValset, - clientTMValset, - altSigners, - ), - } - }, - func() {}, - false, - }, - } - - for i, tc := range testCases { - - s.Run(tc.name, func() { - // run each test against fresh client states - cCtx, _ := s.providerCtx().CacheContext() + // test cases + // misbehaviour nil + // misbheaviour header 1 nil + // misbehaviour header 2 nil - tc.malleate() - - err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( - cCtx, - *tc.misbehaviour(), - ) - - // Misbehaviour passed - if tc.expPass { - s.NoError(err, "valid test case %s failed with error %s", tc.name, err) - clientState, found := s.providerApp.GetIBCKeeper().ClientKeeper.GetClientState(cCtx, tc.misbehaviour().ClientId) - s.Require().True(found, "valid test case %d failed: %s", i, tc.name) - s.Require().True(!clientState.(*ibctmtypes.ClientState).FrozenHeight.IsZero(), "valid test case %d failed: %s", i, tc.name) - - } else { - // Misbehaviour rejected - s.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - }) - } -} - -func (s *CCVTestSuite) TestGetByzantineValidators() { + // misbehaviour no common height s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -522,19 +122,153 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { ), } - err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( - s.providerCtx(), - *misb, - ) + _ = misb - s.NoError(err) + // emptyHeader := &ibctmtypes.Header{} - byzVals, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( - s.providerCtx(), - *misb, - ) - s.NoError(err) - s.Require().Equal(len(altValset.Validators), len(byzVals)) + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expPass bool + }{ + { + "invalid misbehaviour - Header1 is empty", + &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + )}, + false, + }, { + "invalid misbehaviour - Header2 is empty", + &ibctmtypes.Misbehaviour{ + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: &ibctmtypes.Header{}, + }, + false, + }, { + "invalid misbehaviour - ClientId is empty", + &ibctmtypes.Misbehaviour{ + ClientId: "unknown-client-id", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "light client attack - lunatic attack", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, { + "light client attack - equivocation", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + }, + true, + }, + } + + // check how it's tested on CometBFTNewExtensionOptionsDecorator + + for _, tc := range testCases { + s.Run(tc.name, func() { + ev, err := s.providerApp.GetProviderKeeper().ConstructLigthClientEvidence( + s.providerCtx(), + *tc.misbehaviour, + ) + if tc.expPass { + s.NoError(err) + s.Require().Equal(len(altValset.Validators), len(ev.ByzantineValidators)) + fmt.Println("headers 2 validators") + + for _, v := range tc.misbehaviour.Header2.ValidatorSet.Validators { + fmt.Println(v.String()) + } + fmt.Println("byzantine validators") + for _, v := range ev.ByzantineValidators { + fmt.Println(v.String()) + } + // TODO: check that the byzantine validators == altValset.Validators + } else { + s.Error(err) + } + }) + } - // TODO: check that the byzantine validators == altValset.Validators } diff --git a/testutil/e2e/debug_test.go b/testutil/e2e/debug_test.go index d33b2f5e34..9e19a2574d 100644 --- a/testutil/e2e/debug_test.go +++ b/testutil/e2e/debug_test.go @@ -248,10 +248,6 @@ func TestHandleConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerMisbehaviour") } -func TestCheckConsumerMisbehaviour(t *testing.T) { - runCCVTestByName(t, "TestCheckConsumerMisbehaviour") -} - -func TestGetByzantineValidators(t *testing.T) { - runCCVTestByName(t, "TestGetByzantineValidators") +func TestConstructLigthClientEvidence(t *testing.T) { + runCCVTestByName(t, "TestConstructLigthClientEvidence") } diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 5bc48a4b01..35d9dfd706 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -570,6 +570,20 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } +// CheckMisbehaviourAndUpdateState mocks base method. +func (m *MockClientKeeper) CheckMisbehaviourAndUpdateState(ctx types.Context, misbehaviour exported.Misbehaviour) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckMisbehaviourAndUpdateState", ctx, misbehaviour) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckMisbehaviourAndUpdateState indicates an expected call of CheckMisbehaviourAndUpdateState. +func (mr *MockClientKeeperMockRecorder) CheckMisbehaviourAndUpdateState(ctx, misbehaviour interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckMisbehaviourAndUpdateState", reflect.TypeOf((*MockClientKeeper)(nil).CheckMisbehaviourAndUpdateState), ctx, misbehaviour) +} + // ClientStore mocks base method. func (m *MockClientKeeper) ClientStore(ctx types.Context, clientID string) types.KVStore { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 5a612ca57e..d3b3163ef8 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -6,117 +6,87 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/ibc-go/v4/modules/core/exported" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) +// HandleConsumerMisbehaviour checks whether the given IBC misbehaviour is valid and, if they are, the misbehaving +// CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attck evidence. +// proceed to the jailing and tombstoning of the bzyantine validators. func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - if err := k.CheckConsumerMisbehaviour(ctx, misbehaviour); err != nil { - return err - } + logger := ctx.Logger() - byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) - if err != nil { + if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { return err } // Since the misbehaviour packet was received within the trusting period // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go - for _, v := range byzantineValidators { - // convert address to key assigned + + // construct a ligth client attack evidence + evidence, err := k.ConstructLigthClientEvidence(ctx, misbehaviour) + if err != nil { + return err + } + + // jail and tombstone the byzantine validators + for _, v := range evidence.ByzantineValidators { + // convert consumer consensus address consuAddr := sdk.ConsAddress(v.Address.Bytes()) provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consuAddr) k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr) + if !ok || val.IsUnbonded() { - // Defensive: Simulation doesn't take unbonding periods into account, and - // Tendermint might break this assumption at some point. - k.Logger(ctx).Error("validator not found or is unbonded", provAddr.String()) + logger.Error("validator not found or is unbonded", provAddr.String()) continue } - // TODO: continue if validator is already tombstoned/jailed + log - k.stakingKeeper.Jail(ctx, provAddr) + + // jail validator if not already + if !val.IsJailed() { + k.stakingKeeper.Jail(ctx, provAddr) + } + + // tombstone validator if not already + if !k.slashingKeeper.IsTombstoned(ctx, provAddr) { + k.slashingKeeper.Tombstone(ctx, provAddr) + } + + // update jail time to end after double sign jail duration k.slashingKeeper.JailUntil(ctx, provAddr, evidencetypes.DoubleSignJailEndTime) - k.slashingKeeper.Tombstone(ctx, provAddr) - // store misbehaviour? } - logger := ctx.Logger() logger.Info( "confirmed equivocation", - "byzantine validators", byzantineValidators, + "byzantine validators", evidence.ByzantineValidators, ) return nil } -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - - clientID := misbehaviour.GetClientID() - - clientState, found := k.clientKeeper.GetClientState(ctx, clientID) - if !found { - return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) - } - - clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) - - if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { - return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) - } - - if err := misbehaviour.ValidateBasic(); err != nil { - return err - } - - trusted, conflicted := misbehaviour.Header1, misbehaviour.Header2 - - // A common trusted height is required to get the byzantine validators who signed both headers - if !trusted.TrustedHeight.EQ(conflicted.TrustedHeight) { - return fmt.Errorf("misbehaviour headers have different trusted height %d != %d", trusted.TrustedHeight, conflicted.TrustedHeight) - } - - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) - if err != nil { - return err - } - - k.clientKeeper.SetClientState(ctx, clientID, clientState) - k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) - - // TBD - // defer func() { - // telemetry.IncrCounterWithLabels( - // []string{"ibc", "client", "misbehaviour"}, - // 1, - // []metrics.Label{ - // telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()), - // telemetry.NewLabel(types.LabelClientID, misbehaviour.GetClientID()), - // }, - // ) - // }() - - // EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) - return nil -} - -func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { +// ConstructLigthClientEvidence constructs and returns a CometBFT Ligth Client Attack(LCA) evidence struct +// from the given misbehaviour +func (k Keeper) ConstructLigthClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { - trusted, err := HeaderToLightBlock(*misbehaviour.Header1) + // construct the trusted and conflicetd ligth blocks + trusted, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err } - conflicted, err := HeaderToLightBlock(*misbehaviour.Header2) + conflicted, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { return nil, err } - commonHeight, commonTs, commonValset, err := k.GetCommonFromMisbehaviour(ctx, misbehaviour) + + // get common header using the IBC misbehaviour + commonHeight, commonTs, commonValset, err := k.GetTrustedInfoFromMisbehaviour(ctx, misbehaviour) if err != nil { return nil, err } + // construct the LCA evidence by copying the CometBFT constructor + // see newLightClientAttackEvidence() in tendermint/light/detector.go ev := tmtypes.LightClientAttackEvidence{ ConflictingBlock: conflicted, } @@ -131,12 +101,16 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() } - return ev.GetByzantineValidators(commonValset, trusted.SignedHeader), nil + ev.ByzantineValidators = ev.GetByzantineValidators(commonValset, trusted.SignedHeader) + + return &ev, nil } -func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { +// GetCommonFromMisbehaviour checks whether the given ibc misbehaviour's headers share common trusted height +// and that a consensus state exists for this height. In this case, it returns the associated trusted height, timestamp and valset. +func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // A common trusted height is required + // a common trusted height is required commonHeight := misbehaviour.Header1.TrustedHeight if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted height: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) @@ -155,7 +129,8 @@ func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtyp return int64(commonHeight.RevisionHeight), time.Unix(0, int64(cs.GetTimestamp())), vs, nil } -func HeaderToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { +// headerToLightBlock returns a CometBFT ligth block from the given IBC header +func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { sh, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) if err != nil { return nil, err diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 0195918272..24b78325c4 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -81,6 +81,7 @@ type ClientKeeper interface { ClientStore(ctx sdk.Context, clientID string) sdk.KVStore SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) GetClientConsensusState(ctx sdk.Context, clientID string, height ibcexported.Height) (ibcexported.ConsensusState, bool) + CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour ibcexported.Misbehaviour) error } // TODO: Expected interfaces for distribution on provider and consumer chains