mukan-ibc/modules/light-clients/07-tendermint/update_test.go
Mukan Erkin Törük 6852832fe8
Some checks failed
CodeQL / Analyze (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Deploy to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Buf-Push / push (push) Has been cancelled
initial: sovereign Mukan Network fork
2026-05-11 03:18:28 +03:00

958 lines
38 KiB
Go

package tendermint_test
import (
"errors"
"time"
storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
cmttypes "github.com/cometbft/cometbft/types"
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types"
host "github.com/cosmos/ibc-go/v10/modules/core/24-host"
"github.com/cosmos/ibc-go/v10/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v10/testing"
)
func (suite *TendermintTestSuite) TestVerifyHeader() {
var (
path *ibctesting.Path
header *ibctm.Header
)
// Setup different validators and signers for testing different types of updates
altPrivVal := cmttypes.NewMockPV()
altPubKey, err := altPrivVal.GetPubKey()
suite.Require().NoError(err)
revisionHeight := int64(height.RevisionHeight)
// create modified heights to use for test-cases
altVal := cmttypes.NewValidator(altPubKey, 100)
// Create alternative validator set with only altVal, invalid update (too much change in valSet)
altValSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{altVal})
altSigners := getAltSigners(altVal, altPrivVal)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
name: "success",
malleate: func() {},
expErr: nil,
},
{
name: "successful verify header for header with a previous height",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// passing the ProposedHeader.Height as the block height as it will become a previous height once we commit N blocks
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
// commit some blocks so that the created Header now has a previous height as the BlockHeight
suite.coordinator.CommitNBlocks(suite.chainB, 5)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
},
expErr: nil,
},
{
name: "successful verify header: header with future height and different validator set",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// Create bothValSet with both suite validator and altVal
bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal))
bothSigners := suite.chainB.Signers
bothSigners[altVal.Address.String()] = altPrivVal
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+5, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners)
},
expErr: nil,
},
{
name: "successful verify header: header with next height and different validator set",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// Create bothValSet with both suite validator and altVal
bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal))
bothSigners := suite.chainB.Signers
bothSigners[altVal.Address.String()] = altPrivVal
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners)
},
expErr: nil,
},
{
name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
// Create bothValSet with both suite validator and altVal
bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal))
bothSigners := suite.chainB.Signers
bothSigners[altVal.Address.String()] = altPrivVal
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, bothValSet, bothValSet, bothSigners)
},
expErr: errors.New("invalid validator set"),
},
{
name: "unsuccessful verify header with next height: update header mismatches nextValSetHash",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// this will err as altValSet.Hash() != consState.NextValidatorsHash
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, altValSet, trustedVals, altSigners)
},
expErr: errors.New("failed to verify header"),
},
{
name: "unsuccessful update with future height: too much change in validator set",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, altValSet, trustedVals, altSigners)
},
expErr: errors.New("failed to verify header: cant trust new val set"),
},
{
name: "unsuccessful verify header: header height revision and trusted height revision mismatch",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
header = suite.chainB.CreateTMClientHeader(chainIDRevision1, 3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("invalid client header"),
},
{
name: "unsuccessful verify header: header height < consensus height",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
heightMinus1 := clienttypes.NewHeight(trustedHeight.RevisionNumber, trustedHeight.RevisionHeight-1)
// Make new header at height less than latest client state
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(heightMinus1.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("invalid client header"),
},
{
name: "unsuccessful verify header: header basic validation failed",
malleate: func() {
// cause header to fail validatebasic by changing commit height to mismatch header height
header.Commit.Height = revisionHeight - 1
},
expErr: errors.New("header and commit height mismatch"),
},
{
name: "unsuccessful verify header: header timestamp is not past last client timestamp",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight]
suite.Require().True(ok)
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time.Add(-time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("failed to verify header"),
},
{
name: "unsuccessful verify header: header with incorrect header chain-id",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight]
suite.Require().True(ok)
header = suite.chainB.CreateTMClientHeader(chainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("header height revision 0 does not match trusted header revision 1"),
},
{
name: "unsuccessful update: trusting period has passed since last client timestamp",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight]
suite.Require().True(ok)
header = suite.chainA.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
suite.chainB.ExpireClient(ibctesting.TrustingPeriod)
},
expErr: errors.New("failed to verify header"),
},
{
name: "unsuccessful update for a previous revision",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// passing the ProposedHeader.Height as the block height as it will become an update to previous revision once we upgrade the client
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
// increment the revision of the chain
err = path.EndpointB.UpgradeChain()
suite.Require().NoError(err)
},
expErr: errors.New("failed to verify header"),
},
{
name: "successful update with identical header to a previous update",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// passing the ProposedHeader.Height as the block height as it will become a previous height once we commit N blocks
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
// update client so the header constructed becomes a duplicate
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
},
expErr: nil,
},
{
name: "unsuccessful update to a future revision",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID+"-1", suite.chainB.ProposedHeader.Height+5, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("failed to verify header"),
},
{
name: "unsuccessful update: header height revision and trusted height revision mismatch",
malleate: func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
// increment the revision of the chain
err = path.EndpointB.UpgradeChain()
suite.Require().NoError(err)
header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
},
expErr: errors.New("header height revision 2 does not match trusted header revision 1"),
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
path = ibctesting.NewPath(suite.chainA, suite.chainB)
err := path.EndpointA.CreateClient()
suite.Require().NoError(err)
// ensure counterparty state is committed
suite.coordinator.CommitBlock(suite.chainB)
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
header, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().NoError(err)
tc.malleate()
err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), path.EndpointA.ClientID, header)
if tc.expErr == nil {
suite.Require().NoError(err, tc.name)
} else {
suite.Require().Error(err)
suite.Require().ErrorContains(err, tc.expErr.Error())
}
})
}
}
func (suite *TendermintTestSuite) TestUpdateState() {
var (
path *ibctesting.Path
clientMessage exported.ClientMessage
clientStore storetypes.KVStore
consensusHeights []exported.Height
pruneHeight clienttypes.Height
prevClientState exported.ClientState
prevConsensusState exported.ConsensusState
)
testCases := []struct {
name string
malleate func()
expResult func()
expPass bool
}{
{
"success with height later than latest height", func() {
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientLatestHeight().(clienttypes.Height).LT(tmHeader.GetHeight()))
},
func() {
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState)
suite.Require().True(ok)
suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed
suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0]))
}, true,
},
{
"success with height earlier than latest height", func() {
// commit a block so the pre-created ClientMessage
// isn't used to update the client to a newer height
suite.coordinator.CommitBlock(suite.chainB)
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.Require().True(path.EndpointA.GetClientLatestHeight().(clienttypes.Height).GT(tmHeader.GetHeight()))
prevClientState = path.EndpointA.GetClientState()
},
func() {
clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState)
suite.Require().True(ok)
suite.Require().Equal(clientState, prevClientState) // fill in height, no change to client state
suite.Require().True(clientState.LatestHeight.GT(consensusHeights[0]))
}, true,
},
{
"success with duplicate header", func() {
// update client in advance
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// use the same header which just updated the client
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.Require().Equal(path.EndpointA.GetClientLatestHeight().(clienttypes.Height), tmHeader.GetHeight())
prevClientState = path.EndpointA.GetClientState()
prevConsensusState = path.EndpointA.GetConsensusState(tmHeader.GetHeight())
},
func() {
clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState)
suite.Require().True(ok)
suite.Require().Equal(clientState, prevClientState)
suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0]))
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.Require().Equal(path.EndpointA.GetConsensusState(tmHeader.GetHeight()), prevConsensusState)
}, true,
},
{
"success with pruned consensus state", func() {
// this height will be expired and pruned
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
var ok bool
pruneHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
// Increment the time by a week
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
// create the consensus state that can be used as trusted height for next update
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// Increment the time by another week, then update the client.
// This will cause the first two consensus states to become expired.
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// ensure counterparty state is committed
suite.coordinator.CommitBlock(suite.chainB)
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
},
func() {
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState)
suite.Require().True(ok)
suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed
suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0]))
// ensure consensus state was pruned
_, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
suite.Require().False(found)
}, true,
},
{
"success with pruned consensus state using duplicate header", func() {
// this height will be expired and pruned
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
var ok bool
pruneHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
// assert that a consensus state exists at the prune height
consensusState, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
suite.Require().True(found)
suite.Require().NotNil(consensusState)
// Increment the time by a week
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
// create the consensus state that can be used as trusted height for next update
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// Increment the time by another week, then update the client.
// This will cause the first two consensus states to become expired.
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// use the same header which just updated the client
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
},
func() {
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState)
suite.Require().True(ok)
suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed
suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0]))
// ensure consensus state was pruned
_, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
suite.Require().False(found)
}, true,
},
{
"invalid ClientMessage type", func() {
clientMessage = &ibctm.Misbehaviour{}
},
func() {},
false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
pruneHeight = clienttypes.ZeroHeight()
path = ibctesting.NewPath(suite.chainA, suite.chainB)
err := path.EndpointA.CreateClient()
suite.Require().NoError(err)
// ensure counterparty state is committed
suite.coordinator.CommitBlock(suite.chainB)
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().NoError(err)
tc.malleate()
clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID)
if tc.expPass {
consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage)
header, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
expConsensusState := &ibctm.ConsensusState{
Timestamp: header.GetTime(),
Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()),
NextValidatorsHash: header.Header.NextValidatorsHash,
}
bz := clientStore.Get(host.ConsensusStateKey(header.GetHeight()))
updatedConsensusState := clienttypes.MustUnmarshalConsensusState(suite.chainA.App.AppCodec(), bz)
suite.Require().Equal(expConsensusState, updatedConsensusState)
} else {
consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage)
suite.Require().Empty(consensusHeights)
consensusState, found := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight())))
suite.Require().False(found)
suite.Require().Nil(consensusState)
}
// perform custom checks
tc.expResult()
})
}
}
func (suite *TendermintTestSuite) TestUpdateStateCheckTx() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupClients()
createClientMessage := func() exported.ClientMessage {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
header, err := path.EndpointB.Chain.IBCClientHeader(path.EndpointB.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
return header
}
// get the first height as it will be pruned first.
var pruneHeight exported.Height
getFirstHeightCb := func(height exported.Height) bool {
pruneHeight = height
return true
}
ctx := path.EndpointA.Chain.GetContext()
clientStore := path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID)
ibctm.IterateConsensusStateAscending(clientStore, getFirstHeightCb)
// Increment the time by a week
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().NoError(err)
ctx = path.EndpointA.Chain.GetContext().WithIsCheckTx(true)
lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage())
// Increment the time by another week, then update the client.
// This will cause the first two consensus states to become expired.
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
ctx = path.EndpointA.Chain.GetContext().WithIsCheckTx(true)
lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage())
assertPrune := func(pruned bool) {
// check consensus states and associated metadata
consState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
suite.Require().Equal(!pruned, ok)
processTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight)
suite.Require().Equal(!pruned, ok)
processHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight)
suite.Require().Equal(!pruned, ok)
consKey := ibctm.GetIterationKey(clientStore, pruneHeight)
if pruned {
suite.Require().Nil(consState, "expired consensus state not pruned")
suite.Require().Empty(processTime, "processed time metadata not pruned")
suite.Require().Nil(processHeight, "processed height metadata not pruned")
suite.Require().Nil(consKey, "iteration key not pruned")
} else {
suite.Require().NotNil(consState, "expired consensus state pruned")
suite.Require().NotEqual(uint64(0), processTime, "processed time metadata pruned")
suite.Require().NotNil(processHeight, "processed height metadata pruned")
suite.Require().NotNil(consKey, "iteration key pruned")
}
}
assertPrune(false)
// simulation mode must prune to calculate gas correctly
ctx = ctx.WithExecMode(sdk.ExecModeSimulate)
lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage())
assertPrune(true)
}
func (suite *TendermintTestSuite) TestPruneConsensusState() {
// create path and setup clients
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupClients()
// get the first height as it will be pruned first.
var pruneHeight exported.Height
getFirstHeightCb := func(height exported.Height) bool {
pruneHeight = height
return true
}
ctx := path.EndpointA.Chain.GetContext()
clientStore := path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID)
ibctm.IterateConsensusStateAscending(clientStore, getFirstHeightCb)
// this height will be expired but not pruned
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
expiredHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
// expected values that must still remain in store after pruning
expectedConsState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, expiredHeight)
suite.Require().True(ok)
ctx = path.EndpointA.Chain.GetContext()
clientStore = path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID)
expectedProcessTime, ok := ibctm.GetProcessedTime(clientStore, expiredHeight)
suite.Require().True(ok)
expectedProcessHeight, ok := ibctm.GetProcessedHeight(clientStore, expiredHeight)
suite.Require().True(ok)
expectedConsKey := ibctm.GetIterationKey(clientStore, expiredHeight)
suite.Require().NotNil(expectedConsKey)
// Increment the time by a week
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
// create the consensus state that can be used as trusted height for next update
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// Increment the time by another week, then update the client.
// This will cause the first two consensus states to become expired.
suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
ctx = path.EndpointA.Chain.GetContext()
clientStore = path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID)
// check that the first expired consensus state got deleted along with all associated metadata
consState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight)
suite.Require().Nil(consState, "expired consensus state not pruned")
suite.Require().False(ok)
// check processed time metadata is pruned
processTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight)
suite.Require().Equal(uint64(0), processTime, "processed time metadata not pruned")
suite.Require().False(ok)
processHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight)
suite.Require().Nil(processHeight, "processed height metadata not pruned")
suite.Require().False(ok)
// check iteration key metadata is pruned
consKey := ibctm.GetIterationKey(clientStore, pruneHeight)
suite.Require().Nil(consKey, "iteration key not pruned")
// check that second expired consensus state doesn't get deleted
// this ensures that there is a cap on gas cost of UpdateClient
consState, ok = path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, expiredHeight)
suite.Require().Equal(expectedConsState, consState, "consensus state incorrectly pruned")
suite.Require().True(ok)
// check processed time metadata is not pruned
processTime, ok = ibctm.GetProcessedTime(clientStore, expiredHeight)
suite.Require().Equal(expectedProcessTime, processTime, "processed time metadata incorrectly pruned")
suite.Require().True(ok)
// check processed height metadata is not pruned
processHeight, ok = ibctm.GetProcessedHeight(clientStore, expiredHeight)
suite.Require().Equal(expectedProcessHeight, processHeight, "processed height metadata incorrectly pruned")
suite.Require().True(ok)
// check iteration key metadata is not pruned
consKey = ibctm.GetIterationKey(clientStore, expiredHeight)
suite.Require().Equal(expectedConsKey, consKey, "iteration key incorrectly pruned")
}
func (suite *TendermintTestSuite) TestCheckForMisbehaviour() {
var (
path *ibctesting.Path
clientMessage exported.ClientMessage
)
testCases := []struct {
name string
malleate func()
expPass bool
}{
{
"valid update no misbehaviour",
func() {},
false,
},
{
"consensus state already exists, already updated",
func() {
header, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
consensusState := &ibctm.ConsensusState{
Timestamp: header.GetTime(),
Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()),
NextValidatorsHash: header.Header.NextValidatorsHash,
}
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState)
},
false,
},
{
"invalid fork misbehaviour: identical headers", func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
misbehaviourHeader := suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers)
clientMessage = &ibctm.Misbehaviour{
Header1: misbehaviourHeader,
Header2: misbehaviourHeader,
}
}, false,
},
{
"invalid time misbehaviour: monotonically increasing time", func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
clientMessage = &ibctm.Misbehaviour{
Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers),
Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers),
}
}, false,
},
{
"consensus state already exists, app hash mismatch",
func() {
header, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
consensusState := &ibctm.ConsensusState{
Timestamp: header.GetTime(),
Root: commitmenttypes.NewMerkleRoot([]byte{}), // empty bytes
NextValidatorsHash: header.Header.NextValidatorsHash,
}
tmHeader, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState)
},
true,
},
{
"previous consensus state exists and header time is before previous consensus state time",
func() {
header, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
// offset header timestamp before previous consensus state timestamp
header.Header.Time = header.GetTime().Add(-time.Hour)
},
true,
},
{
"next consensus state exists and header time is after next consensus state time",
func() {
header, ok := clientMessage.(*ibctm.Header)
suite.Require().True(ok)
// commit block and update client, adding a new consensus state
suite.coordinator.CommitBlock(suite.chainB)
err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
// increase timestamp of current header
header.Header.Time = header.Header.Time.Add(time.Hour)
},
true,
},
{
"valid fork misbehaviour returns true",
func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
header1, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
// commit block and update client
suite.coordinator.CommitBlock(suite.chainB)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)
trustedHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
header2, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
// assign the same height, each header will have a different commit hash
header1.Header.Height = header2.Header.Height
clientMessage = &ibctm.Misbehaviour{
Header1: header1,
Header2: header2,
ClientId: path.EndpointA.ClientID,
}
},
true,
},
{
"valid time misbehaviour: not monotonically increasing time", func() {
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1]
suite.Require().True(ok)
clientMessage = &ibctm.Misbehaviour{
Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers),
Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers),
}
}, true,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// reset suite to create fresh application state
suite.SetupTest()
path = ibctesting.NewPath(suite.chainA, suite.chainB)
err := path.EndpointA.CreateClient()
suite.Require().NoError(err)
// ensure counterparty state is committed
suite.coordinator.CommitBlock(suite.chainB)
trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height)
suite.Require().True(ok)
clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight)
suite.Require().NoError(err)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().NoError(err)
tc.malleate()
foundMisbehaviour := lightClientModule.CheckForMisbehaviour(
suite.chainA.GetContext(),
path.EndpointA.ClientID,
clientMessage,
)
if tc.expPass {
suite.Require().True(foundMisbehaviour)
} else {
suite.Require().False(foundMisbehaviour)
}
})
}
}
func (suite *TendermintTestSuite) TestUpdateStateOnMisbehaviour() {
var path *ibctesting.Path
testCases := []struct {
name string
malleate func()
expPass bool
}{
{
"success",
func() {},
true,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// reset suite to create fresh application state
suite.SetupTest()
path = ibctesting.NewPath(suite.chainA, suite.chainB)
err := path.EndpointA.CreateClient()
suite.Require().NoError(err)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().NoError(err)
tc.malleate()
clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID)
lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), path.EndpointA.ClientID, nil)
if tc.expPass {
clientStateBz := clientStore.Get(host.ClientStateKey())
suite.Require().NotEmpty(clientStateBz)
newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz)
suite.Require().Equal(frozenHeight, newClientState.(*ibctm.ClientState).FrozenHeight)
}
})
}
}