package keeper_test import ( "math/rand" "testing" "time" "github.com/stretchr/testify/suite" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types" localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/09-localhost/types" ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ibctestingmock "github.com/cosmos/cosmos-sdk/x/ibc/testing/mock" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ( testChainID = "gaiahub-0" testChainIDEpoch1 = "gaiahub-1" testClientID = "gaiachain" testClientID2 = "ethbridge" testClientID3 = "ethermint" height = 5 trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 maxClockDrift time.Duration = time.Second * 10 ) var ( testClientHeight = types.NewHeight(0, 5) testClientHeightEpoch1 = types.NewHeight(1, 5) upgradeHeight = types.NewHeight(1, 1) ) type KeeperTestSuite struct { suite.Suite coordinator *ibctesting.Coordinator chainA *ibctesting.TestChain chainB *ibctesting.TestChain cdc codec.Marshaler ctx sdk.Context keeper *keeper.Keeper consensusState *ibctmtypes.ConsensusState header *ibctmtypes.Header valSet *tmtypes.ValidatorSet valSetHash tmbytes.HexBytes privVal tmtypes.PrivValidator now time.Time past time.Time queryClient types.QueryClient } func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) isCheckTx := false suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) suite.past = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) now2 := suite.now.Add(time.Hour) app := simapp.Setup(isCheckTx) suite.cdc = app.AppCodec() suite.ctx = app.BaseApp.NewContext(isCheckTx, tmproto.Header{Height: height, ChainID: testClientID, Time: now2}) suite.keeper = &app.IBCKeeper.ClientKeeper suite.privVal = ibctestingmock.NewPV() pubKey, err := suite.privVal.GetPubKey() suite.Require().NoError(err) testClientHeightMinus1 := types.NewHeight(0, height-1) validator := tmtypes.NewValidator(pubKey.(cryptotypes.IntoTmPubKey).AsTmPubKey(), 1) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) suite.valSetHash = suite.valSet.Hash() suite.header = ibctmtypes.CreateTestHeader(testChainID, testClientHeight, testClientHeightMinus1, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.consensusState = ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.valSetHash) var validators stakingtypes.Validators for i := 1; i < 11; i++ { privVal := ibctestingmock.NewPV() pk, err := privVal.GetPubKey() suite.Require().NoError(err) val := stakingtypes.NewValidator(sdk.ValAddress(pk.Address()), pk, stakingtypes.Description{}) val.Status = sdk.Bonded val.Tokens = sdk.NewInt(rand.Int63()) validators = append(validators, val) app.StakingKeeper.SetHistoricalInfo(suite.ctx, int64(i), stakingtypes.NewHistoricalInfo(suite.ctx.BlockHeader(), validators)) } queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, app.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, app.IBCKeeper.ClientKeeper) suite.queryClient = types.NewQueryClient(queryHelper) } func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } func (suite *KeeperTestSuite) TestSetClientState() { clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) retrievedState, found := suite.keeper.GetClientState(suite.ctx, testClientID) suite.Require().True(found, "GetClientState failed") suite.Require().Equal(clientState, retrievedState, "Client states are not equal") } func (suite *KeeperTestSuite) TestSetClientConsensusState() { suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState) retrievedConsState, found := suite.keeper.GetClientConsensusState(suite.ctx, testClientID, testClientHeight) suite.Require().True(found, "GetConsensusState failed") tmConsState, ok := retrievedConsState.(*ibctmtypes.ConsensusState) suite.Require().True(ok) suite.Require().Equal(suite.consensusState, tmConsState, "ConsensusState not stored correctly") } func (suite *KeeperTestSuite) TestValidateSelfClient() { badUpgradePath := commitmenttypes.NewMerklePath([]string{"bad", "upgrade", "path"}) testCases := []struct { name string clientState exported.ClientState expPass bool }{ { "success", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), true, }, { "success with nil UpgradePath", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), nil, false, false), true, }, { "invalid client type", localhosttypes.NewClientState(testChainID, testClientHeight), false, }, { "frozen client", &ibctmtypes.ClientState{testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false}, false, }, { "incorrect chainID", ibctmtypes.NewClientState("gaiatestnet", ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid client height", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, testClientHeight.EpochHeight+10), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid client epoch", ibctmtypes.NewClientState(testChainIDEpoch1, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeightEpoch1, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid proof specs", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, nil, &ibctesting.UpgradePath, false, false), false, }, { "invalid trust level", ibctmtypes.NewClientState(testChainID, ibctmtypes.Fraction{0, 1}, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid unbonding period", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod+10, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid trusting period", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, ubdPeriod+10, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), false, }, { "invalid upgrade path", ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &badUpgradePath, false, false), false, }, } ctx := suite.ctx.WithChainID(testChainID) ctx = ctx.WithBlockHeight(height) for _, tc := range testCases { err := suite.keeper.ValidateSelfClient(ctx, tc.clientState) if tc.expPass { suite.Require().NoError(err, "expected valid client for case: %s", tc.name) } else { suite.Require().Error(err, "expected invalid client for case: %s", tc.name) } } } func (suite KeeperTestSuite) TestGetAllClients() { clientIDs := []string{ testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), } for i := range expClients { suite.keeper.SetClientState(suite.ctx, clientIDs[i], expClients[i]) } // add localhost client localHostClient, found := suite.keeper.GetClientState(suite.ctx, exported.Localhost) suite.Require().True(found) expClients = append(expClients, localHostClient) clients := suite.keeper.GetAllClients(suite.ctx) suite.Require().Len(clients, len(expClients)) suite.Require().Equal(expClients, clients) } func (suite KeeperTestSuite) TestGetAllGenesisClients() { clientIDs := []string{ testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false), } expGenClients := make([]types.IdentifiedClientState, len(expClients)) for i := range expClients { suite.keeper.SetClientState(suite.ctx, clientIDs[i], expClients[i]) expGenClients[i] = types.NewIdentifiedClientState(clientIDs[i], expClients[i]) } // add localhost client localHostClient, found := suite.keeper.GetClientState(suite.ctx, exported.Localhost) suite.Require().True(found) expGenClients = append(expGenClients, types.NewIdentifiedClientState(exported.Localhost, localHostClient)) genClients := suite.keeper.GetAllGenesisClients(suite.ctx) suite.Require().Equal(expGenClients, genClients) } func (suite KeeperTestSuite) TestGetConsensusState() { suite.ctx = suite.ctx.WithBlockHeight(10) cases := []struct { name string height types.Height expPass bool }{ {"zero height", types.ZeroHeight(), false}, {"height > latest height", types.NewHeight(0, uint64(suite.ctx.BlockHeight())+1), false}, {"latest height - 1", types.NewHeight(0, uint64(suite.ctx.BlockHeight())-1), true}, {"latest height", types.GetSelfHeight(suite.ctx), true}, } for i, tc := range cases { tc := tc cs, found := suite.keeper.GetSelfConsensusState(suite.ctx, tc.height) if tc.expPass { suite.Require().True(found, "Case %d should have passed: %s", i, tc.name) suite.Require().NotNil(cs, "Case %d should have passed: %s", i, tc.name) } else { suite.Require().False(found, "Case %d should have failed: %s", i, tc.name) suite.Require().Nil(cs, "Case %d should have failed: %s", i, tc.name) } } } func (suite KeeperTestSuite) TestConsensusStateHelpers() { // initial setup clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), &ibctesting.UpgradePath, false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState) nextState := ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("next")), suite.valSetHash) testClientHeightPlus5 := types.NewHeight(0, height+5) header := ibctmtypes.CreateTestHeader(testClientID, testClientHeightPlus5, testClientHeight, suite.header.Header.Time.Add(time.Minute), suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) // mock update functionality clientState.LatestHeight = header.GetHeight().(types.Height) suite.keeper.SetClientConsensusState(suite.ctx, testClientID, header.GetHeight(), nextState) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) latest, ok := suite.keeper.GetLatestClientConsensusState(suite.ctx, testClientID) suite.Require().True(ok) suite.Require().Equal(nextState, latest, "Latest client not returned correctly") // Should return existing consensusState at latestClientHeight lte, ok := suite.keeper.GetClientConsensusStateLTE(suite.ctx, testClientID, types.NewHeight(0, height+3)) suite.Require().True(ok) suite.Require().Equal(suite.consensusState, lte, "LTE helper function did not return latest client state below height: %d", height+3) } // 2 clients in total are created on chainA. The first client is updated so it contains an initial consensus state // and a consensus state at the update height. func (suite KeeperTestSuite) TestGetAllConsensusStates() { clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint) clientState := suite.chainA.GetClientState(clientA) expConsensusHeight0 := clientState.GetLatestHeight() consensusState0, ok := suite.chainA.GetConsensusState(clientA, expConsensusHeight0) suite.Require().True(ok) // update client to create a second consensus state err := suite.coordinator.UpdateClient(suite.chainA, suite.chainB, clientA, ibctesting.Tendermint) suite.Require().NoError(err) clientState = suite.chainA.GetClientState(clientA) expConsensusHeight1 := clientState.GetLatestHeight() suite.Require().True(expConsensusHeight1.GT(expConsensusHeight0)) consensusState1, ok := suite.chainA.GetConsensusState(clientA, expConsensusHeight1) suite.Require().True(ok) expConsensus := []exported.ConsensusState{ consensusState0, consensusState1, } // create second client on chainA clientA2, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint) clientState = suite.chainA.GetClientState(clientA2) expConsensusHeight2 := clientState.GetLatestHeight() consensusState2, ok := suite.chainA.GetConsensusState(clientA2, expConsensusHeight2) suite.Require().True(ok) expConsensus2 := []exported.ConsensusState{consensusState2} expConsensusStates := types.ClientsConsensusStates{ types.NewClientConsensusStates(clientA, []types.ConsensusStateWithHeight{ types.NewConsensusStateWithHeight(expConsensusHeight0.(types.Height), expConsensus[0]), types.NewConsensusStateWithHeight(expConsensusHeight1.(types.Height), expConsensus[1]), }), types.NewClientConsensusStates(clientA2, []types.ConsensusStateWithHeight{ types.NewConsensusStateWithHeight(expConsensusHeight2.(types.Height), expConsensus2[0]), }), }.Sort() consStates := suite.chainA.App.IBCKeeper.ClientKeeper.GetAllConsensusStates(suite.chainA.GetContext()) suite.Require().Equal(expConsensusStates, consStates, "%s \n\n%s", expConsensusStates, consStates) }