Skip to content

Commit

Permalink
refactor: removing CheckMisbehaviourAndUpdateState from ClientState i…
Browse files Browse the repository at this point in the history
…nterface (cosmos#1212)

* refactor: removing CheckForMisbehaviourAndUpdateState from ClientState interface and associated client implemtations

* chore: changelog
  • Loading branch information
seantking authored and oshorefueled committed Aug 9, 2022
1 parent 7cb5ea8 commit be68f1e
Show file tree
Hide file tree
Showing 8 changed files with 1 addition and 1,044 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements
* (modules/core/02-client) [\#1188](https://github.com/cosmos/ibc-go/pull/1188/files) Routing `MsgSubmitMisbehaviour` to `UpdateClient` keeper function. Deprecating `SubmitMisbehaviour` endpoint.


* (modules/core/02-client) [\#1208](https://github.com/cosmos/ibc-go/pull/1208) Replace `CheckHeaderAndUpdateState` usage in 02-client with calls to `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour` and `UpdateState`.
* (modules/light-clients/09-localhost) [\#1187](https://github.com/cosmos/ibc-go/pull/1187/) Removing localhost light client implementation as it is not functional.
* [\#1186](https://github.com/cosmos/ibc-go/pull/1186/files) Removing `GetRoot` function from ConsensusState interface in `02-client`. `GetRoot` is unused by core IBC.
Expand All @@ -54,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (modules/core/02-client) [\#1170](https://github.com/cosmos/ibc-go/pull/1170) Updating `ClientUpdateProposal` to set client state in lightclient implementations `CheckSubstituteAndUpdateState` methods.
* (modules/core/02-client) [\#1197](https://github.com/cosmos/ibc-go/pull/1197) Adding `CheckForMisbehaviour` to `ClientState` interface.
* (modules/core/02-client) [\#1195](https://github.com/cosmos/ibc-go/pull/1210) Removing `CheckHeaderAndUpdateState` from `ClientState` interface & associated light client implementations.
* (modules/core/02-client) [\#1189](https://github.com/cosmos/ibc-go/pull/1212) Removing `CheckMisbehaviourAndUpdateState` from `ClientState` interface & associated light client implementations.
* (modules/core/exported) [\#1206](https://github.com/cosmos/ibc-go/pull/1206) Adding new method `UpdateState` to `ClientState` interface.

### Features
Expand Down
42 changes: 0 additions & 42 deletions modules/core/02-client/keeper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,45 +161,3 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e

return nil
}

// CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the
// client if so.
func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, clientID string, misbehaviour exported.ClientMessage) error {
clientState, found := k.GetClientState(ctx, clientID)
if !found {
return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", clientID)
}

clientStore := k.ClientStore(ctx, clientID)

if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active {
return sdkerrors.Wrapf(types.ErrClientNotActive, "cannot process misbehaviour for client (%s) with status %s", clientID, status)
}

if err := misbehaviour.ValidateBasic(); err != nil {
return err
}

clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, misbehaviour)
if err != nil {
return err
}

k.SetClientState(ctx, clientID, clientState)
k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID)

defer func() {
telemetry.IncrCounterWithLabels(
[]string{"ibc", "client", "misbehaviour"},
1,
[]metrics.Label{
telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()),
telemetry.NewLabel(types.LabelClientID, clientID),
},
)
}()

EmitSubmitMisbehaviourEvent(ctx, clientID, clientState)

return nil
}
249 changes: 0 additions & 249 deletions modules/core/02-client/keeper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"time"

upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
Expand All @@ -15,7 +14,6 @@ import (
solomachinetypes "github.com/cosmos/ibc-go/v3/modules/light-clients/06-solomachine/types"
ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types"
ibctesting "github.com/cosmos/ibc-go/v3/testing"
ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock"
)

func (suite *KeeperTestSuite) TestCreateClient() {
Expand Down Expand Up @@ -408,253 +406,6 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {

}

func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() {
var (
clientID string
err error
)

altPrivVal := ibctestingmock.NewPV()
altPubKey, err := altPrivVal.GetPubKey()
suite.Require().NoError(err)
altVal := tmtypes.NewValidator(altPubKey, 4)

// Set valSet here with suite.valSet so it doesn't get reset on each testcase
valSet := suite.valSet
valsHash := valSet.Hash()

// Create bothValSet with both suite validator and altVal
bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal))
bothValsHash := bothValSet.Hash()
// Create alternative validator set with only altVal
altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal})

// Create signer array and ensure it is in same order as bothValSet
_, suiteVal := suite.valSet.GetByIndex(0)
bothSigners := make(map[string]tmtypes.PrivValidator, 2)
bothSigners[suiteVal.Address.String()] = suite.privVal
bothSigners[altVal.Address.String()] = altPrivVal

altSigners := make(map[string]tmtypes.PrivValidator, 1)
altSigners[altVal.Address.String()] = altPrivVal

// Create valid Misbehaviour by making a duplicate header that signs over different block time
altTime := suite.ctx.BlockTime().Add(time.Minute)

heightPlus3 := types.NewHeight(0, height+3)
heightPlus5 := types.NewHeight(0, height+5)

testCases := []struct {
name string
misbehaviour *ibctmtypes.Misbehaviour
malleate func() error
expPass bool
}{
{
"trusting period misbehavior should pass",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = bothValsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

return err
},
true,
},
{
"time misbehavior should pass",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+5), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = bothValsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

return err
},
true,
},
{
"misbehavior at later height should pass",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, valSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, valSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = valsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

// store intermediate consensus state to check that trustedHeight does not need to be highest consensus state before header height
intermediateConsState := &ibctmtypes.ConsensusState{
Timestamp: suite.now.Add(time.Minute),
NextValidatorsHash: suite.chainB.Vals.Hash(),
}
suite.keeper.SetClientConsensusState(suite.ctx, clientID, heightPlus3, intermediateConsState)

clientState.LatestHeight = heightPlus3
suite.keeper.SetClientState(suite.ctx, clientID, clientState)

return err
},
true,
},
{
"misbehavior at later height with different trusted heights should pass",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, valSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = valsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

// store trusted consensus state for Header2
intermediateConsState := &ibctmtypes.ConsensusState{
Timestamp: suite.now.Add(time.Minute),
NextValidatorsHash: bothValsHash,
}
suite.keeper.SetClientConsensusState(suite.ctx, clientID, heightPlus3, intermediateConsState)

clientState.LatestHeight = heightPlus3
suite.keeper.SetClientState(suite.ctx, clientID, clientState)

return err
},
true,
},
{
"misbehavior ValidateBasic fails: misbehaviour height is at same height as trusted height",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = bothValsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

return err
},
false,
},
{
"trusted ConsensusState1 not found",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, valSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = valsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)
// intermediate consensus state at height + 3 is not created
return err
},
false,
},
{
"trusted ConsensusState2 not found",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, valSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = valsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)
// intermediate consensus state at height + 3 is not created
return err
},
false,
},
{
"client state not found",
&ibctmtypes.Misbehaviour{},
func() error { return nil },
false,
},
{
"client already is not active - client is frozen",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothValSet, bothSigners),
ClientId: clientID,
},
func() error {
suite.consensusState.NextValidatorsHash = bothValsHash
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

clientState.FrozenHeight = types.NewHeight(0, 1)
suite.keeper.SetClientState(suite.ctx, clientID, clientState)

return err
},
false,
},
{
"misbehaviour check failed",
&ibctmtypes.Misbehaviour{
Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothValSet, bothSigners),
Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), altValSet, altValSet, bothValSet, altSigners),
ClientId: clientID,
},
func() error {
clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false)
if err != nil {
return err
}
clientID, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState)

