From 9b61e0947e58a0f489a7a7f84540977873d8e5e3 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 4 Aug 2020 05:05:03 -0400 Subject: [PATCH] Remove Last Header from ClientState (#6873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement features, fix tendermint tests * allow test headers to pass in custom nextvalidatorset * allow updates to previous heights and test * remove unnecessary testing feature * fix client tests * fix ibc tests, and updating consensus state * fix ibc-transfer tests * appease linter * Apply suggestions from code review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * add chainID test case * fix expPass value * Apply suggestions from code review Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * address rest of @colin-axner review * fix bug and errors * Apply suggestions from code review Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * address rest of @colin-axner review * implement updating before frozen height Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com> --- simapp/test_helpers.go | 7 +- x/ibc/02-client/exported/exported.go | 1 + x/ibc/02-client/keeper/client.go | 13 +- x/ibc/02-client/keeper/client_test.go | 129 +++++++++++++----- x/ibc/02-client/keeper/keeper.go | 9 +- x/ibc/02-client/keeper/keeper_test.go | 39 +++--- x/ibc/02-client/types/genesis_test.go | 23 ++-- x/ibc/07-tendermint/misbehaviour_test.go | 22 +-- x/ibc/07-tendermint/tendermint_test.go | 6 +- x/ibc/07-tendermint/types/client_state.go | 49 ++++--- .../07-tendermint/types/client_state_test.go | 87 ++++++------ x/ibc/07-tendermint/types/consensus_state.go | 24 ++-- x/ibc/07-tendermint/types/errors.go | 16 ++- x/ibc/07-tendermint/types/msgs.go | 9 +- x/ibc/07-tendermint/update.go | 86 ++++++++---- x/ibc/07-tendermint/update_test.go | 93 ++++++++++--- x/ibc/09-localhost/types/client_state.go | 5 + x/ibc/genesis_test.go | 6 +- x/ibc/ibc_test.go | 6 +- x/ibc/testing/chain.go | 4 +- 20 files changed, 419 insertions(+), 215 deletions(-) diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index a4ae4e32c..4901eb8cc 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -136,7 +136,12 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // commit genesis changes app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + app.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{ + Height: app.LastBlockHeight() + 1, + AppHash: app.LastCommitID().Hash, + ValidatorsHash: valSet.Hash(), + NextValidatorsHash: valSet.Hash(), + }}) return app } diff --git a/x/ibc/02-client/exported/exported.go b/x/ibc/02-client/exported/exported.go index aab2b61d0..dab670839 100644 --- a/x/ibc/02-client/exported/exported.go +++ b/x/ibc/02-client/exported/exported.go @@ -21,6 +21,7 @@ type ClientState interface { ClientType() ClientType GetLatestHeight() uint64 IsFrozen() bool + GetFrozenHeight() uint64 Validate() error GetProofSpecs() []*ics23.ProofSpec diff --git a/x/ibc/02-client/keeper/client.go b/x/ibc/02-client/keeper/client.go index eb40d30d5..96e51da3b 100644 --- a/x/ibc/02-client/keeper/client.go +++ b/x/ibc/02-client/keeper/client.go @@ -58,8 +58,8 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H return nil, sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) } - // addition to spec: prevent update if the client is frozen - if clientState.IsFrozen() { + // prevent update if the client is frozen before or at header height + if clientState.IsFrozen() && clientState.GetFrozenHeight() <= header.GetHeight() { return nil, sdkerrors.Wrapf(types.ErrClientFrozen, "cannot update client with ID %s", clientID) } @@ -71,9 +71,16 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H switch clientType { case exported.Tendermint: + trustedConsState, found := k.GetClientConsensusStateLTE(ctx, clientID, header.GetHeight()) + if !found { + return nil, sdkerrors.Wrapf(types.ErrConsensusStateNotFound, "could not find consensus state less than header height: %d to verify header against", header.GetHeight()) + } clientState, consensusState, err = tendermint.CheckValidityAndUpdateState( - clientState, header, ctx.BlockTime(), + clientState, trustedConsState, header, ctx.BlockTime(), ) + if err != nil { + err = sdkerrors.Wrapf(err, "failed to update client using trusted consensus state height %d", trustedConsState.GetHeight()) + } case exported.Localhost: // override client state and update the block height clientState = localhosttypes.NewClientState( diff --git a/x/ibc/02-client/keeper/client_test.go b/x/ibc/02-client/keeper/client_test.go index 157891039..2a81523ea 100644 --- a/x/ibc/02-client/keeper/client_test.go +++ b/x/ibc/02-client/keeper/client_test.go @@ -38,12 +38,12 @@ func (suite *KeeperTestSuite) TestCreateClient() { i := i if tc.expPanic { suite.Require().Panics(func() { - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) suite.Require().NoError(err, "err on client state initialization") suite.keeper.CreateClient(suite.ctx, tc.clientID, clientState, suite.consensusState) }, "Msg %d didn't panic: %s", i, tc.msg) } else { - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, 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) @@ -64,15 +64,19 @@ func (suite *KeeperTestSuite) TestCreateClient() { 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), + createFutureUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header { + return ibctmtypes.CreateTestHeader(testChainID, 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), + createPastUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header { + return ibctmtypes.CreateTestHeader(testChainID, suite.header.Height-3, suite.header.Time, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) } - var updateHeader ibctmtypes.Header + var ( + updateHeader ibctmtypes.Header + clientState ibctmtypes.ClientState + err error + ) cases := []struct { name string @@ -80,41 +84,95 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { expPass bool }{ {"valid update", func() error { - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err = ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) if err != nil { return err } _, err = suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) - updateHeader = createValidUpdateFn(suite) + updateHeader = createFutureUpdateFn(suite) return err }, true}, + {"valid past update", func() error { + clientState, err = ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + if err != nil { + return err + } + _, err = suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) + + // store previous consensus state + prevConsState := ibctmtypes.ConsensusState{ + Height: 1, + Timestamp: suite.past, + NextValidatorsHash: suite.valSet.Hash(), + ValidatorSet: suite.valSet, + } + suite.keeper.SetClientConsensusState(suite.ctx, testClientID, 1, prevConsState) + + // updateHeader will fill in consensus state between prevConsState and suite.consState + // clientState should not be updated + updateHeader = createPastUpdateFn(suite) + return nil + }, true}, {"client type not found", func() error { - updateHeader = createValidUpdateFn(suite) + updateHeader = createFutureUpdateFn(suite) return nil }, false}, {"client type and header type mismatch", func() error { suite.keeper.SetClientType(suite.ctx, testClientID, invalidClientType) - updateHeader = createValidUpdateFn(suite) + updateHeader = createFutureUpdateFn(suite) return nil }, false}, {"client state not found", func() error { suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) - updateHeader = createValidUpdateFn(suite) + updateHeader = createFutureUpdateFn(suite) return nil }, false}, - {"frozen client", func() error { - clientState := ibctmtypes.ClientState{FrozenHeight: 1, LastHeader: suite.header} + {"consensus state not found", func() error { + clientState, err = ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + if err != nil { + return err + } suite.keeper.SetClientState(suite.ctx, testClientID, clientState) suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) - updateHeader = createValidUpdateFn(suite) + updateHeader = createFutureUpdateFn(suite) return nil }, false}, + {"frozen client before update", func() error { + clientState = ibctmtypes.ClientState{FrozenHeight: 1, LatestHeight: testClientHeight} + suite.keeper.SetClientState(suite.ctx, testClientID, clientState) + suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) + updateHeader = createFutureUpdateFn(suite) + + return nil + }, false}, + {"valid past update before client was frozen", func() error { + clientState, err = ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + if err != nil { + return err + } + clientState.FrozenHeight = testClientHeight - 1 + _, err = suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) + + // store previous consensus state + prevConsState := ibctmtypes.ConsensusState{ + Height: 1, + Timestamp: suite.past, + NextValidatorsHash: suite.valSet.Hash(), + ValidatorSet: suite.valSet, + } + suite.keeper.SetClientConsensusState(suite.ctx, testClientID, 1, prevConsState) + + // updateHeader will fill in consensus state between prevConsState and suite.consState + // clientState should not be updated + updateHeader = createPastUpdateFn(suite) + return nil + }, true}, {"invalid header", func() error { - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err = ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) if err != nil { return err } @@ -122,7 +180,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { if err != nil { return err } - updateHeader = createInvalidUpdateFn(suite) + updateHeader = createPastUpdateFn(suite) return nil }, false}, @@ -145,13 +203,14 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { suite.Require().NoError(err, err) expConsensusState := ibctmtypes.ConsensusState{ - Height: updateHeader.GetHeight(), - Timestamp: updateHeader.Time, - Root: commitmenttypes.NewMerkleRoot(updateHeader.AppHash), - ValidatorSet: updateHeader.ValidatorSet, + Height: updateHeader.GetHeight(), + Timestamp: updateHeader.Time, + Root: commitmenttypes.NewMerkleRoot(updateHeader.AppHash), + NextValidatorsHash: updateHeader.NextValidatorsHash, + ValidatorSet: updateHeader.ValidatorSet, } - clientState, found := suite.keeper.GetClientState(suite.ctx, testClientID) + newClientState, 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()) @@ -161,16 +220,20 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // 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") + // check returned client state is same as client state in store + suite.Require().Equal(updatedClientState, newClientState, "updatedClient state not persisted correctly") - // recalculate cached totalVotingPower field for equality check - tmClientState.LastHeader.ValidatorSet.TotalVotingPower() + // Determine if clientState should be updated or not + if uint64(updateHeader.Height) > clientState.GetLatestHeight() { + // Header Height is greater than clientState latest Height, clientState should be updated with header.Height + suite.Require().Equal(uint64(updateHeader.Height), updatedClientState.GetLatestHeight(), "clientstate height did not update") + } else { + // Update will add past consensus state, clientState should not be updated at all + suite.Require().Equal(clientState.GetLatestHeight(), updatedClientState.GetLatestHeight(), "client state height updated for past header") + } 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) } @@ -228,7 +291,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.ValidatorSet = bothValSet - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) if err != nil { return err } @@ -248,7 +311,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.ValidatorSet = bothValSet - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) if err != nil { return err } @@ -273,7 +336,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { ClientID: testClientID, }, func() error { - clientState := ibctmtypes.ClientState{FrozenHeight: 1, LastHeader: suite.header} + clientState := ibctmtypes.ClientState{FrozenHeight: 1, LatestHeight: testClientHeight} suite.keeper.SetClientState(suite.ctx, testClientID, clientState) return nil }, @@ -288,7 +351,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { ClientID: testClientID, }, func() error { - clientState := ibctmtypes.ClientState{FrozenHeight: 1, LastHeader: suite.header} + clientState := ibctmtypes.ClientState{FrozenHeight: 1, LatestHeight: testClientHeight} suite.keeper.SetClientState(suite.ctx, testClientID, clientState) return nil }, @@ -303,7 +366,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { ClientID: testClientID, }, func() error { - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) if err != nil { return err } diff --git a/x/ibc/02-client/keeper/keeper.go b/x/ibc/02-client/keeper/keeper.go index e568d7d5b..afa9be88a 100644 --- a/x/ibc/02-client/keeper/keeper.go +++ b/x/ibc/02-client/keeper/keeper.go @@ -211,10 +211,11 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height uint64) (exported. valSet := stakingtypes.Validators(histInfo.Valset) consensusState := ibctmtypes.ConsensusState{ - Height: height, - Timestamp: histInfo.Header.Time, - Root: commitmenttypes.NewMerkleRoot(histInfo.Header.AppHash), - ValidatorSet: tmtypes.NewValidatorSet(valSet.ToTmValidators()), + Height: height, + Timestamp: histInfo.Header.Time, + Root: commitmenttypes.NewMerkleRoot(histInfo.Header.AppHash), + NextValidatorsHash: histInfo.Header.NextValidatorsHash, + ValidatorSet: tmtypes.NewValidatorSet(valSet.ToTmValidators()), } return consensusState, true } diff --git a/x/ibc/02-client/keeper/keeper_test.go b/x/ibc/02-client/keeper/keeper_test.go index 67d94872f..ff0032d3a 100644 --- a/x/ibc/02-client/keeper/keeper_test.go +++ b/x/ibc/02-client/keeper/keeper_test.go @@ -23,6 +23,8 @@ import ( ) const ( + testChainID = "gaiahub" + testClientID = "gaiachain" testClientID2 = "ethbridge" testClientID3 = "ethermint" @@ -45,11 +47,13 @@ type KeeperTestSuite struct { valSet *tmtypes.ValidatorSet privVal tmtypes.PrivValidator now time.Time + past time.Time } func (suite *KeeperTestSuite) SetupTest() { 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) @@ -63,12 +67,13 @@ func (suite *KeeperTestSuite) SetupTest() { validator := tmtypes.NewValidator(pubKey, 1) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - suite.header = ibctmtypes.CreateTestHeader(testClientID, testClientHeight, now2, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.header = ibctmtypes.CreateTestHeader(testChainID, testClientHeight, now2, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.consensusState = ibctmtypes.ConsensusState{ - Height: testClientHeight, - Timestamp: suite.now, - Root: commitmenttypes.NewMerkleRoot([]byte("hash")), - ValidatorSet: suite.valSet, + Height: testClientHeight, + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte("hash")), + NextValidatorsHash: suite.valSet.Hash(), + ValidatorSet: suite.valSet, } var validators stakingtypes.Validators @@ -90,7 +95,7 @@ func TestKeeperTestSuite(t *testing.T) { } func (suite *KeeperTestSuite) TestSetClientState() { - clientState := ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) retrievedState, found := suite.keeper.GetClientState(suite.ctx, testClientID) @@ -124,9 +129,9 @@ func (suite KeeperTestSuite) TestGetAllClients() { testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), } for i := range expClients { @@ -148,9 +153,9 @@ func (suite KeeperTestSuite) TestGetAllGenesisClients() { testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), } expGenClients := make([]types.GenesisClientState, len(expClients)) @@ -198,7 +203,7 @@ func (suite KeeperTestSuite) TestGetConsensusState() { func (suite KeeperTestSuite) TestConsensusStateHelpers() { // initial setup - clientState, err := ibctmtypes.Initialize(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState, err := ibctmtypes.Initialize(testChainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) suite.Require().NoError(err) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) @@ -214,7 +219,7 @@ func (suite KeeperTestSuite) TestConsensusStateHelpers() { header := ibctmtypes.CreateTestHeader(testClientID, testClientHeight+5, suite.header.Time.Add(time.Minute), suite.valSet, []tmtypes.PrivValidator{suite.privVal}) // mock update functionality - clientState.LastHeader = header + clientState.LatestHeight = uint64(header.Height) suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight+5, nextState) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) @@ -238,10 +243,10 @@ func (suite KeeperTestSuite) TestGetAllConsensusStates() { testClientID, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - suite.consensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.consensusState.GetHeight(), &tmtypes.ValidatorSet{}, + suite.consensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.consensusState.GetHeight(), nil, &tmtypes.ValidatorSet{}, ), ibctmtypes.NewConsensusState( - suite.consensusState.Timestamp.Add(time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash")), suite.consensusState.GetHeight()+1, &tmtypes.ValidatorSet{}, + suite.consensusState.Timestamp.Add(time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash")), suite.consensusState.GetHeight()+1, nil, &tmtypes.ValidatorSet{}, ), }, ), @@ -249,7 +254,7 @@ func (suite KeeperTestSuite) TestGetAllConsensusStates() { testClientID2, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - suite.consensusState.Timestamp.Add(2*time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash_2")), suite.consensusState.GetHeight()+2, &tmtypes.ValidatorSet{}, + suite.consensusState.Timestamp.Add(2*time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash_2")), suite.consensusState.GetHeight()+2, nil, &tmtypes.ValidatorSet{}, ), }, ), diff --git a/x/ibc/02-client/types/genesis_test.go b/x/ibc/02-client/types/genesis_test.go index 0b790b8c3..86354e5a8 100644 --- a/x/ibc/02-client/types/genesis_test.go +++ b/x/ibc/02-client/types/genesis_test.go @@ -17,8 +17,11 @@ import ( ) const ( + chainID = "chainID" clientID = "ethbridge" + height = 10 + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 maxClockDrift time.Duration = time.Second * 10 @@ -34,7 +37,7 @@ func TestValidateGenesis(t *testing.T) { val := tmtypes.NewValidator(pubKey, 10) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) - header := ibctmtypes.CreateTestHeader("chainID", 10, now, valSet, []tmtypes.PrivValidator{privVal}) + header := ibctmtypes.CreateTestHeader(chainID, height, now, valSet, []tmtypes.PrivValidator{privVal}) testCases := []struct { name string @@ -51,7 +54,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.GenesisClientState{ types.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), types.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chainID", 10), @@ -62,7 +65,7 @@ func TestValidateGenesis(t *testing.T) { clientID, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), header.GetHeight(), header.ValidatorSet, + header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), header.GetHeight(), header.ValidatorSet.Hash(), header.ValidatorSet, ), }, }, @@ -76,7 +79,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.GenesisClientState{ types.NewGenesisClientState( - "/~@$*", ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, commitmenttypes.GetSDKSpecs()), + "/~@$*", ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), types.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chainID", 10), @@ -87,7 +90,7 @@ func TestValidateGenesis(t *testing.T) { clientID, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), header.GetHeight(), header.ValidatorSet, + header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), header.GetHeight(), header.ValidatorSet.Hash(), header.ValidatorSet, ), }, }, @@ -102,7 +105,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.GenesisClientState{ types.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), types.NewGenesisClientState(exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", 0)), }, @@ -116,7 +119,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.GenesisClientState{ types.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), types.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", 10), @@ -127,7 +130,7 @@ func TestValidateGenesis(t *testing.T) { "CLIENTID2", []exported.ConsensusState{ ibctmtypes.NewConsensusState( - header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet, + header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet.Hash(), header.ValidatorSet, ), }, }, @@ -141,7 +144,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.GenesisClientState{ types.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), types.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", 10), @@ -152,7 +155,7 @@ func TestValidateGenesis(t *testing.T) { clientID, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet, + header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet.Hash(), header.ValidatorSet, ), }, ), diff --git a/x/ibc/07-tendermint/misbehaviour_test.go b/x/ibc/07-tendermint/misbehaviour_test.go index a207cc89e..5547c3246 100644 --- a/x/ibc/07-tendermint/misbehaviour_test.go +++ b/x/ibc/07-tendermint/misbehaviour_test.go @@ -54,7 +54,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }{ { "valid misbehavior evidence", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -69,7 +69,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "valid misbehavior at height greater than last consensusState", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Height: height - 1, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -84,7 +84,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "consensus state's valset hash different from evidence should still pass", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Height: height - 1, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: suite.valSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -129,7 +129,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "invalid tendermint consensus state", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), nil, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, altValSet, altSigners), @@ -144,7 +144,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "invalid tendermint misbehaviour evidence", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, nil, simapp.DefaultConsensusParams, @@ -154,7 +154,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "rejected misbehaviour due to expired age", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -169,7 +169,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "provided height ≠ header height", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -184,7 +184,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "unbonding period expired", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: time.Time{}, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -199,7 +199,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "first valset has too much change", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, altValSet, altSigners), @@ -214,7 +214,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "second valset has too much change", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, bothValSet, bothSigners), @@ -229,7 +229,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviour() { }, { "both valsets have too much change", - ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ibctmtypes.ConsensusState{Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), ValidatorSet: bothValSet}, ibctmtypes.Evidence{ Header1: ibctmtypes.CreateTestHeader(chainID, height, suite.now, altValSet, altSigners), diff --git a/x/ibc/07-tendermint/tendermint_test.go b/x/ibc/07-tendermint/tendermint_test.go index 8d05ddadd..a2bf6bfd4 100644 --- a/x/ibc/07-tendermint/tendermint_test.go +++ b/x/ibc/07-tendermint/tendermint_test.go @@ -26,6 +26,7 @@ type TendermintTestSuite struct { suite.Suite cdc *codec.Codec + signers []tmtypes.PrivValidator privVal tmtypes.PrivValidator valSet *tmtypes.ValidatorSet header ibctmtypes.Header @@ -47,15 +48,18 @@ func (suite *TendermintTestSuite) SetupTest() { // Header time is intended to be time for any new header used for updates suite.headerTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) suite.privVal = tmtypes.NewMockPV() + suite.signers = []tmtypes.PrivValidator{suite.privVal} pubKey, err := suite.privVal.GetPubKey() suite.Require().NoError(err) val := tmtypes.NewValidator(pubKey, 10) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) + // Suite header is intended to be header passed in for initial ClientState // Thus it should have same height and time as ClientState - suite.header = ibctmtypes.CreateTestHeader(chainID, height, suite.clientTime, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + // Note: default header has the same validator set suite.valSet as next validators set + suite.header = ibctmtypes.CreateTestHeader(chainID, height, suite.clientTime, suite.valSet, suite.signers) } func TestTendermintTestSuite(t *testing.T) { diff --git a/x/ibc/07-tendermint/types/client_state.go b/x/ibc/07-tendermint/types/client_state.go index 9b5bee8f7..d2e1ca2ca 100644 --- a/x/ibc/07-tendermint/types/client_state.go +++ b/x/ibc/07-tendermint/types/client_state.go @@ -1,6 +1,7 @@ package types import ( + "strings" "time" ics23 "github.com/confio/ics23/go" @@ -26,6 +27,8 @@ var _ clientexported.ClientState = ClientState{} // ClientState from Tendermint tracks the current validator set, latest height, // and a possible frozen height. type ClientState struct { + ChainID string `json:"chain_id" yaml:"chain_id"` + TrustLevel tmmath.Fraction `json:"trust_level" yaml:"trust_level"` // Duration of the period since the LastestTimestamp during which the @@ -42,8 +45,8 @@ type ClientState struct { // Block height when the client was frozen due to a misbehaviour FrozenHeight uint64 `json:"frozen_height" yaml:"frozen_height"` - // Last Header that was stored by client - LastHeader Header `json:"last_header" yaml:"last_header"` + // Latest height the client was updated up to + LatestHeight uint64 `json:"latest_height" yaml:"latest_height"` ProofSpecs []*ics23.ProofSpec `json:"proof_specs" yaml:"proof_specs"` } @@ -51,47 +54,48 @@ type ClientState struct { // InitializeFromMsg creates a tendermint client state from a CreateClientMsg func InitializeFromMsg(msg *MsgCreateClient) (ClientState, error) { return Initialize( + msg.Header.ChainID, msg.TrustLevel, msg.TrustingPeriod, msg.UnbondingPeriod, msg.MaxClockDrift, - msg.Header, msg.ProofSpecs, + uint64(msg.Header.Height), msg.ProofSpecs, ) } // Initialize creates a client state and validates its contents, checking that // the provided consensus state is from the same client type. func Initialize( + chainID string, trustLevel tmmath.Fraction, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, - header Header, specs []*ics23.ProofSpec, + latestHeight uint64, specs []*ics23.ProofSpec, ) (ClientState, error) { - clientState := NewClientState(trustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, specs) + clientState := NewClientState(chainID, trustLevel, trustingPeriod, ubdPeriod, maxClockDrift, latestHeight, specs) return clientState, nil } // NewClientState creates a new ClientState instance func NewClientState( + chainID string, trustLevel tmmath.Fraction, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, - header Header, specs []*ics23.ProofSpec, + latestHeight uint64, specs []*ics23.ProofSpec, ) ClientState { return ClientState{ + ChainID: chainID, TrustLevel: trustLevel, TrustingPeriod: trustingPeriod, UnbondingPeriod: ubdPeriod, MaxClockDrift: maxClockDrift, - LastHeader: header, + LatestHeight: latestHeight, FrozenHeight: 0, ProofSpecs: specs, } } -// GetChainID returns the chain-id from the last header +// GetChainID returns the chain-id func (cs ClientState) GetChainID() string { - if cs.LastHeader.SignedHeader.Header == nil { - return "" - } - return cs.LastHeader.SignedHeader.Header.ChainID + return cs.ChainID } // ClientType is tendermint. @@ -101,12 +105,7 @@ func (cs ClientState) ClientType() clientexported.ClientType { // GetLatestHeight returns latest block height. func (cs ClientState) GetLatestHeight() uint64 { - return uint64(cs.LastHeader.Height) -} - -// GetLatestTimestamp returns latest block time. -func (cs ClientState) GetLatestTimestamp() time.Time { - return cs.LastHeader.Time + return cs.LatestHeight } // IsFrozen returns true if the frozen height has been set. @@ -114,8 +113,17 @@ func (cs ClientState) IsFrozen() bool { return cs.FrozenHeight != 0 } +// FrozenHeight returns the height at which client is frozen +// NOTE: FrozenHeight is 0 if client is unfrozen +func (cs ClientState) GetFrozenHeight() uint64 { + return cs.FrozenHeight +} + // Validate performs a basic validation of the client state fields. func (cs ClientState) Validate() error { + if strings.TrimSpace(cs.ChainID) == "" { + return sdkerrors.Wrap(ErrInvalidChainID, "chain id cannot be empty string") + } if err := lite.ValidateTrustLevel(cs.TrustLevel); err != nil { return err } @@ -128,6 +136,9 @@ func (cs ClientState) Validate() error { if cs.MaxClockDrift == 0 { return sdkerrors.Wrap(ErrInvalidMaxClockDrift, "max clock drift cannot be zero") } + if cs.LatestHeight == 0 { + return sdkerrors.Wrap(ErrInvalidHeaderHeight, "tendermint height cannot be zero") + } if cs.TrustingPeriod >= cs.UnbondingPeriod { return sdkerrors.Wrapf( ErrInvalidTrustingPeriod, @@ -144,7 +155,7 @@ func (cs ClientState) Validate() error { } } - return cs.LastHeader.ValidateBasic(cs.GetChainID()) + return nil } // GetProofSpecs returns the format the client expects for proof verification diff --git a/x/ibc/07-tendermint/types/client_state_test.go b/x/ibc/07-tendermint/types/client_state_test.go index 1175e8072..f429790b5 100644 --- a/x/ibc/07-tendermint/types/client_state_test.go +++ b/x/ibc/07-tendermint/types/client_state_test.go @@ -28,47 +28,52 @@ func (suite *TendermintTestSuite) TestValidate() { }{ { name: "valid client", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), expPass: true, }, + { + name: "invalid chainID", + clientState: ibctmtypes.NewClientState(" ", lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + expPass: false, + }, { name: "invalid trust level", - clientState: ibctmtypes.NewClientState(tmmath.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, tmmath.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), expPass: false, }, { name: "invalid trusting period", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), expPass: false, }, { name: "invalid unbonding period", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), expPass: false, }, { name: "invalid max clock drift", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs()), expPass: false, }, { - name: "invalid header", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}, commitmenttypes.GetSDKSpecs()), + name: "invalid height", + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, 0, commitmenttypes.GetSDKSpecs()), expPass: false, }, { name: "trusting period not less than unbonding period", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), expPass: false, }, { name: "proof specs is nil", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, suite.header, nil), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil), expPass: false, }, { name: "proof specs contains nil", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, suite.header, []*ics23.ProofSpec{ics23.TendermintSpec, nil}), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}), expPass: false, }, } @@ -95,7 +100,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { // FIXME: uncomment // { // name: "successful verification", - // clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + // clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), // consensusState: ibctmtypes.ConsensusState{ // Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), // }, @@ -104,7 +109,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -113,7 +118,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -122,7 +127,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -131,7 +136,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), ValidatorSet: suite.valSet, @@ -173,7 +178,7 @@ func (suite *TendermintTestSuite) TestVerifyConnectionState() { // FIXME: uncomment // { // name: "successful verification", - // clientState: ibctmtypes.NewClientState(chainID, chainID, height, commitmenttypes.GetSDKSpecs()), + // clientState: ibctmtypes.NewClientState(chainID, chainID, chainID, height, commitmenttypes.GetSDKSpecs()), // connection: conn, // consensusState: ibctmtypes.ConsensusState{ // Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -183,7 +188,7 @@ func (suite *TendermintTestSuite) TestVerifyConnectionState() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), connection: conn, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -193,7 +198,7 @@ func (suite *TendermintTestSuite) TestVerifyConnectionState() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), connection: conn, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -203,7 +208,7 @@ func (suite *TendermintTestSuite) TestVerifyConnectionState() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, connection: conn, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -213,7 +218,7 @@ func (suite *TendermintTestSuite) TestVerifyConnectionState() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), connection: conn, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -266,7 +271,7 @@ func (suite *TendermintTestSuite) TestVerifyChannelState() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), channel: ch, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -276,7 +281,7 @@ func (suite *TendermintTestSuite) TestVerifyChannelState() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), channel: ch, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -286,7 +291,7 @@ func (suite *TendermintTestSuite) TestVerifyChannelState() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, channel: ch, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -296,7 +301,7 @@ func (suite *TendermintTestSuite) TestVerifyChannelState() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), channel: ch, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -346,7 +351,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketCommitment() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), commitment: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -356,7 +361,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketCommitment() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), commitment: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -366,7 +371,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketCommitment() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, commitment: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -376,7 +381,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketCommitment() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), commitment: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -416,7 +421,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() { // FIXME: uncomment // { // name: "successful verification", - // clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + // clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), // connection: conn, // consensusState: ibctmtypes.ConsensusState{ // Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -426,7 +431,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ack: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -436,7 +441,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ack: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -446,7 +451,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, ack: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -456,7 +461,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ack: []byte{}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), @@ -505,7 +510,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgementAbsence() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -514,7 +519,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgementAbsence() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -523,7 +528,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgementAbsence() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -532,7 +537,7 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgementAbsence() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), ValidatorSet: suite.valSet, @@ -580,7 +585,7 @@ func (suite *TendermintTestSuite) TestVerifyNextSeqRecv() { // }, { name: "ApplyPrefix failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -589,7 +594,7 @@ func (suite *TendermintTestSuite) TestVerifyNextSeqRecv() { }, { name: "latest client height < height", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -598,7 +603,7 @@ func (suite *TendermintTestSuite) TestVerifyNextSeqRecv() { }, { name: "client is frozen", - clientState: ibctmtypes.ClientState{LastHeader: suite.header, FrozenHeight: height - 1}, + clientState: ibctmtypes.ClientState{LatestHeight: height, FrozenHeight: height - 1}, consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), }, @@ -607,7 +612,7 @@ func (suite *TendermintTestSuite) TestVerifyNextSeqRecv() { }, { name: "proof verification failed", - clientState: ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientState: ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), consensusState: ibctmtypes.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.AppHash), ValidatorSet: suite.valSet, diff --git a/x/ibc/07-tendermint/types/consensus_state.go b/x/ibc/07-tendermint/types/consensus_state.go index 6dbe1d247..f601c03b2 100644 --- a/x/ibc/07-tendermint/types/consensus_state.go +++ b/x/ibc/07-tendermint/types/consensus_state.go @@ -3,6 +3,7 @@ package types import ( "time" + tmbytes "github.com/tendermint/tendermint/libs/bytes" tmtypes "github.com/tendermint/tendermint/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -13,22 +14,24 @@ import ( // ConsensusState defines a Tendermint consensus state type ConsensusState struct { - Timestamp time.Time `json:"timestamp" yaml:"timestamp"` - Root commitmentexported.Root `json:"root" yaml:"root"` - Height uint64 `json:"height" yaml:"height"` - ValidatorSet *tmtypes.ValidatorSet `json:"validator_set" yaml:"validator_set"` + Timestamp time.Time `json:"timestamp" yaml:"timestamp"` + Root commitmentexported.Root `json:"root" yaml:"root"` + Height uint64 `json:"height" yaml:"height"` + NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` // validators hash for the next block + ValidatorSet *tmtypes.ValidatorSet `json:"validator_set" yaml:"validator_set"` } // NewConsensusState creates a new ConsensusState instance. func NewConsensusState( timestamp time.Time, root commitmentexported.Root, height uint64, - valset *tmtypes.ValidatorSet, + nextValsHash tmbytes.HexBytes, valset *tmtypes.ValidatorSet, ) ConsensusState { return ConsensusState{ - Timestamp: timestamp, - Root: root, - Height: height, - ValidatorSet: valset, + Timestamp: timestamp, + Root: root, + Height: height, + NextValidatorsHash: nextValsHash, + ValidatorSet: valset, } } @@ -60,6 +63,9 @@ func (cs ConsensusState) ValidateBasic() error { if cs.ValidatorSet == nil { return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "validator set cannot be nil") } + if err := tmtypes.ValidateHash(cs.NextValidatorsHash); err != nil { + return sdkerrors.Wrap(err, "next validators hash is invalid") + } if cs.Height == 0 { return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "height cannot be 0") } diff --git a/x/ibc/07-tendermint/types/errors.go b/x/ibc/07-tendermint/types/errors.go index 81a176491..dd6a3f79c 100644 --- a/x/ibc/07-tendermint/types/errors.go +++ b/x/ibc/07-tendermint/types/errors.go @@ -10,11 +10,13 @@ const ( // IBC tendermint client sentinel errors var ( - ErrInvalidTrustingPeriod = sdkerrors.Register(SubModuleName, 2, "invalid trusting period") - ErrInvalidUnbondingPeriod = sdkerrors.Register(SubModuleName, 3, "invalid unbonding period") - ErrInvalidHeader = sdkerrors.Register(SubModuleName, 4, "invalid header") - ErrInvalidMaxClockDrift = sdkerrors.Register(SubModuleName, 5, "invalid max clock drift") - ErrTrustingPeriodExpired = sdkerrors.Register(SubModuleName, 6, "time since latest trusted state has passed the trusting period") - ErrUnbondingPeriodExpired = sdkerrors.Register(SubModuleName, 7, "time since latest trusted state has passed the unbonding period") - ErrInvalidProofSpecs = sdkerrors.Register(SubModuleName, 8, "invalid proof specs") + ErrInvalidChainID = sdkerrors.Register(SubModuleName, 2, "invalid chain-id") + ErrInvalidTrustingPeriod = sdkerrors.Register(SubModuleName, 3, "invalid trusting period") + ErrInvalidUnbondingPeriod = sdkerrors.Register(SubModuleName, 4, "invalid unbonding period") + ErrInvalidHeaderHeight = sdkerrors.Register(SubModuleName, 5, "invalid header height") + ErrInvalidHeader = sdkerrors.Register(SubModuleName, 6, "invalid header") + ErrInvalidMaxClockDrift = sdkerrors.Register(SubModuleName, 7, "invalid max clock drift") + ErrTrustingPeriodExpired = sdkerrors.Register(SubModuleName, 8, "time since latest trusted state has passed the trusting period") + ErrUnbondingPeriodExpired = sdkerrors.Register(SubModuleName, 9, "time since latest trusted state has passed the unbonding period") + ErrInvalidProofSpecs = sdkerrors.Register(SubModuleName, 10, "invalid proof specs") ) diff --git a/x/ibc/07-tendermint/types/msgs.go b/x/ibc/07-tendermint/types/msgs.go index 1e5f175b7..84476898b 100644 --- a/x/ibc/07-tendermint/types/msgs.go +++ b/x/ibc/07-tendermint/types/msgs.go @@ -142,10 +142,11 @@ func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState { // Construct initial consensus state from provided Header root := commitmenttypes.NewMerkleRoot(msg.Header.AppHash) return ConsensusState{ - Timestamp: msg.Header.Time, - Root: root, - Height: uint64(msg.Header.Height), - ValidatorSet: msg.Header.ValidatorSet, + Timestamp: msg.Header.Time, + Root: root, + Height: uint64(msg.Header.Height), + NextValidatorsHash: msg.Header.NextValidatorsHash, + ValidatorSet: msg.Header.ValidatorSet, } } diff --git a/x/ibc/07-tendermint/update.go b/x/ibc/07-tendermint/update.go index 728fe1eaa..6db9bfdb6 100644 --- a/x/ibc/07-tendermint/update.go +++ b/x/ibc/07-tendermint/update.go @@ -4,6 +4,7 @@ import ( "time" lite "github.com/tendermint/tendermint/lite2" + tmtypes "github.com/tendermint/tendermint/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" @@ -12,18 +13,28 @@ import ( commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" ) -// CheckValidityAndUpdateState checks if the provided header is valid and updates -// the consensus state if appropriate. It returns an error if: +// CheckValidityAndUpdateState checks if the provided header is valid, and if valid it will: +// create the consensus state for the header.Height +// and update the client state if the header height is greater than the latest client state height +// It returns an error if: // - the client or header provided are not parseable to tendermint types // - the header is invalid -// - header height is lower than the latest client height +// - header height is less than or equal to the consensus state height // - 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 // +// UpdateClient may be used to either create a consensus state for: +// - a future height greater than the latest client state height +// - a past height that was skipped during bisection +// If we are updating to a past height, a consensus state is created for that height to be persisted in client store +// If we are updating to a future height, the consensus state is created and the client state is updated to reflect +// the new latest height // Tendermint client validity checking uses the bisection algorithm described // in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). func CheckValidityAndUpdateState( - clientState clientexported.ClientState, header clientexported.Header, - currentTimestamp time.Time, + clientState clientexported.ClientState, consState clientexported.ConsensusState, + header clientexported.Header, currentTimestamp time.Time, ) (clientexported.ClientState, clientexported.ConsensusState, error) { tmClientState, ok := clientState.(types.ClientState) if !ok { @@ -32,6 +43,13 @@ func CheckValidityAndUpdateState( ) } + tmConsState, ok := consState.(types.ConsensusState) + if !ok { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidConsensus, "expected type %T, got %T", types.ConsensusState{}, consState, + ) + } + tmHeader, ok := header.(types.Header) if !ok { return nil, nil, sdkerrors.Wrapf( @@ -39,7 +57,7 @@ func CheckValidityAndUpdateState( ) } - if err := checkValidity(tmClientState, tmHeader, currentTimestamp); err != nil { + if err := checkValidity(tmClientState, tmConsState, tmHeader, currentTimestamp); err != nil { return nil, nil, err } @@ -48,49 +66,58 @@ func CheckValidityAndUpdateState( } // checkValidity checks if the Tendermint header is valid. -// -// CONTRACT: assumes header.Height > consensusState.Height func checkValidity( - clientState types.ClientState, header types.Header, currentTimestamp time.Time, + clientState types.ClientState, consState types.ConsensusState, header types.Header, currentTimestamp time.Time, ) error { // assert trusting period has not yet passed - if currentTimestamp.Sub(clientState.GetLatestTimestamp()) >= clientState.TrustingPeriod { + if currentTimestamp.Sub(consState.Timestamp) >= clientState.TrustingPeriod { return sdkerrors.Wrapf( types.ErrTrustingPeriodExpired, - "current timestamp minus the latest trusted client state timestamp is greater than or equal to the trusting period (%s >= %s)", - currentTimestamp.Sub(clientState.GetLatestTimestamp()), clientState.TrustingPeriod, + "current timestamp minus the consensus state timestamp is greater than or equal to the trusting period (%s >= %s)", + currentTimestamp.Sub(consState.Timestamp), clientState.TrustingPeriod, ) } // assert header timestamp is not past the trusting period - if header.Time.Sub(clientState.GetLatestTimestamp()) >= clientState.TrustingPeriod { + if header.Time.Sub(consState.Timestamp) >= clientState.TrustingPeriod { return sdkerrors.Wrap( clienttypes.ErrInvalidHeader, - "header blocktime is outside trusting period from last client timestamp", + "header timestamp is beyond trusting period in relation to the consensus state timestamp", ) } - // assert header timestamp is past latest clientstate timestamp - if header.Time.Unix() <= clientState.GetLatestTimestamp().Unix() { + // assert header timestamp is past latest stored consensus state timestamp + if header.Time.Unix() <= consState.Timestamp.Unix() { return sdkerrors.Wrapf( clienttypes.ErrInvalidHeader, - "header blocktime ≤ latest client state block time (%s ≤ %s)", - header.Time.UTC(), clientState.GetLatestTimestamp().UTC(), + "header timestamp ≤ consensus state timestamp (%s ≤ %s)", + header.Time.UTC(), consState.Timestamp.UTC(), ) } - // assert header height is newer than any we know - if header.GetHeight() <= clientState.GetLatestHeight() { + // assert header height is newer than consensus state + if header.GetHeight() <= consState.Height { return sdkerrors.Wrapf( clienttypes.ErrInvalidHeader, - "header height ≤ latest client state height (%d ≤ %d)", header.GetHeight(), clientState.GetLatestHeight(), + "header height ≤ consensus state height (%d ≤ %d)", header.GetHeight(), consState.Height, ) } + // Construct a trusted header using the fields in consensus state + // Only Height, Time, and NextValidatorsHash are necessary for verification + trustedHeader := tmtypes.Header{ + Height: int64(consState.Height), + Time: consState.Timestamp, + NextValidatorsHash: consState.NextValidatorsHash, + } + signedHeader := tmtypes.SignedHeader{ + Header: &trustedHeader, + } + // Verify next header with the last header's validatorset as trusted validatorset err := lite.Verify( - clientState.GetChainID(), &clientState.LastHeader.SignedHeader, - clientState.LastHeader.ValidatorSet, &header.SignedHeader, header.ValidatorSet, + clientState.GetChainID(), &signedHeader, + consState.ValidatorSet, &header.SignedHeader, header.ValidatorSet, clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, clientState.TrustLevel, ) if err != nil { @@ -101,12 +128,15 @@ func checkValidity( // update the consensus state from a new header func update(clientState types.ClientState, header types.Header) (types.ClientState, types.ConsensusState) { - clientState.LastHeader = header + if uint64(header.Height) > clientState.LatestHeight { + clientState.LatestHeight = uint64(header.Height) + } consensusState := types.ConsensusState{ - Height: uint64(header.Height), - Timestamp: header.Time, - Root: commitmenttypes.NewMerkleRoot(header.AppHash), - ValidatorSet: header.ValidatorSet, + Height: uint64(header.Height), + Timestamp: header.Time, + Root: commitmenttypes.NewMerkleRoot(header.AppHash), + NextValidatorsHash: header.NextValidatorsHash, + ValidatorSet: header.ValidatorSet, } return clientState, consensusState diff --git a/x/ibc/07-tendermint/update_test.go b/x/ibc/07-tendermint/update_test.go index 2b21ed089..7da660b09 100644 --- a/x/ibc/07-tendermint/update_test.go +++ b/x/ibc/07-tendermint/update_test.go @@ -14,9 +14,10 @@ import ( func (suite *TendermintTestSuite) TestCheckValidity() { var ( - clientState ibctmtypes.ClientState - newHeader ibctmtypes.Header - currentTime time.Time + clientState ibctmtypes.ClientState + consensusState ibctmtypes.ConsensusState + newHeader ibctmtypes.Header + currentTime time.Time ) // Setup different validators and signers for testing different types of updates @@ -54,7 +55,8 @@ func (suite *TendermintTestSuite) TestCheckValidity() { { name: "successful update with next height and same validator set", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, suite.valSet, signers) currentTime = suite.now }, @@ -63,25 +65,58 @@ func (suite *TendermintTestSuite) TestCheckValidity() { { name: "successful update with future height and different validator set", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+5, suite.headerTime, bothValSet, bothSigners) currentTime = suite.now }, expPass: true, }, + { + name: "successful update with next height and different validator set", + setup: func() { + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, bothValSet.Hash(), suite.valSet) + newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, bothValSet, bothSigners) + currentTime = suite.now + }, + expPass: true, + }, + { + name: "successful update for a previous height", + setup: func() { + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height-3, bothValSet.Hash(), suite.valSet) + newHeader = ibctmtypes.CreateTestHeader(chainID, height-1, suite.headerTime, bothValSet, bothSigners) + currentTime = suite.now + }, + expPass: true, + }, { name: "unsuccessful update with next height: update header mismatches nextValSetHash", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, bothValSet, bothSigners) currentTime = suite.now }, expPass: false, }, + { + name: "unsuccessful update with next height: update header mismatches different nextValSetHash", + setup: func() { + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, bothValSet.Hash(), suite.valSet) + newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, suite.valSet, signers) + currentTime = suite.now + }, + expPass: false, + }, { name: "unsuccessful update with future height: too much change in validator set", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+5, suite.headerTime, altValSet, altSigners) currentTime = suite.now }, @@ -90,17 +125,19 @@ func (suite *TendermintTestSuite) TestCheckValidity() { { name: "unsuccessful update: trusting period has passed since last client timestamp", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate - currentTime = suite.now.Add(ubdPeriod) + currentTime = suite.now.Add(trustingPeriod) }, expPass: false, }, { name: "unsuccessful update: header timestamp is past current timestamp", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.now.Add(time.Minute), suite.valSet, signers) currentTime = suite.now }, @@ -109,7 +146,8 @@ func (suite *TendermintTestSuite) TestCheckValidity() { { name: "unsuccessful update: header timestamp is not past last client timestamp", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.clientTime, suite.valSet, signers) currentTime = suite.now }, @@ -118,7 +156,8 @@ func (suite *TendermintTestSuite) TestCheckValidity() { { name: "header basic validation failed", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) newHeader = ibctmtypes.CreateTestHeader(chainID, height+1, suite.headerTime, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height newHeader.SignedHeader.Commit.Height = height - 1 @@ -127,14 +166,14 @@ func (suite *TendermintTestSuite) TestCheckValidity() { expPass: false, }, { - name: "header height < latest client height", + name: "header height < consensus height", setup: func() { - clientState = ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height+5, commitmenttypes.GetSDKSpecs()) + consensusState = ibctmtypes.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.AppHash), height, suite.valSet.Hash(), suite.valSet) // Make new header at height less than latest client state newHeader = ibctmtypes.CreateTestHeader(chainID, height-1, suite.headerTime, suite.valSet, signers) currentTime = suite.now }, - expPass: false, }, } @@ -145,21 +184,31 @@ func (suite *TendermintTestSuite) TestCheckValidity() { tc.setup() expectedConsensus := ibctmtypes.ConsensusState{ - Height: uint64(newHeader.Height), - Timestamp: newHeader.Time, - Root: commitmenttypes.NewMerkleRoot(newHeader.AppHash), - ValidatorSet: newHeader.ValidatorSet, + Height: uint64(newHeader.Height), + Timestamp: newHeader.Time, + Root: commitmenttypes.NewMerkleRoot(newHeader.AppHash), + NextValidatorsHash: newHeader.NextValidatorsHash, + ValidatorSet: newHeader.ValidatorSet, } - clientState, consensusState, err := tendermint.CheckValidityAndUpdateState(clientState, newHeader, currentTime) + newClientState, consensusState, err := tendermint.CheckValidityAndUpdateState(clientState, consensusState, newHeader, currentTime) if tc.expPass { suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(newHeader.GetHeight(), clientState.GetLatestHeight(), "valid test case %d failed: %s", i, tc.name) + + // Determine if clientState should be updated or not + if uint64(newHeader.Height) > clientState.LatestHeight { + // Header Height is greater than clientState latest Height, clientState should be updated with header.Height + suite.Require().Equal(uint64(newHeader.Height), newClientState.GetLatestHeight(), "clientstate height did not update") + } else { + // Update will add past consensus state, clientState should not be updated at all + suite.Require().Equal(clientState.LatestHeight, newClientState.GetLatestHeight(), "client state height updated for past header") + } + suite.Require().Equal(expectedConsensus, consensusState, "valid test case %d failed: %s", i, tc.name) } else { suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - suite.Require().Nil(clientState, "invalid test case %d passed: %s", i, tc.name) + suite.Require().Nil(newClientState, "invalid test case %d passed: %s", i, tc.name) suite.Require().Nil(consensusState, "invalid test case %d passed: %s", i, tc.name) } } diff --git a/x/ibc/09-localhost/types/client_state.go b/x/ibc/09-localhost/types/client_state.go index 8b90ae982..cda9ad10a 100644 --- a/x/ibc/09-localhost/types/client_state.go +++ b/x/ibc/09-localhost/types/client_state.go @@ -51,6 +51,11 @@ func (cs ClientState) IsFrozen() bool { return false } +// GetFrozenHeight returns 0. +func (cs ClientState) GetFrozenHeight() uint64 { + return 0 +} + // Validate performs a basic validation of the client state fields. func (cs ClientState) Validate() error { if strings.TrimSpace(cs.ChainID) == "" { diff --git a/x/ibc/genesis_test.go b/x/ibc/genesis_test.go index 48fc7d129..1ef8a502f 100644 --- a/x/ibc/genesis_test.go +++ b/x/ibc/genesis_test.go @@ -32,7 +32,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { ClientGenesis: clienttypes.NewGenesisState( []clienttypes.GenesisClientState{ clienttypes.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), clienttypes.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", 10), @@ -43,7 +43,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { clientID, []exported.ConsensusState{ ibctmtypes.NewConsensusState( - suite.header.Time, commitmenttypes.NewMerkleRoot(suite.header.AppHash), suite.header.GetHeight(), suite.header.ValidatorSet, + suite.header.Time, commitmenttypes.NewMerkleRoot(suite.header.AppHash), suite.header.GetHeight(), suite.header.ValidatorSet.Hash(), suite.header.ValidatorSet, ), }, ), @@ -92,7 +92,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { ClientGenesis: clienttypes.NewGenesisState( []clienttypes.GenesisClientState{ clienttypes.NewGenesisClientState( - clientID, ibctmtypes.NewClientState(lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, suite.header, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, lite.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), ), clienttypes.NewGenesisClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("(chaindID)", 0), diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 55f96c2d8..4ad12b3cd 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -17,6 +17,8 @@ import ( ) const ( + chainID = "chainID" + connectionID = "connectionidone" clientID = "clientidone" connectionID2 = "connectionidtwo" @@ -31,6 +33,8 @@ const ( channelOrder = channeltypes.ORDERED channelVersion = "1.0" + height = 10 + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 maxClockDrift time.Duration = time.Second * 10 @@ -58,7 +62,7 @@ func (suite *IBCTestSuite) SetupTest() { val := tmtypes.NewValidator(pubKey, 10) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) - suite.header = ibctmtypes.CreateTestHeader("chainID", 10, now, valSet, []tmtypes.PrivValidator{privVal}) + suite.header = ibctmtypes.CreateTestHeader(chainID, height, now, valSet, []tmtypes.PrivValidator{privVal}) suite.cdc = suite.app.Codec() suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, abci.Header{}) diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index f8b4e78df..942e909e0 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -202,7 +202,9 @@ func (chain *TestChain) NextBlock() { AppHash: chain.App.LastCommitID().Hash, // NOTE: the time is increased by the coordinator to maintain time synchrony amongst // chains. - Time: chain.CurrentHeader.Time, + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.Vals.Hash(), } chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader})