From f4d959e6668eb9432ba428c7826f83129c015e2a Mon Sep 17 00:00:00 2001 From: Sean King Date: Mon, 28 Mar 2022 18:24:31 +0200 Subject: [PATCH] 02-client refactor: Adding VerifyClientMessage helper fn (#1119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Adding VerifyClientMessage helper fn to ClientState * refactor: creating verifyHeader priv fn and respective test * refactor: adding initial test cases * refactor: add more test cases * nit: move fns * remove clientState var * refactor: adding different val set test case * refactor: add test case for header with next height and diff validator set * refactor: adding remaining test cases * chore: uncomment previous tests: * fix: chainA -> chainB * chore: comment * refactor: remove consState from api + fix tests * refactor: add verifyHeader to clientState * fix: incorret trusted validators for concensus state test * Update modules/light-clients/07-tendermint/types/update_test.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * chore: add comment * fix: params * refactor: remove timestamp from api * refactor: switch and type * fix: remove height+1 * 02-client refactor: add tests for verifyMisbehaviour (#1166) * refactor: move misbehaviour validation into verifyMisbehaviour function * begin writing misbehaviour tests * fix misbehaviour test * continue adding misbehaviour test cases * add more test cases to verifyMisbehaviour test * add changing validator set tests * finish rest of tests except revision height testing * Update modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go Co-authored-by: Damian Nolan * add back misbehaviour type assertion Co-authored-by: Damian Nolan Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Damian Nolan --- .../07-tendermint/misbehaviour_handle.go | 62 ++- .../types/misbehaviour_handle_test.go | 370 +++++++++++++++ .../07-tendermint/types/update.go | 55 ++- .../07-tendermint/types/update_test.go | 439 ++++++++++-------- 4 files changed, 678 insertions(+), 248 deletions(-) diff --git a/modules/light-clients/07-tendermint/misbehaviour_handle.go b/modules/light-clients/07-tendermint/misbehaviour_handle.go index 98878fbbbe8..932829aa56b 100644 --- a/modules/light-clients/07-tendermint/misbehaviour_handle.go +++ b/modules/light-clients/07-tendermint/misbehaviour_handle.go @@ -32,47 +32,62 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", misbehaviour, &Misbehaviour{}) } - // The status of the client is checked in 02-client + if err := cs.VerifyClientMessage(ctx, clientStore, cdc, tmMisbehaviour); err != nil { + return nil, err + } + + cs.FrozenHeight = FrozenHeight + + return &cs, nil +} + +// verifyMisbehaviour determines whether or not two conflicting +// headers at the same height would have convinced the light client. +// +// NOTE: consensusState1 is the trusted consensus state that corresponds to the TrustedHeight +// of misbehaviour.Header1 +// Similarly, consensusState2 is the trusted consensus state that corresponds +// to misbehaviour.Header2 +// Misbehaviour sets frozen height to {0, 1} since it is only used as a boolean value (zero or non-zero). +func (cs *ClientState) verifyMisbehaviour(ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, misbehaviour *Misbehaviour) error { // if heights are equal check that this is valid misbehaviour of a fork // otherwise if heights are unequal check that this is valid misbehavior of BFT time violation - if tmMisbehaviour.Header1.GetHeight().EQ(tmMisbehaviour.Header2.GetHeight()) { - blockID1, err := tmtypes.BlockIDFromProto(&tmMisbehaviour.Header1.SignedHeader.Commit.BlockID) + if misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { + blockID1, err := tmtypes.BlockIDFromProto(&misbehaviour.Header1.SignedHeader.Commit.BlockID) if err != nil { - return nil, sdkerrors.Wrap(err, "invalid block ID from header 1 in misbehaviour") + return sdkerrors.Wrap(err, "invalid block ID from header 1 in misbehaviour") } - blockID2, err := tmtypes.BlockIDFromProto(&tmMisbehaviour.Header2.SignedHeader.Commit.BlockID) + + blockID2, err := tmtypes.BlockIDFromProto(&misbehaviour.Header2.SignedHeader.Commit.BlockID) if err != nil { - return nil, sdkerrors.Wrap(err, "invalid block ID from header 2 in misbehaviour") + return sdkerrors.Wrap(err, "invalid block ID from header 2 in misbehaviour") } // Ensure that Commit Hashes are different if bytes.Equal(blockID1.Hash, blockID2.Hash) { - return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers block hashes are equal") + return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers block hashes are equal") } + } else { // Header1 is at greater height than Header2, therefore Header1 time must be less than or equal to // Header2 time in order to be valid misbehaviour (violation of monotonic time). - if tmMisbehaviour.Header1.SignedHeader.Header.Time.After(tmMisbehaviour.Header2.SignedHeader.Header.Time) { - return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers are not at same height and are monotonically increasing") + if misbehaviour.Header1.SignedHeader.Header.Time.After(misbehaviour.Header2.SignedHeader.Header.Time) { + return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers are not at same height and are monotonically increasing") } } // Regardless of the type of misbehaviour, ensure that both headers are valid and would have been accepted by light-client // Retrieve trusted consensus states for each Header in misbehaviour - // and unmarshal from clientStore - - // Get consensus bytes from clientStore - tmConsensusState1, err := GetConsensusState(clientStore, cdc, tmMisbehaviour.Header1.TrustedHeight) + tmConsensusState1, err := GetConsensusState(clientStore, cdc, misbehaviour.Header1.TrustedHeight) if err != nil { - return nil, sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s", tmMisbehaviour.Header1) + return sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s", misbehaviour.Header1.TrustedHeight) } - // Get consensus bytes from clientStore - tmConsensusState2, err := GetConsensusState(clientStore, cdc, tmMisbehaviour.Header2.TrustedHeight) + tmConsensusState2, err := GetConsensusState(clientStore, cdc, misbehaviour.Header2.TrustedHeight) if err != nil { - return nil, sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s", tmMisbehaviour.Header2) + return sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s", misbehaviour.Header2.TrustedHeight) } // Check the validity of the two conflicting headers against their respective @@ -81,19 +96,17 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( // misbehaviour.ValidateBasic by the client keeper and msg.ValidateBasic // by the base application. if err := checkMisbehaviourHeader( - &cs, tmConsensusState1, tmMisbehaviour.Header1, ctx.BlockTime(), + cs, tmConsensusState1, misbehaviour.Header1, ctx.BlockTime(), ); err != nil { - return nil, sdkerrors.Wrap(err, "verifying Header1 in Misbehaviour failed") + return sdkerrors.Wrap(err, "verifying Header1 in Misbehaviour failed") } if err := checkMisbehaviourHeader( - &cs, tmConsensusState2, tmMisbehaviour.Header2, ctx.BlockTime(), + cs, tmConsensusState2, misbehaviour.Header2, ctx.BlockTime(), ); err != nil { - return nil, sdkerrors.Wrap(err, "verifying Header2 in Misbehaviour failed") + return sdkerrors.Wrap(err, "verifying Header2 in Misbehaviour failed") } - cs.FrozenHeight = FrozenHeight - - return &cs, nil + return nil } // checkMisbehaviourHeader checks that a Header in Misbehaviour is valid misbehaviour given @@ -101,7 +114,6 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( func checkMisbehaviourHeader( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - tmTrustedValset, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) if err != nil { return sdkerrors.Wrap(err, "trusted validator set is not tendermint validator set type") diff --git a/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go b/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go index ef92046b9a8..4657bae4d34 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go @@ -10,7 +10,9 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" + smtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/06-solomachine/types" "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" ) @@ -423,3 +425,371 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }) } } + +func (suite *TendermintTestSuite) TestVerifyMisbehaviour() { + // Setup different validators and signers for testing different types of updates + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + // create modified heights to use for test-cases + altVal := tmtypes.NewValidator(altPubKey, 100) + + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + altSigners := getAltSigners(altVal, altPrivVal) + + var ( + path *ibctesting.Path + misbehaviour exported.ClientMessage + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid fork misbehaviour", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + true, + }, + { + "valid time misbehaviour", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+3, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + true, + }, + { + "valid time misbehaviour, header 1 time stricly less than header 2 time", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+3, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Hour), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + true, + }, + { + "valid misbehavior at height greater than last consensusState", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, true, + }, + { + "valid misbehaviour with different trusted heights", func() { + trustedHeight1 := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals1, found := suite.chainB.GetValsAtHeight(int64(trustedHeight1.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + trustedHeight2 := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals2, found := suite.chainB.GetValsAtHeight(int64(trustedHeight2.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight1, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals1, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight2, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals2, suite.chainB.Signers), + } + + }, + true, + }, + /* + { + + "valid misbehaviour at a previous revision", + types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothValSet, bothSigners), + Header2: suite.chainA.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour at a future revision", + types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothValSet, bothSigners), + Header2: suite.chainA.CreateTMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour with trusted heights at a previous revision", + types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothValSet, bothSigners), + Header2: suite.chainA.CreateTMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + */ + { + "consensus state's valset hash different from misbehaviour should still pass", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altValSet.Proposer)) + bothSigners := suite.chainB.Signers + bothSigners[altValSet.Proposer.Address.String()] = altPrivVal + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + } + }, true, + }, + { + "invalid fork misbehaviour: identical headers", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviourHeader := suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + misbehaviour = &types.Misbehaviour{ + Header1: misbehaviourHeader, + Header2: misbehaviourHeader, + } + }, false, + }, + { + "invalid time misbehaviour: monotonically increasing time", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+3, trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, false, + }, + { + "invalid misbehaviour: misbehaviour from different chain", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + + }, false, + }, + { + "misbehaviour trusted validators does not match validator hash in trusted consensus state", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + } + }, false, + }, + { + "trusted consensus state does not exist", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight.Increment().(clienttypes.Height), suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, false, + }, + { + "invalid tendermint misbehaviour", func() { + misbehaviour = &smtypes.Misbehaviour{} + }, false, + }, + { + "trusting period expired", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + suite.chainA.ExpireClient(path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, false, + }, + { + "header 1 valset has too much change", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, false, + }, + { + "header 2 valset has too much change", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, false, + }, + { + "both header 1 and header 2 valsets have too much change", func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + misbehaviour = &types.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + clientState := path.EndpointA.GetClientState() + + // TODO: remove casting when `VerifyClientMessage` is apart of ClientState interface + tmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + err = tmClientState.VerifyClientMessage(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec(), misbehaviour) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/types/update.go b/modules/light-clients/07-tendermint/types/update.go index 18b4a2daa50..0862447a4b3 100644 --- a/modules/light-clients/07-tendermint/types/update.go +++ b/modules/light-clients/07-tendermint/types/update.go @@ -3,7 +3,6 @@ package types import ( "bytes" "reflect" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -65,15 +64,7 @@ func (cs ClientState) CheckHeaderAndUpdateState( conflictingHeader = true } - // get consensus state from clientStore - trustedConsState, err := GetConsensusState(clientStore, cdc, tmHeader.TrustedHeight) - if err != nil { - return nil, nil, sdkerrors.Wrapf( - err, "could not get consensus state from clientstore at TrustedHeight: %s", tmHeader.TrustedHeight, - ) - } - - if err := checkValidity(&cs, trustedConsState, tmHeader, ctx.BlockTime()); err != nil { + if err := cs.VerifyClientMessage(ctx, clientStore, cdc, tmHeader); err != nil { return nil, nil, err } @@ -126,12 +117,41 @@ func checkTrustedHeader(header *Header, consState *ConsensusState) error { return nil } -// checkValidity checks if the Tendermint header is valid. -// CONTRACT: consState.Height == header.TrustedHeight -func checkValidity( - clientState *ClientState, consState *ConsensusState, - header *Header, currentTimestamp time.Time, +// VerifyClientMessage checks if the clientMessage is of type Header or Misbehaviour and verifies the message +func (cs *ClientState) VerifyClientMessage( + ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, + clientMsg exported.ClientMessage, ) error { + switch msg := clientMsg.(type) { + case *Header: + return cs.verifyHeader(ctx, clientStore, cdc, msg) + case *Misbehaviour: + return cs.verifyMisbehaviour(ctx, clientStore, cdc, msg) + default: + return clienttypes.ErrInvalidClientType + } +} + +// verifyHeader returns an error if: +// - the client or header provided are not parseable to tendermint types +// - the header is invalid +// - header height is less than or equal to the trusted header height +// - header revision is not equal to trusted header revision +// - header valset commit verification fails +// - header timestamp is past the trusting period in relation to the consensus state +// - header timestamp is less than or equal to the consensus state timestamp +func (cs *ClientState) verifyHeader( + ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, + header *Header, +) error { + currentTimestamp := ctx.BlockTime() + + // Retrieve trusted consensus states for each Header in misbehaviour + consState, err := GetConsensusState(clientStore, cdc, header.TrustedHeight) + if err != nil { + return sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header at TrustedHeight: %s", header.TrustedHeight) + } + if err := checkTrustedHeader(header, consState); err != nil { return err } @@ -169,7 +189,7 @@ func checkValidity( ) } - chainID := clientState.GetChainID() + chainID := cs.GetChainID() // If chainID is in revision format, then set revision number of chainID with the revision number // of the header we are verifying // This is useful if the update is at a previous revision rather than an update to the latest revision @@ -200,11 +220,12 @@ func checkValidity( err = light.Verify( &signedHeader, tmTrustedValidators, tmSignedHeader, tmValidatorSet, - clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, clientState.TrustLevel.ToTendermint(), + cs.TrustingPeriod, currentTimestamp, cs.MaxClockDrift, cs.TrustLevel.ToTendermint(), ) if err != nil { return sdkerrors.Wrap(err, "failed to verify header") } + return nil } diff --git a/modules/light-clients/07-tendermint/types/update_test.go b/modules/light-clients/07-tendermint/types/update_test.go index 7216f48d33f..a6ae04c0897 100644 --- a/modules/light-clients/07-tendermint/types/update_test.go +++ b/modules/light-clients/07-tendermint/types/update_test.go @@ -4,15 +4,15 @@ import ( "fmt" "time" - tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" "github.com/cosmos/ibc-go/v3/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" types "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" + tmtypes "github.com/tendermint/tendermint/types" ) func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { @@ -35,14 +35,13 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { // create modified heights to use for test-cases heightPlus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+1) - heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) + // heightPlus5 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+5) + // heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) - heightPlus5 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+5) - altVal := tmtypes.NewValidator(altPubKey, revisionHeight) // Create alternative validator set with only altVal, invalid update (too much change in valSet) - altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - altSigners := getAltSigners(altVal, altPrivVal) + // altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + // altSigners := getAltSigners(altVal, altPrivVal) testCases := []struct { name string @@ -50,51 +49,6 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen bool expPass bool }{ - { - name: "successful update with next height and same validator set", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: true, - }, - { - name: "successful update with future height and different validator set", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, suite.valSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: true, - }, - { - name: "successful update with next height and different validator set", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothValSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: true, - }, - { - name: "successful update for a previous height", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - consStateHeight = heightMinus3 - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, bothValSet, suite.valSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: true, - }, { name: "successful update for a previous revision", setup: func(suite *TendermintTestSuite) { @@ -120,69 +74,6 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen: false, expPass: true, }, - { - name: "misbehaviour detection: header conflicts with existing consensus state", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - ctx := suite.chainA.GetContext().WithBlockTime(currentTime) - // Change the consensus state of header and store in client store to create a conflict - conflictConsState := newHeader.ConsensusState() - conflictConsState.Root = commitmenttypes.NewMerkleRoot([]byte("conflicting apphash")) - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus1, conflictConsState) - }, - expFrozen: true, - expPass: true, - }, - { - name: "misbehaviour detection: previous consensus state time is not before header time. time monotonicity violation", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - // create an intermediate consensus state with the same time as the newHeader to create a time violation. - // header time is after client time - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - prevConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - ctx := suite.chainA.GetContext().WithBlockTime(currentTime) - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus1, prevConsensusState) - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID) - types.SetIterationKey(clientStore, heightPlus1) - }, - expFrozen: true, - expPass: true, - }, - { - name: "misbehaviour detection: next consensus state time is not after header time. time monotonicity violation", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - // create the next consensus state with the same time as the intermediate newHeader to create a time violation. - // header time is after clientTime - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - nextConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - ctx := suite.chainA.GetContext().WithBlockTime(currentTime) - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus5, nextConsensusState) - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID) - types.SetIterationKey(clientStore, heightPlus5) - }, - expFrozen: true, - expPass: true, - }, - { - name: "unsuccessful update with incorrect header chain-id", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, { name: "unsuccessful update to a future revision", setup: func(suite *TendermintTestSuite) { @@ -204,50 +95,6 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen: false, expPass: false, }, - { - name: "unsuccessful update with next height: update header mismatches nextValSetHash", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, suite.valSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful update with next height: update header mismatches different nextValSetHash", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, bothValSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful update with future height: too much change in validator set", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, altValSet, altValSet, suite.valSet, altSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothValSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, { name: "unsuccessful update: trusting period has passed since last client timestamp", setup: func(suite *TendermintTestSuite) { @@ -260,53 +107,6 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen: false, expPass: false, }, - { - name: "unsuccessful update: header timestamp is past current timestamp", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful update: header timestamp is not past last client timestamp", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "header basic validation failed", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - // cause new header to fail validatebasic by changing commit height to mismatch header height - newHeader.SignedHeader.Commit.Height = revisionHeight - 1 - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "header height < consensus height", - setup: func(suite *TendermintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - // Make new header at height less than latest client state - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, suite.valSet, suite.signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, } for i, tc := range testCases { @@ -368,6 +168,233 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { } } +func (suite *TendermintTestSuite) TestVerifyHeader() { + var ( + path *ibctesting.Path + header *ibctmtypes.Header + ) + + // Setup different validators and signers for testing different types of updates + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + revisionHeight := int64(height.RevisionHeight) + + // create modified heights to use for test-cases + altVal := tmtypes.NewValidator(altPubKey, 100) + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + altSigners := getAltSigners(altVal, altPrivVal) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + name: "success", + malleate: func() {}, + expPass: true, + }, + { + name: "successful verify header for header with a previous height", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + // passing the CurrentHeader.Height as the block height as it will become a previous height once we commit N blocks + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + // commit some blocks so that the created Header now has a previous height as the BlockHeight + suite.coordinator.CommitNBlocks(suite.chainB, 5) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, + expPass: true, + }, + { + name: "successful verify header: header with future height and different validator set", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+5, trustedHeight, suite.chainB.CurrentHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners) + }, + expPass: true, + }, + { + name: "successful verify header: header with next height and different validator set", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height, trustedHeight, suite.chainB.CurrentHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners) + }, + expPass: true, + }, + { + name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, bothValSet, bothValSet, bothValSet, bothSigners) + }, + expPass: false, + }, + { + name: "unsuccessful verify header with next height: update header mismatches nextValSetHash", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + // this will err as altValSet.Hash() != consState.NextValidatorsHash + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, altValSet, altValSet, trustedVals, altSigners) + }, + expPass: false, + }, + { + name: "unsuccessful update with future height: too much change in validator set", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, altValSet, altValSet, trustedVals, altSigners) + }, + expPass: false, + }, + { + name: "unsuccessful verify header: header height revision and trusted height revision mismatch", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + header = suite.chainB.CreateTMClientHeader(chainIDRevision1, 3, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expPass: false, + }, + { + name: "unsuccessful verify header: header height < consensus height", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight) + 1) + suite.Require().True(found) + + heightMinus1 := clienttypes.NewHeight(trustedHeight.RevisionNumber, trustedHeight.RevisionHeight-1) + + // Make new header at height less than latest client state + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(heightMinus1.RevisionHeight), trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expPass: false, + }, + { + name: "unsuccessful verify header: header basic validation failed", + malleate: func() { + // cause header to fail validatebasic by changing commit height to mismatch header height + header.SignedHeader.Commit.Height = revisionHeight - 1 + }, + expPass: false, + }, + { + name: "unsuccessful verify header: header timestamp is not past last client timestamp", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight)) + suite.Require().True(found) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time.Add(-time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expPass: false, + }, + { + name: "unsuccessful verify header: header with incorrect header chain-id", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight)) + suite.Require().True(found) + + header = suite.chainB.CreateTMClientHeader(chainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expPass: false, + }, + { + name: "unsuccessful update: trusting period has passed since last client timestamp", + malleate: func() { + trustedHeight := path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height) + + trustedVals, found := suite.chainB.GetValsAtHeight(int64(trustedHeight.RevisionHeight)) + suite.Require().True(found) + + header = suite.chainA.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.CurrentHeader.Height+1, trustedHeight, suite.chainB.CurrentHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + suite.chainB.ExpireClient(ibctesting.TrustingPeriod) + }, + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + header, err = path.EndpointA.Chain.ConstructUpdateTMClientHeader(path.EndpointA.Counterparty.Chain, path.EndpointA.ClientID) + suite.Require().NoError(err) + + clientState := path.EndpointA.GetClientState() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + tc.malleate() + + tmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + err = tmClientState.VerifyClientMessage(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec(), header) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + } +} + func (suite *TendermintTestSuite) TestUpdateState() { var ( path *ibctesting.Path