diff --git a/modules/light-clients/07-tendermint/client_state.go b/modules/light-clients/07-tendermint/client_state.go index d0a1d10e30e..daa14c9e0a1 100644 --- a/modules/light-clients/07-tendermint/client_state.go +++ b/modules/light-clients/07-tendermint/client_state.go @@ -123,7 +123,7 @@ func (cs ClientState) Validate() error { } if err := light.ValidateTrustLevel(cs.TrustLevel.ToTendermint()); err != nil { - return err + return errorsmod.Wrapf(ErrInvalidTrustLevel, err.Error()) } if cs.TrustingPeriod <= 0 { return errorsmod.Wrap(ErrInvalidTrustingPeriod, "trusting period must be greater than zero") @@ -303,7 +303,6 @@ func verifyDelayPeriodPassed(ctx sdk.Context, store storetypes.KVStore, proofHei return errorsmod.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until time: %d, current time: %d", validTime, currentTimestamp) } - } if delayBlockPeriod != 0 { diff --git a/modules/light-clients/07-tendermint/client_state_test.go b/modules/light-clients/07-tendermint/client_state_test.go index 0fa2222de6e..49bd513fd77 100644 --- a/modules/light-clients/07-tendermint/client_state_test.go +++ b/modules/light-clients/07-tendermint/client_state_test.go @@ -1,21 +1,11 @@ package tendermint_test import ( - "time" - ics23 "github.com/cosmos/ics23/go" - sdk "github.com/cosmos/cosmos-sdk/types" - - transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v8/testing" - ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" ) const ( @@ -26,741 +16,121 @@ const ( var invalidProof = []byte("invalid proof") -func (suite *TendermintTestSuite) TestStatus() { - var ( - path *ibctesting.Path - clientState *ibctm.ClientState - ) - - testCases := []struct { - name string - malleate func() - expStatus exported.Status - }{ - {"client is active", func() {}, exported.Active}, - {"client is frozen", func() { - clientState.FrozenHeight = clienttypes.NewHeight(0, 1) - path.EndpointA.SetClientState(clientState) - }, exported.Frozen}, - {"client status without consensus state", func() { - clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) - path.EndpointA.SetClientState(clientState) - }, exported.Expired}, - {"client status is expired", func() { - suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) - }, exported.Expired}, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupClients() - - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) - clientState = path.EndpointA.GetClientState().(*ibctm.ClientState) - - tc.malleate() - - status := clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) - suite.Require().Equal(tc.expStatus, status) - }) - - } -} - -func (suite *TendermintTestSuite) TestGetTimestampAtHeight() { - var ( - path *ibctesting.Path - height exported.Height - ) - expectedTimestamp := time.Unix(1, 0) - - testCases := []struct { - name string - malleate func() - expErr error - }{ - { - "success", - func() {}, - nil, - }, - { - "failure: consensus state not found for height", - func() { - clientState := path.EndpointA.GetClientState().(*ibctm.ClientState) - height = clientState.LatestHeight.Increment() - }, - clienttypes.ErrConsensusStateNotFound, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() - - path = ibctesting.NewPath(suite.chainA, suite.chainB) - path.SetupClients() - - clientState := path.EndpointA.GetClientState().(*ibctm.ClientState) - height = clientState.LatestHeight - - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) - - // grab consensusState from store and update with a predefined timestamp - consensusState := path.EndpointA.GetConsensusState(height) - tmConsensusState, ok := consensusState.(*ibctm.ConsensusState) - suite.Require().True(ok) - - tmConsensusState.Timestamp = expectedTimestamp - path.EndpointA.SetConsensusState(tmConsensusState, height) - - tc.malleate() - - timestamp, err := clientState.GetTimestampAtHeight(suite.chainA.GetContext(), store, suite.chainA.Codec, height) - - expPass := tc.expErr == nil - if expPass { - suite.Require().NoError(err) - - expectedTimestamp := uint64(expectedTimestamp.UnixNano()) - suite.Require().Equal(expectedTimestamp, timestamp) - } else { - suite.Require().ErrorIs(err, tc.expErr) - } - }) - } -} - func (suite *TendermintTestSuite) TestValidate() { testCases := []struct { name string clientState *ibctm.ClientState + expErr error expPass bool }{ { name: "valid client", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: true, + expErr: nil, }, { name: "valid client with nil upgrade path", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil), - expPass: true, + expErr: nil, }, { name: "invalid chainID", clientState: ibctm.NewClientState(" ", ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidChainID, }, { // NOTE: if this test fails, the code must account for the change in chainID length across tendermint versions! // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 - name: "valid chainID - chainID validation failed for chainID of length 50! ", + name: "valid chainID - chainID validation did not fail for chainID of length 50! ", clientState: ibctm.NewClientState(fiftyCharChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: true, + expErr: nil, }, { // NOTE: if this test fails, the code must account for the change in chainID length across tendermint versions! // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 - name: "invalid chainID - chainID validation did not fail for chainID of length 51! ", + name: "invalid chainID - chainID validation failed for chainID of length 51! ", clientState: ibctm.NewClientState(fiftyOneCharChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidChainID, }, { name: "invalid trust level", clientState: ibctm.NewClientState(chainID, ibctm.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidTrustLevel, }, { name: "invalid zero trusting period", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidTrustingPeriod, }, { name: "invalid negative trusting period", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, -1, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidTrustingPeriod, }, { name: "invalid zero unbonding period", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidUnbondingPeriod, }, { name: "invalid negative unbonding period", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, -1, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidUnbondingPeriod, }, { name: "invalid zero max clock drift", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidMaxClockDrift, }, { name: "invalid negative max clock drift", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, -1, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidMaxClockDrift, }, { name: "invalid revision number", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidHeaderHeight, }, { name: "invalid revision height", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidHeaderHeight, }, { name: "trusting period not less than unbonding period", clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), - expPass: false, + expErr: ibctm.ErrInvalidTrustingPeriod, }, { name: "proof specs is nil", - clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil, upgradePath), - expPass: false, + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, nil, upgradePath), + expErr: ibctm.ErrInvalidProofSpecs, }, { name: "proof specs contains nil", - clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath), - expPass: false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - err := tc.clientState.Validate() - if tc.expPass { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Error(err, tc.name) - } - }) - } -} - -func (suite *TendermintTestSuite) TestInitialize() { - testCases := []struct { - name string - consensusState exported.ConsensusState - expPass bool - }{ - { - name: "valid consensus", - consensusState: &ibctm.ConsensusState{}, - expPass: true, - }, - { - name: "invalid consensus: consensus state is solomachine consensus", - consensusState: ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState(), - expPass: false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() - path := ibctesting.NewPath(suite.chainA, suite.chainB) - - tmConfig, ok := path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig) - suite.Require().True(ok) - - clientState := ibctm.NewClientState( - path.EndpointB.Chain.ChainID, - tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - suite.chainB.LatestCommittedHeader.GetTrustedHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, - ) - - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) - err := clientState.Initialize(suite.chainA.GetContext(), suite.chainA.Codec, store, tc.consensusState) - - if tc.expPass { - suite.Require().NoError(err, "valid case returned an error") - suite.Require().True(store.Has(host.ClientStateKey())) - suite.Require().True(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetTrustedHeight()))) - } else { - suite.Require().Error(err, "invalid case didn't return an error") - suite.Require().False(store.Has(host.ClientStateKey())) - suite.Require().False(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetTrustedHeight()))) - } - }) - } -} - -func (suite *TendermintTestSuite) TestVerifyMembership() { - var ( - testingpath *ibctesting.Path - delayTimePeriod uint64 - delayBlockPeriod uint64 - err error - proofHeight exported.Height - proof []byte - path exported.Path - value []byte - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "successful ClientState verification", - func() { - // default proof construction uses ClientState - }, - true, - }, - { - "successful ConsensusState verification", func() { - latestHeight := testingpath.EndpointB.GetClientLatestHeight() - - key := host.FullConsensusStateKey(testingpath.EndpointB.ClientID, latestHeight) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - - consensusState := testingpath.EndpointB.GetConsensusState(latestHeight).(*ibctm.ConsensusState) - value, err = suite.chainB.Codec.MarshalInterface(consensusState) - suite.Require().NoError(err) - }, - true, - }, - { - "successful Connection verification", func() { - key := host.ConnectionKey(testingpath.EndpointB.ConnectionID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - - connection := testingpath.EndpointB.GetConnection() - value, err = suite.chainB.Codec.Marshal(&connection) - suite.Require().NoError(err) - }, - true, - }, - { - "successful Channel verification", func() { - key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - - channel := testingpath.EndpointB.GetChannel() - value, err = suite.chainB.Codec.Marshal(&channel) - suite.Require().NoError(err) - }, - true, - }, - { - "successful PacketCommitment verification", func() { - // send from chainB to chainA since we are proving chainB sent a packet - sequence, err := testingpath.EndpointB.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) - suite.Require().NoError(err) - - // make packet commitment proof - packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, clienttypes.NewHeight(1, 100), 0) - key := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - - value = channeltypes.CommitPacket(suite.chainA.App.GetIBCKeeper().Codec(), packet) - }, true, - }, - { - "successful Acknowledgement verification", func() { - // send from chainA to chainB since we are proving chainB wrote an acknowledgement - sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) - suite.Require().NoError(err) - - // write receipt and ack - packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) - err = testingpath.EndpointB.RecvPacket(packet) - suite.Require().NoError(err) - - key := host.PacketAcknowledgementKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - - value = channeltypes.CommitAcknowledgement(ibcmock.MockAcknowledgement.Acknowledgement()) - }, - true, - }, - { - "successful NextSequenceRecv verification", func() { - // send from chainA to chainB since we are proving chainB incremented the sequence recv - - // send packet - sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) - suite.Require().NoError(err) - - // next seq recv incremented - packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) - err = testingpath.EndpointB.RecvPacket(packet) - suite.Require().NoError(err) - - key := host.NextSequenceRecvKey(packet.GetSourcePort(), packet.GetSourceChannel()) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - - value = sdk.Uint64ToBigEndian(packet.GetSequence() + 1) - }, - true, - }, - { - "successful verification outside IBC store", func() { - key := transfertypes.PortKey - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) - - value = []byte(suite.chainB.GetSimApp().TransferKeeper.GetPort(suite.chainB.GetContext())) - suite.Require().NoError(err) - }, - true, - }, - { - "delay time period has passed", func() { - delayTimePeriod = uint64(time.Second.Nanoseconds()) - }, - true, - }, - { - "delay time period has not passed", func() { - delayTimePeriod = uint64(time.Hour.Nanoseconds()) - }, - false, - }, - { - "delay block period has passed", func() { - delayBlockPeriod = 1 - }, - true, - }, - { - "delay block period has not passed", func() { - delayBlockPeriod = 1000 - }, - false, - }, - { - "latest client height < height", func() { - proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() - }, false, - }, - { - "invalid path type", - func() { - path = ibcmock.KeyPath{} - }, - false, - }, - { - "failed to unmarshal merkle proof", func() { - proof = invalidProof - }, false, - }, - { - "consensus state not found", func() { - proofHeight = clienttypes.ZeroHeight() - }, false, + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath), + expErr: ibctm.ErrInvalidProofSpecs, }, { - "proof verification failed", func() { - // change the value being proved - value = []byte("invalid value") - }, false, - }, - { - "proof is empty", func() { - // change the inserted proof - proof = []byte{} - }, false, + name: "invalid upgrade path", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), invalidUpgradePath), + expErr: clienttypes.ErrInvalidClient, }, } for _, tc := range testCases { tc := tc - suite.Run(tc.name, func() { - suite.SetupTest() // reset - testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) - testingpath.SetChannelOrdered() - testingpath.Setup() - - // reset time and block delays to 0, malleate may change to a specific non-zero value. - delayTimePeriod = 0 - delayBlockPeriod = 0 - - // create default proof, merklePath, and value which passes - // may be overwritten by malleate() - key := host.FullClientStateKey(testingpath.EndpointB.ClientID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - - clientState := testingpath.EndpointB.GetClientState().(*ibctm.ClientState) - value, err = suite.chainB.Codec.MarshalInterface(clientState) - suite.Require().NoError(err) - - tc.malleate() // make changes as necessary - - clientState = testingpath.EndpointA.GetClientState().(*ibctm.ClientState) - - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, testingpath.EndpointA.ClientID) - - err = clientState.VerifyMembership( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, - proof, path, value, - ) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *TendermintTestSuite) TestVerifyNonMembership() { - var ( - testingpath *ibctesting.Path - delayTimePeriod uint64 - delayBlockPeriod uint64 - err error - proofHeight exported.Height - path exported.Path - proof []byte - invalidClientID = "09-tendermint" - invalidConnectionID = "connection-100" - invalidChannelID = "channel-800" - invalidPortID = "invalid-port" - ) - - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "successful ClientState verification of non membership", - func() { - // default proof construction uses ClientState - }, - true, - }, - { - "successful ConsensusState verification of non membership", func() { - key := host.FullConsensusStateKey(invalidClientID, testingpath.EndpointB.GetClientLatestHeight()) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - }, - true, - }, - { - "successful Connection verification of non membership", func() { - key := host.ConnectionKey(invalidConnectionID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - }, - true, - }, - { - "successful Channel verification of non membership", func() { - key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, invalidChannelID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - }, - true, - }, - { - "successful PacketCommitment verification of non membership", func() { - // make packet commitment proof - key := host.PacketCommitmentKey(invalidPortID, invalidChannelID, 1) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - }, true, - }, - { - "successful Acknowledgement verification of non membership", func() { - key := host.PacketAcknowledgementKey(invalidPortID, invalidChannelID, 1) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - }, - true, - }, - { - "successful NextSequenceRecv verification of non membership", func() { - key := host.NextSequenceRecvKey(invalidPortID, invalidChannelID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = testingpath.EndpointB.QueryProof(key) - }, - true, - }, - { - "successful verification of non membership outside IBC store", func() { - key := []byte{0x08} - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) - }, - true, - }, - { - "delay time period has passed", func() { - delayTimePeriod = uint64(time.Second.Nanoseconds()) - }, - true, - }, - { - "delay time period has not passed", func() { - delayTimePeriod = uint64(time.Hour.Nanoseconds()) - }, - false, - }, - { - "delay block period has passed", func() { - delayBlockPeriod = 1 - }, - true, - }, - { - "delay block period has not passed", func() { - delayBlockPeriod = 1000 - }, - false, - }, - { - "latest client height < height", func() { - proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() - }, false, - }, - { - "invalid path type", - func() { - path = ibcmock.KeyPath{} - }, - false, - }, - { - "failed to unmarshal merkle proof", func() { - proof = invalidProof - }, false, - }, - { - "consensus state not found", func() { - proofHeight = clienttypes.ZeroHeight() - }, false, - }, - { - "verify non membership fails as path exists", func() { - // change the value being proved - key := host.FullClientStateKey(testingpath.EndpointB.ClientID) - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - }, false, - }, - { - "proof is empty", func() { - // change the inserted proof - proof = []byte{} - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() // reset - testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) - testingpath.SetChannelOrdered() - testingpath.Setup() - - // reset time and block delays to 0, malleate may change to a specific non-zero value. - delayTimePeriod = 0 - delayBlockPeriod = 0 - - // create default proof, merklePath, and value which passes - // may be overwritten by malleate() - key := host.FullClientStateKey("invalid-client-id") - - merklePath := commitmenttypes.NewMerklePath(string(key)) - path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) - suite.Require().NoError(err) - - proof, proofHeight = suite.chainB.QueryProof(key) - - tc.malleate() // make changes as necessary - - clientState := testingpath.EndpointA.GetClientState().(*ibctm.ClientState) - - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, testingpath.EndpointA.ClientID) - - err = clientState.VerifyNonMembership( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, - proof, path, - ) + err := tc.clientState.Validate() - if tc.expPass { - suite.Require().NoError(err) + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err, tc.name) } else { - suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) } }) } diff --git a/modules/light-clients/07-tendermint/errors.go b/modules/light-clients/07-tendermint/errors.go index 8bbb58ec33a..11c2cc0c66d 100644 --- a/modules/light-clients/07-tendermint/errors.go +++ b/modules/light-clients/07-tendermint/errors.go @@ -19,4 +19,5 @@ var ( ErrUnbondingPeriodExpired = errorsmod.Register(ModuleName, 12, "time since latest trusted state has passed the unbonding period") ErrInvalidProofSpecs = errorsmod.Register(ModuleName, 13, "invalid proof specs") ErrInvalidValidatorSet = errorsmod.Register(ModuleName, 14, "invalid validator set") + ErrInvalidTrustLevel = errorsmod.Register(ModuleName, 15, "invalid trust level") ) diff --git a/modules/light-clients/07-tendermint/light_client_module.go b/modules/light-clients/07-tendermint/light_client_module.go index 46e52eec621..9a66b8d6e75 100644 --- a/modules/light-clients/07-tendermint/light_client_module.go +++ b/modules/light-clients/07-tendermint/light_client_module.go @@ -1,6 +1,8 @@ package tendermint import ( + "fmt" + errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/codec" @@ -41,7 +43,7 @@ func (l *LightClientModule) RegisterStoreProvider(storeProvider exported.ClientS func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientStateBz, consensusStateBz []byte) error { var clientState ClientState if err := l.keeper.Codec().Unmarshal(clientStateBz, &clientState); err != nil { - return err + return fmt.Errorf("failed to unmarshal client state bytes into client state: %w", err) } if err := clientState.Validate(); err != nil { @@ -50,7 +52,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt var consensusState ConsensusState if err := l.keeper.Codec().Unmarshal(consensusStateBz, &consensusState); err != nil { - return err + return fmt.Errorf("failed to unmarshal consensus state bytes into consensus state: %w", err) } if err := consensusState.ValidateBasic(); err != nil { diff --git a/modules/light-clients/07-tendermint/light_client_module_test.go b/modules/light-clients/07-tendermint/light_client_module_test.go index fef8763a836..db1a9ea90d3 100644 --- a/modules/light-clients/07-tendermint/light_client_module_test.go +++ b/modules/light-clients/07-tendermint/light_client_module_test.go @@ -2,19 +2,24 @@ package tendermint_test import ( "crypto/sha256" + "fmt" "time" upgradetypes "cosmossdk.io/x/upgrade/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ibctesting "github.com/cosmos/ibc-go/v8/testing" + ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" ) var ( @@ -22,6 +27,240 @@ var ( solomachineClientID = clienttypes.FormatClientIdentifier(exported.Solomachine, 0) ) +func (suite *TendermintTestSuite) TestStatus() { + var ( + path *ibctesting.Path + clientState *ibctm.ClientState + ) + + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, + exported.Frozen, + }, + { + "client status without consensus state", + func() { + clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) + path.EndpointA.SetClientState(clientState) + }, + exported.Expired, + }, + { + "client status is expired", + func() { + suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) + }, + exported.Expired, + }, + { + "client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + exported.Unknown, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(path.EndpointA.ClientID) + suite.Require().True(found) + + clientState = path.EndpointA.GetClientState().(*ibctm.ClientState) + + tc.malleate() + + status := lightClientModule.Status(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().Equal(tc.expStatus, status) + }) + + } +} + +func (suite *TendermintTestSuite) TestGetTimestampAtHeight() { + var ( + path *ibctesting.Path + height exported.Height + ) + expectedTimestamp := time.Unix(1, 0) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client state not found for height", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: consensus state not found for height", + func() { + clientState := path.EndpointA.GetClientState().(*ibctm.ClientState) + height = clientState.LatestHeight.Increment() + }, + clienttypes.ErrConsensusStateNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + clientState := path.EndpointA.GetClientState().(*ibctm.ClientState) + height = clientState.LatestHeight + + // grab consensusState from store and update with a predefined timestamp + consensusState := path.EndpointA.GetConsensusState(height) + tmConsensusState, ok := consensusState.(*ibctm.ConsensusState) + suite.Require().True(ok) + + tmConsensusState.Timestamp = expectedTimestamp + path.EndpointA.SetConsensusState(tmConsensusState, height) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(path.EndpointA.ClientID) + suite.Require().True(found) + + tc.malleate() + + timestamp, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), path.EndpointA.ClientID, height) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + + expectedTimestamp := uint64(expectedTimestamp.UnixNano()) + suite.Require().Equal(expectedTimestamp, timestamp) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TendermintTestSuite) TestInitialize() { + var consensusState exported.ConsensusState + var clientState exported.ClientState + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "valid consensus & client states", + func() {}, + nil, + }, + { + "invalid client state", + func() { + clientState.(*ibctm.ClientState).ChainId = "" + }, + ibctm.ErrInvalidChainID, + }, + { + "invalid client state: solomachine client state", + func() { + clientState = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ClientState() + }, + fmt.Errorf("failed to unmarshal client state bytes into client state"), + }, + { + "invalid consensus: consensus state is solomachine consensus", + func() { + consensusState = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState() + }, + fmt.Errorf("failed to unmarshal consensus state bytes into consensus state"), + }, + { + "invalid consensus state", + func() { + consensusState = ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), []byte("invalidNextValsHash")) + }, + fmt.Errorf("next validators hash is invalid"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + tmConfig, ok := path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig) + suite.Require().True(ok) + + clientState = ibctm.NewClientState( + path.EndpointA.Chain.ChainID, + tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + suite.chainA.LatestCommittedHeader.GetHeight().(clienttypes.Height), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, + ) + + consensusState = ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), suite.chainA.ProposedHeader.ValidatorsHash) + + clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), clientState.ClientType()) + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID) + suite.Require().True(found) + + tc.malleate() + + clientStateBz := suite.chainA.Codec.MustMarshal(clientState) + consStateBz := suite.chainA.Codec.MustMarshal(consensusState) + + err := lightClientModule.Initialize(suite.chainA.GetContext(), path.EndpointA.ClientID, clientStateBz, consStateBz) + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err, "valid case returned an error") + suite.Require().True(store.Has(host.ClientStateKey())) + suite.Require().True(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetHeight()))) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + suite.Require().False(store.Has(host.ClientStateKey())) + suite.Require().False(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetHeight()))) + } + }) + } +} + func (suite *TendermintTestSuite) TestRecoverClient() { var ( subjectClientID, substituteClientID string @@ -276,3 +515,496 @@ func (suite *TendermintTestSuite) TestVerifyUpgradeAndUpdateState() { }) } } + +func (suite *TendermintTestSuite) TestVerifyMembership() { + var ( + testingpath *ibctesting.Path + delayTimePeriod uint64 + delayBlockPeriod uint64 + err error + proofHeight exported.Height + proof []byte + path exported.Path + value []byte + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "successful ClientState verification", + func() { + // default proof construction uses ClientState + }, + nil, + }, + { + "successful ConsensusState verification", func() { + latestHeight := testingpath.EndpointB.GetClientLatestHeight() + + key := host.FullConsensusStateKey(testingpath.EndpointB.ClientID, latestHeight) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + consensusState := testingpath.EndpointB.GetConsensusState(latestHeight).(*ibctm.ConsensusState) + value, err = suite.chainB.Codec.MarshalInterface(consensusState) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful Connection verification", func() { + key := host.ConnectionKey(testingpath.EndpointB.ConnectionID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + connection := testingpath.EndpointB.GetConnection() + value, err = suite.chainB.Codec.Marshal(&connection) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful Channel verification", func() { + key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + channel := testingpath.EndpointB.GetChannel() + value, err = suite.chainB.Codec.Marshal(&channel) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful PacketCommitment verification", func() { + // send from chainB to chainA since we are proving chainB sent a packet + sequence, err := testingpath.EndpointB.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // make packet commitment proof + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, clienttypes.NewHeight(1, 100), 0) + key := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = channeltypes.CommitPacket(suite.chainA.App.GetIBCKeeper().Codec(), packet) + }, nil, + }, + { + "successful Acknowledgement verification", func() { + // send from chainA to chainB since we are proving chainB wrote an acknowledgement + sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // write receipt and ack + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) + err = testingpath.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + key := host.PacketAcknowledgementKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = channeltypes.CommitAcknowledgement(ibcmock.MockAcknowledgement.Acknowledgement()) + }, + nil, + }, + { + "successful NextSequenceRecv verification", func() { + // send from chainA to chainB since we are proving chainB incremented the sequence recv + + // send packet + sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // next seq recv incremented + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) + err = testingpath.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + key := host.NextSequenceRecvKey(packet.GetSourcePort(), packet.GetSourceChannel()) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = sdk.Uint64ToBigEndian(packet.GetSequence() + 1) + }, + nil, + }, + { + "successful verification outside IBC store", func() { + key := transfertypes.PortKey + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) + + value = []byte(suite.chainB.GetSimApp().TransferKeeper.GetPort(suite.chainB.GetContext())) + suite.Require().NoError(err) + }, + nil, + }, + { + "delay time period has passed", func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + nil, + }, + { + "delay time period has not passed", func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "delay block period has passed", func() { + delayBlockPeriod = 1 + }, + nil, + }, + { + "delay block period has not passed", func() { + delayBlockPeriod = 1000 + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "latest client height < height", func() { + proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failed to unmarshal merkle proof", func() { + proof = invalidProof + }, + commitmenttypes.ErrInvalidProof, + }, + { + "consensus state not found", func() { + proofHeight = clienttypes.ZeroHeight() + }, + clienttypes.ErrConsensusStateNotFound, + }, + { + "proof verification failed", func() { + // change the value being proved + value = []byte("invalid value") + }, + commitmenttypes.ErrInvalidProof, + }, + { + "proof is empty", func() { + // change the inserted proof + proof = []byte{} + }, + commitmenttypes.ErrInvalidMerkleProof, + }, + { + "client state not found for height", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) + testingpath.SetChannelOrdered() + testingpath.Setup() + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + + // create default proof, merklePath, and value which passes + // may be overwritten by malleate() + key := host.FullClientStateKey(testingpath.EndpointB.ClientID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + clientState := testingpath.EndpointB.GetClientState().(*ibctm.ClientState) + value, err = suite.chainB.Codec.MarshalInterface(clientState) + suite.Require().NoError(err) + + tc.malleate() // make changes as necessary + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(testingpath.EndpointA.ClientID) + suite.Require().True(found) + + err = lightClientModule.VerifyMembership( + suite.chainA.GetContext(), testingpath.EndpointA.ClientID, proofHeight, delayTimePeriod, delayBlockPeriod, + proof, path, value, + ) + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +func (suite *TendermintTestSuite) TestVerifyNonMembership() { + var ( + testingpath *ibctesting.Path + delayTimePeriod uint64 + delayBlockPeriod uint64 + err error + proofHeight exported.Height + path exported.Path + proof []byte + invalidClientID = "09-tendermint" + invalidConnectionID = "connection-100" + invalidChannelID = "channel-800" + invalidPortID = "invalid-port" + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "successful ClientState verification of non membership", + func() { + // default proof construction uses ClientState + }, + nil, + }, + { + "successful ConsensusState verification of non membership", func() { + key := host.FullConsensusStateKey(invalidClientID, testingpath.EndpointB.GetClientLatestHeight()) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful Connection verification of non membership", func() { + key := host.ConnectionKey(invalidConnectionID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful Channel verification of non membership", func() { + key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, invalidChannelID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful PacketCommitment verification of non membership", func() { + // make packet commitment proof + key := host.PacketCommitmentKey(invalidPortID, invalidChannelID, 1) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful Acknowledgement verification of non membership", func() { + key := host.PacketAcknowledgementKey(invalidPortID, invalidChannelID, 1) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful NextSequenceRecv verification of non membership", func() { + key := host.NextSequenceRecvKey(invalidPortID, invalidChannelID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful verification of non membership outside IBC store", func() { + key := []byte{0x08} + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) + }, + nil, + }, + { + "delay time period has passed", func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + nil, + }, + { + "delay time period has not passed", func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "delay block period has passed", func() { + delayBlockPeriod = 1 + }, + nil, + }, + { + "delay block period has not passed", func() { + delayBlockPeriod = 1000 + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "latest client height < height", func() { + proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failed to unmarshal merkle proof", func() { + proof = invalidProof + }, + commitmenttypes.ErrInvalidProof, + }, + { + "consensus state not found", func() { + proofHeight = clienttypes.ZeroHeight() + }, + clienttypes.ErrConsensusStateNotFound, + }, + { + "verify non membership fails as path exists", func() { + // change the value being proved + key := host.FullClientStateKey(testingpath.EndpointB.ClientID) + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + commitmenttypes.ErrInvalidProof, + }, + { + "proof is empty", func() { + // change the inserted proof + proof = []byte{} + }, + commitmenttypes.ErrInvalidMerkleProof, + }, + { + "client state not found for height", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) + testingpath.SetChannelOrdered() + testingpath.Setup() + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + + // create default proof, merklePath, and value which passes + // may be overwritten by malleate() + key := host.FullClientStateKey("invalid-client-id") + + merklePath := commitmenttypes.NewMerklePath(string(key)) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + tc.malleate() // make changes as necessary + + lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(testingpath.EndpointA.ClientID) + suite.Require().True(found) + + err = lightClientModule.VerifyNonMembership( + suite.chainA.GetContext(), testingpath.EndpointA.ClientID, proofHeight, delayTimePeriod, delayBlockPeriod, + proof, path, + ) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/tendermint_test.go b/modules/light-clients/07-tendermint/tendermint_test.go index 5dc7ec722a2..b39162b930d 100644 --- a/modules/light-clients/07-tendermint/tendermint_test.go +++ b/modules/light-clients/07-tendermint/tendermint_test.go @@ -29,9 +29,10 @@ const ( ) var ( - height = clienttypes.NewHeight(0, 4) - newClientHeight = clienttypes.NewHeight(1, 1) - upgradePath = []string{"upgrade", "upgradedIBCState"} + height = clienttypes.NewHeight(0, 4) + newClientHeight = clienttypes.NewHeight(1, 1) + upgradePath = []string{"upgrade", "upgradedIBCState"} + invalidUpgradePath = []string{"upgrade", ""} ) type TendermintTestSuite struct {