package keeper_test import ( "bytes" "fmt" "time" lite "github.com/tendermint/tendermint/lite2" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" ) const ( invalidClientType exported.ClientType = 0 ) func (suite *KeeperTestSuite) TestCreateClient() { suite.keeper.SetClientType(suite.ctx, testClientID2, exported.Tendermint) cases := []struct { msg string clientID string expPass bool expPanic bool }{ {"success", testClientID, true, false}, {"client ID exists", testClientID, false, false}, {"client type exists", testClientID2, false, true}, } for i, tc := range cases { tc := tc i := i if tc.expPanic { suite.Require().Panics(func() { clientState, err := ibctmtypes.Initialize(tc.clientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) suite.Require().NoError(err, "err on client state initialization") suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) }, "Msg %d didn't panic: %s", i, tc.msg) } else { clientState, err := ibctmtypes.Initialize(tc.clientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if tc.expPass { suite.Require().NoError(err, "errored on initialization") suite.Require().NotNil(clientState, "valid test case %d failed: %s", i, tc.msg) } // If we were able to initialize clientstate successfully, try persisting it to state if err == nil { _, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) } if tc.expPass { suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg) } else { suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.msg) } } } } func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // Must create header creation functions since suite.header gets recreated on each test case createValidUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header { return ibctmtypes.CreateTestHeader(testClientID, suite.header.Height+1, suite.header.Time.Add(time.Minute), suite.valSet, []tmtypes.PrivValidator{suite.privVal}) } createInvalidUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header { return ibctmtypes.CreateTestHeader(testClientID, suite.header.Height-3, suite.header.Time.Add(time.Minute), suite.valSet, []tmtypes.PrivValidator{suite.privVal}) } var updateHeader ibctmtypes.Header cases := []struct { name string malleate func() error expPass bool }{ {"valid update", func() error { clientState, err := ibctmtypes.Initialize(testClientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) updateHeader = createValidUpdateFn(suite) return err }, true}, {"client type not found", func() error { updateHeader = createValidUpdateFn(suite) return nil }, false}, {"client type and header type mismatch", func() error { suite.keeper.SetClientType(suite.ctx, testClientID, invalidClientType) updateHeader = createValidUpdateFn(suite) return nil }, false}, {"client state not found", func() error { suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) updateHeader = createValidUpdateFn(suite) return nil }, false}, {"frozen client", func() error { clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header} suite.keeper.SetClientState(suite.ctx, clientState) suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) updateHeader = createValidUpdateFn(suite) return nil }, false}, {"invalid header", func() error { clientState, err := ibctmtypes.Initialize(testClientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) if err != nil { return err } updateHeader = createInvalidUpdateFn(suite) return nil }, false}, } for i, tc := range cases { tc := tc i := i suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.SetupTest() err := tc.malleate() suite.Require().NoError(err) suite.ctx = suite.ctx.WithBlockTime(updateHeader.Time.Add(time.Minute)) updatedClientState, err := suite.keeper.UpdateClient(suite.ctx, testClientID, updateHeader) if tc.expPass { suite.Require().NoError(err, err) expConsensusState := ibctmtypes.ConsensusState{ Height: updateHeader.GetHeight(), Timestamp: updateHeader.Time, Root: commitmenttypes.NewMerkleRoot(updateHeader.AppHash), ValidatorSet: updateHeader.ValidatorSet, } clientState, found := suite.keeper.GetClientState(suite.ctx, testClientID) suite.Require().True(found, "valid test case %d failed: %s", i, tc.name) consensusState, found := suite.keeper.GetClientConsensusState(suite.ctx, testClientID, updateHeader.GetHeight()) suite.Require().True(found, "valid test case %d failed: %s", i, tc.name) tmConsState, ok := consensusState.(ibctmtypes.ConsensusState) suite.Require().True(ok, "consensus state is not a tendermint consensus state") // recalculate cached totalVotingPower field for equality check tmConsState.ValidatorSet.TotalVotingPower() tmClientState, ok := updatedClientState.(ibctmtypes.ClientState) suite.Require().True(ok, "client state is not a tendermint client state") // recalculate cached totalVotingPower field for equality check tmClientState.LastHeader.ValidatorSet.TotalVotingPower() suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) suite.Require().Equal(updateHeader.GetHeight(), clientState.GetLatestHeight(), "client state height not updated correctly on case %s", tc.name) suite.Require().Equal(expConsensusState, consensusState, "consensus state should have been updated on case %s", tc.name) suite.Require().Equal(updatedClientState, tmClientState, "client states don't match") } else { suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) } }) } } func (suite *KeeperTestSuite) TestUpdateClientLocalhost() { var localhostClient exported.ClientState = localhosttypes.NewClientState(suite.header.ChainID, suite.ctx.BlockHeight()) suite.ctx = suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 1) updatedClientState, err := suite.keeper.UpdateClient(suite.ctx, exported.ClientTypeLocalHost, nil) suite.Require().NoError(err, err) suite.Require().Equal(localhostClient.GetID(), updatedClientState.GetID()) suite.Require().Equal(localhostClient.GetLatestHeight()+1, updatedClientState.GetLatestHeight()) } func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { altPrivVal := tmtypes.NewMockPV() altPubKey, err := altPrivVal.GetPubKey() suite.Require().NoError(err) altVal := tmtypes.NewValidator(altPubKey, 4) // Create bothValSet with both suite validator and altVal bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) // Create alternative validator set with only altVal altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) pubKey, err := suite.privVal.GetPubKey() suite.Require().NoError(err) // Create signer array and ensure it is in same order as bothValSet var bothSigners []tmtypes.PrivValidator if bytes.Compare(altPubKey.Address(), pubKey.Address()) == -1 { bothSigners = []tmtypes.PrivValidator{altPrivVal, suite.privVal} } else { bothSigners = []tmtypes.PrivValidator{suite.privVal, altPrivVal} } altSigners := []tmtypes.PrivValidator{altPrivVal} testCases := []struct { name string evidence ibctmtypes.Evidence malleate func() error expPass bool }{ { "trusting period misbehavior should pass", ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), Header2: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), ChainID: testClientID, ClientID: testClientID, }, func() error { suite.consensusState.ValidatorSet = bothValSet clientState, err := ibctmtypes.Initialize(testClientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) return err }, true, }, { "misbehavior at later height should pass", ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(testClientID, testClientHeight+5, suite.ctx.BlockTime(), bothValSet, bothSigners), Header2: ibctmtypes.CreateTestHeader(testClientID, testClientHeight+5, suite.ctx.BlockTime(), bothValSet, bothSigners), ChainID: testClientID, ClientID: testClientID, }, func() error { suite.consensusState.ValidatorSet = bothValSet clientState, err := ibctmtypes.Initialize(testClientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, err = suite.keeper.CreateClient(suite.ctx, clientState, suite.consensusState) return err }, true, }, { "client state not found", ibctmtypes.Evidence{}, func() error { return nil }, false, }, { "consensus state not found", ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), Header2: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), ChainID: testClientID, ClientID: testClientID, }, func() error { clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header} suite.keeper.SetClientState(suite.ctx, clientState) return nil }, false, }, { "consensus state not found", ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), Header2: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), ChainID: testClientID, ClientID: testClientID, }, func() error { clientState := ibctmtypes.ClientState{FrozenHeight: 1, ID: testClientID, LastHeader: suite.header} suite.keeper.SetClientState(suite.ctx, clientState) return nil }, false, }, { "misbehaviour check failed", ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), bothValSet, bothSigners), Header2: ibctmtypes.CreateTestHeader(testClientID, testClientHeight, suite.ctx.BlockTime(), altValSet, altSigners), ChainID: testClientID, ClientID: testClientID, }, func() error { clientState, err := ibctmtypes.Initialize(testClientID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, 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 err := tc.malleate() suite.Require().NoError(err) err = suite.keeper.CheckMisbehaviourAndUpdateState(suite.ctx, tc.evidence) if tc.expPass { suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) clientState, found := suite.keeper.GetClientState(suite.ctx, testClientID) suite.Require().True(found, "valid test case %d failed: %s", i, tc.name) suite.Require().True(clientState.IsFrozen(), "valid test case %d failed: %s", i, tc.name) } else { suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) } }) } }