cosmos-sdk/x/ibc/02-client/keeper/client_test.go

342 lines
12 KiB
Go

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)
}
})
}
}