diff --git a/x/ibc/02-client/keeper/client_test.go b/x/ibc/02-client/keeper/client_test.go new file mode 100644 index 000000000000..923a6e710981 --- /dev/null +++ b/x/ibc/02-client/keeper/client_test.go @@ -0,0 +1,113 @@ +package keeper_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + "github.com/stretchr/testify/require" + tmtypes "github.com/tendermint/tendermint/types" +) + +const ( + testClientType exported.ClientType = iota + 2 +) + +func (suite *KeeperTestSuite) TestCreateClient() { + // Test Valid CreateClient + state, err := suite.keeper.CreateClient(suite.ctx, testClientID, exported.Tendermint, suite.consensusState) + require.Nil(suite.T(), err, "CreateClient failed") + + // Test ClientState stored correctly + expectedState := types.State{ + ID: testClientID, + Frozen: false, + } + require.Equal(suite.T(), expectedState, state, "Incorrect ClientState returned") + + // Test ClientType and VerifiedRoot stored correctly + clientType, _ := suite.keeper.GetClientType(suite.ctx, testClientID) + require.Equal(suite.T(), exported.Tendermint, clientType, "Incorrect ClientType stored") + root, _ := suite.keeper.GetVerifiedRoot(suite.ctx, testClientID, suite.consensusState.GetHeight()) + require.Equal(suite.T(), suite.consensusState.GetRoot(), root, "Incorrect root stored") + + // Test that trying to CreateClient on existing client fails + _, err = suite.keeper.CreateClient(suite.ctx, testClientID, exported.Tendermint, suite.consensusState) + require.NotNil(suite.T(), err, "CreateClient on existing client: %s passed", testClientID) +} + +func (suite *KeeperTestSuite) TestUpdateClient() { + privVal := tmtypes.NewMockPV() + validator := tmtypes.NewValidator(privVal.GetPubKey(), 1) + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + altSigners := []tmtypes.PrivValidator{privVal} + + // Test invalid cases all fail and do not update state + cases := []struct { + name string + malleate func() + expErr bool + }{ + {"valid update", func() {}, false}, + {"wrong client type", func() { + suite.keeper.SetClientType(suite.ctx, testClientID, testClientType) + }, true}, + {"frozen client", func() { + clientState, _ := suite.keeper.GetClientState(suite.ctx, testClientID) + clientState.Frozen = true + suite.keeper.SetClientState(suite.ctx, clientState) + }, true}, + {"past height", func() { + suite.header = tendermint.MakeHeader(2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + }, true}, + {"validatorHash incorrect", func() { + suite.header = tendermint.MakeHeader(4, altValSet, suite.valSet, altSigners) + }, true}, + {"nextHash incorrect", func() { + suite.header.NextValidatorSet = altValSet + }, true}, + {"header fails validateBasic", func() { + suite.header.ChainID = "test" + }, true}, + {"verify future commit fails", func() { + suite.consensusState.NextValidatorSet = altValSet + suite.keeper.SetConsensusState(suite.ctx, testClientID, suite.consensusState) + }, true}, + } + + for _, tc := range cases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + // Reset suite on each subtest + suite.SetupTest() + + _, err := suite.keeper.CreateClient(suite.ctx, testClientID, exported.Tendermint, suite.consensusState) + require.Nil(suite.T(), err, "CreateClient failed") + + tc.malleate() + err = suite.keeper.UpdateClient(suite.ctx, testClientID, suite.header) + + retrievedConsState, _ := suite.keeper.GetConsensusState(suite.ctx, testClientID) + tmConsState, _ := retrievedConsState.(tendermint.ConsensusState) + tmConsState.NextValidatorSet.TotalVotingPower() + retrievedRoot, _ := suite.keeper.GetVerifiedRoot(suite.ctx, testClientID, suite.consensusState.GetHeight()+1) + if tc.expErr { + require.NotNil(suite.T(), err, "Invalid UpdateClient passed", tc.name) + + // require no state changes occurred + require.Equal(suite.T(), suite.consensusState, tmConsState, "Consensus state changed after invalid UpdateClient") + require.Nil(suite.T(), retrievedRoot, "Root added for new height after invalid UpdateClient") + } else { + require.Nil(suite.T(), err, "Valid UpdateClient failed", tc.name) + + // require state changes were performed correctly + require.Equal(suite.T(), suite.header.GetHeight(), retrievedConsState.GetHeight(), "height not updated correctly") + require.Equal(suite.T(), commitment.NewRoot(suite.header.AppHash), retrievedConsState.GetRoot(), "root not updated correctly") + require.Equal(suite.T(), suite.header.NextValidatorSet, tmConsState.NextValidatorSet, "NextValidatorSet not updated correctly") + + } + + }) + } +} diff --git a/x/ibc/02-client/keeper/keeper_test.go b/x/ibc/02-client/keeper/keeper_test.go new file mode 100644 index 000000000000..da81be0edb12 --- /dev/null +++ b/x/ibc/02-client/keeper/keeper_test.go @@ -0,0 +1,102 @@ +package keeper_test + +import ( + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const ( + testClientID = "gaia" +) + +type KeeperTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + keeper *keeper.Keeper + consensusState tendermint.ConsensusState + header tendermint.Header + valSet *tmtypes.ValidatorSet + privVal tmtypes.PrivValidator +} + +func (suite *KeeperTestSuite) SetupTest() { + isCheckTx := false + app := simapp.Setup(isCheckTx) + + suite.cdc = app.Codec() + suite.ctx = app.BaseApp.NewContext(isCheckTx, abci.Header{}) + suite.keeper = &app.IBCKeeper.ClientKeeper + + suite.privVal = tmtypes.NewMockPV() + + validator := tmtypes.NewValidator(suite.privVal.GetPubKey(), 1) + suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + + suite.header = tendermint.MakeHeader(4, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + + suite.consensusState = tendermint.ConsensusState{ + ChainID: testClientID, + Height: 3, + Root: commitment.NewRoot([]byte("hash")), + NextValidatorSet: suite.valSet, + } +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestSetClientState() { + clientState := types.NewClientState(testClientID) + suite.keeper.SetClientState(suite.ctx, clientState) + + retrievedState, ok := suite.keeper.GetClientState(suite.ctx, testClientID) + require.True(suite.T(), ok, "GetClientState failed") + require.Equal(suite.T(), clientState, retrievedState, "Client states are not equal") +} + +func (suite *KeeperTestSuite) TestSetClientType() { + suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) + clientType, ok := suite.keeper.GetClientType(suite.ctx, testClientID) + + require.True(suite.T(), ok, "GetClientType failed") + require.Equal(suite.T(), exported.Tendermint, clientType, "ClientTypes not stored correctly") +} + +func (suite *KeeperTestSuite) TestSetConsensusState() { + suite.keeper.SetConsensusState(suite.ctx, testClientID, suite.consensusState) + + retrievedConsState, ok := suite.keeper.GetConsensusState(suite.ctx, testClientID) + + require.True(suite.T(), ok, "GetConsensusState failed") + tmConsState, _ := retrievedConsState.(tendermint.ConsensusState) + // force recalculation of unexported totalVotingPower so we can compare consensusState + tmConsState.NextValidatorSet.TotalVotingPower() + require.Equal(suite.T(), suite.consensusState, tmConsState, "ConsensusState not stored correctly") +} + +func (suite *KeeperTestSuite) TestSetVerifiedRoot() { + root := commitment.NewRoot([]byte("hash")) + suite.keeper.SetVerifiedRoot(suite.ctx, testClientID, 3, root) + + retrievedRoot, ok := suite.keeper.GetVerifiedRoot(suite.ctx, testClientID, 3) + + require.True(suite.T(), ok, "GetVerifiedRoot failed") + require.Equal(suite.T(), root, retrievedRoot, "Root stored incorrectly") +} diff --git a/x/ibc/02-client/types/tendermint/consensus_state_test.go b/x/ibc/02-client/types/tendermint/consensus_state_test.go index 2f4129226228..07c1015a3067 100644 --- a/x/ibc/02-client/types/tendermint/consensus_state_test.go +++ b/x/ibc/02-client/types/tendermint/consensus_state_test.go @@ -27,7 +27,7 @@ func (suite *TendermintTestSuite) TestCheckValidity() { // reset and make header fail validatebasic suite.SetupTest() - suite.header.ChainID = "not_mychain" + suite.header.ChainID = "not_gaia" err = suite.cs.checkValidity(suite.header) require.NotNil(suite.T(), err, "invalid header should fail ValidateBasic") } @@ -44,7 +44,7 @@ func (suite *TendermintTestSuite) TestCheckUpdate() { // make header invalid so update should be unsuccessful suite.SetupTest() - suite.header.ChainID = "not_mychain" + suite.header.ChainID = "not_gaia" cs, err = suite.cs.CheckValidityAndUpdateState(suite.header) require.NotNil(suite.T(), err) diff --git a/x/ibc/02-client/types/tendermint/tendermint_test.go b/x/ibc/02-client/types/tendermint/tendermint_test.go index 11817f73e9ff..cb3e572d6289 100644 --- a/x/ibc/02-client/types/tendermint/tendermint_test.go +++ b/x/ibc/02-client/types/tendermint/tendermint_test.go @@ -1,15 +1,12 @@ package tendermint import ( - "math" "testing" - "time" "github.com/stretchr/testify/suite" "github.com/tendermint/tendermint/crypto/tmhash" tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tendermint/version" commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" ) @@ -27,49 +24,11 @@ func (suite *TendermintTestSuite) SetupTest() { privVal := tmtypes.NewMockPV() val := tmtypes.NewValidator(privVal.GetPubKey(), 10) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) - vsetHash := valSet.Hash() - timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) - tmHeader := tmtypes.Header{ - Version: version.Consensus{Block: 2, App: 2}, - ChainID: "mychain", - Height: 3, - Time: timestamp, - NumTxs: 100, - TotalTxs: 1000, - LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt64, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: vsetHash, - NextValidatorsHash: vsetHash, - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: privVal.GetPubKey().Address(), - } - hhash := tmHeader.Hash() - blockID := makeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) - voteSet := tmtypes.NewVoteSet("mychain", 3, 1, tmtypes.PrecommitType, valSet) - commit, err := tmtypes.MakeCommit(blockID, 3, 1, voteSet, []tmtypes.PrivValidator{privVal}) - if err != nil { - panic(err) - } - - signedHeader := tmtypes.SignedHeader{ - Header: &tmHeader, - Commit: commit, - } - - header := Header{ - SignedHeader: signedHeader, - ValidatorSet: valSet, - NextValidatorSet: valSet, - } - + suite.header = MakeHeader(3, valSet, valSet, []tmtypes.PrivValidator{privVal}) root := commitment.NewRoot(tmhash.Sum([]byte("my root"))) cs := ConsensusState{ - ChainID: "mychain", + ChainID: "gaia", Height: 3, Root: root, NextValidatorSet: valSet, @@ -78,7 +37,6 @@ func (suite *TendermintTestSuite) SetupTest() { // set fields in suite suite.privVal = privVal suite.valSet = valSet - suite.header = header suite.cs = cs } diff --git a/x/ibc/02-client/types/tendermint/test_utils.go b/x/ibc/02-client/types/tendermint/test_utils.go index a88bd13d3b1c..ac25a13fabbf 100644 --- a/x/ibc/02-client/types/tendermint/test_utils.go +++ b/x/ibc/02-client/types/tendermint/test_utils.go @@ -1,8 +1,12 @@ package tendermint import ( + "math" + "time" + "github.com/tendermint/tendermint/crypto/tmhash" tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" ) // Copied unimported test functions from tmtypes to use them here @@ -38,10 +42,52 @@ func randomDuplicatedVoteEvidence() *tmtypes.DuplicateVoteEvidence { val := tmtypes.NewMockPV() blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), 1000, tmhash.Sum([]byte("partshash"))) blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), 1000, tmhash.Sum([]byte("partshash"))) - const chainID = "mychain" + const chainID = "gaia" return &tmtypes.DuplicateVoteEvidence{ PubKey: val.GetPubKey(), VoteA: makeVote(val, chainID, 0, 10, 2, 1, blockID), VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2), } } + +func MakeHeader(height int64, valSet *tmtypes.ValidatorSet, nextValSet *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) Header { + vsetHash := valSet.Hash() + nextHash := nextValSet.Hash() + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + tmHeader := tmtypes.Header{ + Version: version.Consensus{Block: 2, App: 2}, + ChainID: "gaia", + Height: height, + Time: timestamp, + NumTxs: 100, + TotalTxs: 1000, + LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt64, make([]byte, tmhash.Size)), + LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: nextHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: tmhash.Sum([]byte("app_hash")), + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: signers[0].GetPubKey().Address(), + } + hhash := tmHeader.Hash() + blockID := makeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet("gaia", height, 1, tmtypes.PrecommitType, valSet) + commit, err := tmtypes.MakeCommit(blockID, height, 1, voteSet, signers) + if err != nil { + panic(err) + } + + signedHeader := tmtypes.SignedHeader{ + Header: &tmHeader, + Commit: commit, + } + + return Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + NextValidatorSet: nextValSet, + } +}