return err
},
false,
},
}

for i, tc := range testCases {
tc := tc
i := i

suite.Run(tc.name, func() {
suite.SetupTest() // reset
clientID = testClientID // must be explicitly changed

err := tc.malleate()
suite.Require().NoError(err)

tc.misbehaviour.ClientId = clientID

err = suite.keeper.CheckMisbehaviourAndUpdateState(suite.ctx, clientID, tc.misbehaviour)

if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name)

clientState, found := suite.keeper.GetClientState(suite.ctx, clientID)
suite.Require().True(found, "valid test case %d failed: %s", i, tc.name)
suite.Require().True(!clientState.(*ibctmtypes.ClientState).FrozenHeight.IsZero(), "valid test case %d failed: %s", i, tc.name)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name)
}
})
}
}

func (suite *KeeperTestSuite) TestUpdateClientEventEmission() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.SetupClients(path)
Expand Down
1 change: 0 additions & 1 deletion modules/core/exported/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ type ClientState interface {
UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) error

// Update and Misbehaviour functions
CheckMisbehaviourAndUpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) (ClientState, error)
CheckSubstituteAndUpdateState(ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore sdk.KVStore, substituteClient ClientState) (ClientState, error)

// Upgrade functions
Expand Down
43 changes: 0 additions & 43 deletions modules/light-clients/06-solomachine/types/misbehaviour_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,8 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v3/modules/core/exported"
)

// CheckMisbehaviourAndUpdateState determines whether or not the currently registered
// public key signed over two different messages with the same sequence. If this is true
// the client state is updated to a frozen status.
// NOTE: Misbehaviour is not tracked for previous public keys, a solo machine may update to
// a new public key before the misbehaviour is processed. Therefore, misbehaviour is data
// order processing dependent.
func (cs ClientState) CheckMisbehaviourAndUpdateState(
ctx sdk.Context,
cdc codec.BinaryCodec,
clientStore sdk.KVStore,
misbehaviour exported.ClientMessage,
) (exported.ClientState, error) {

soloMisbehaviour, ok := misbehaviour.(*Misbehaviour)
if !ok {
return nil, sdkerrors.Wrapf(
clienttypes.ErrInvalidClientType,
"misbehaviour type %T, expected %T", misbehaviour, &Misbehaviour{},
)
}

// NOTE: a check that the misbehaviour message data are not equal is done by
// misbehaviour.ValidateBasic which is called by the 02-client keeper.

// verify first signature
if err := cs.verifySignatureAndData(cdc, soloMisbehaviour, soloMisbehaviour.SignatureOne); err != nil {
return nil, sdkerrors.Wrap(err, "failed to verify signature one")
}

// verify second signature
if err := cs.verifySignatureAndData(cdc, soloMisbehaviour, soloMisbehaviour.SignatureTwo); err != nil {
return nil, sdkerrors.Wrap(err, "failed to verify signature two")
}

cs.IsFrozen = true
return &cs, nil
}

// verifySignatureAndData verifies that the currently registered public key has signed
// over the provided data and that the data is valid. The data is valid if it can be
// unmarshaled into the specified data type.
Expand Down
Loading

0 comments on commit be68f1e

Please sign in to comment.