diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad0001a09d..99b12c62a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (modules/core) [\#184](https://github.com/cosmos/ibc-go/pull/184) Improve error messages. Uses unique error codes to indicate already relayed packets. * (07-tendermint) [\#182](https://github.com/cosmos/ibc-go/pull/182) Remove duplicate checks in upgrade logic. * (modules/core/04-channel) [\#7949](https://github.com/cosmos/cosmos-sdk/issues/7949) Standardized channel `Acknowledgement` moved to its own file. Codec registration redundancy removed. * (modules/core/04-channel) [\#144](https://github.com/cosmos/ibc-go/pull/144) Introduced a `packet_data_hex` attribute to emit the hex-encoded packet data in events. This allows for raw binary (proto-encoded message) to be sent over events and decoded correctly on relayer. Original `packet_data` is DEPRECATED. All relayers and IBC event consumers are encouraged to switch to `packet_data_hex` as soon as possible. diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index 78755d10c82..f917353aa96 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -241,8 +241,8 @@ func (k Keeper) RecvPacket( _, found := k.GetPacketReceipt(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) if found { return sdkerrors.Wrapf( - types.ErrInvalidPacket, - "packet sequence (%d) already has been received", packet.GetSequence(), + types.ErrPacketReceived, + "packet sequence (%d)", packet.GetSequence(), ) } @@ -262,9 +262,17 @@ func (k Keeper) RecvPacket( ) } + // helpful error message for relayers + if packet.GetSequence() < nextSequenceRecv { + return sdkerrors.Wrapf( + types.ErrPacketReceived, + "packet sequence (%d), next sequence receive (%d)", packet.GetSequence(), nextSequenceRecv, + ) + } + if packet.GetSequence() != nextSequenceRecv { return sdkerrors.Wrapf( - types.ErrInvalidPacket, + types.ErrPacketSequenceOutOfOrder, "packet sequence ≠ next receive sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceRecv, ) } @@ -462,6 +470,10 @@ func (k Keeper) AcknowledgePacket( commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if len(commitment) == 0 { + return sdkerrors.Wrapf(types.ErrPacketCommitmentNotFound, "packet with sequence (%d) has been acknowledged, or timed out. In rare cases the packet was never sent or the packet sequence is incorrect", packet.GetSequence()) + } + packetCommitment := types.CommitPacket(k.cdc, packet) // verify we sent the packet and haven't cleared it out yet @@ -473,7 +485,7 @@ func (k Keeper) AcknowledgePacket( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), acknowledgement, ); err != nil { - return sdkerrors.Wrap(err, "packet acknowledgement verification failed") + return err } // assert packets acknowledged in order @@ -488,7 +500,7 @@ func (k Keeper) AcknowledgePacket( if packet.GetSequence() != nextSequenceAck { return sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, + types.ErrPacketSequenceOutOfOrder, "packet sequence ≠ next ack sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceAck, ) } diff --git a/modules/core/04-channel/keeper/packet_test.go b/modules/core/04-channel/keeper/packet_test.go index 4916ae9d995..91ddc7d15d6 100644 --- a/modules/core/04-channel/keeper/packet_test.go +++ b/modules/core/04-channel/keeper/packet_test.go @@ -1,10 +1,14 @@ package keeper_test import ( + "errors" "fmt" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/modules/core/03-connection/types" "github.com/cosmos/ibc-go/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/modules/core/24-host" "github.com/cosmos/ibc-go/modules/core/exported" @@ -200,6 +204,7 @@ func (suite *KeeperTestSuite) TestRecvPacket() { path *ibctesting.Path packet exported.PacketI channelCap *capabilitytypes.Capability + expError *sdkerrors.Error ) testCases := []testCase{ @@ -235,7 +240,36 @@ func (suite *KeeperTestSuite) TestRecvPacket() { // attempts to receive packet 2 without receiving packet 1 channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, true}, + {"packet already relayed ORDERED channel", func() { + expError = types.ErrPacketReceived + + path.SetChannelOrdered() + suite.coordinator.Setup(path) + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + err = path.EndpointB.RecvPacket(packet.(types.Packet)) + suite.Require().NoError(err) + }, false}, + {"packet already relayed UNORDERED channel", func() { + expError = types.ErrPacketReceived + + // setup uses an UNORDERED channel + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + err = path.EndpointB.RecvPacket(packet.(types.Packet)) + suite.Require().NoError(err) + }, false}, {"out of order packet failure with ORDERED channel", func() { + expError = types.ErrPacketSequenceOutOfOrder + path.SetChannelOrdered() suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -251,12 +285,16 @@ func (suite *KeeperTestSuite) TestRecvPacket() { channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"channel not found", func() { + expError = types.ErrChannelNotFound + // use wrong channel naming suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, ibctesting.InvalidID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"channel not open", func() { + expError = types.ErrInvalidChannelState + suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -265,26 +303,36 @@ func (suite *KeeperTestSuite) TestRecvPacket() { channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"capability cannot authenticate ORDERED", func() { + expError = types.ErrInvalidChannelCapability + path.SetChannelOrdered() suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) err := path.EndpointA.SendPacket(packet) suite.Require().NoError(err) channelCap = capabilitytypes.NewCapability(3) }, false}, {"packet source port ≠ channel counterparty port", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) + // use wrong port for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"packet source channel ID ≠ channel counterparty channel ID", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) + // use wrong port for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"connection not found", func() { + expError = connectiontypes.ErrConnectionNotFound + suite.coordinator.Setup(path) + // pass channel check suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( suite.chainB.GetContext(), @@ -296,6 +344,7 @@ func (suite *KeeperTestSuite) TestRecvPacket() { channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"connection not OPEN", func() { + expError = connectiontypes.ErrInvalidConnectionState suite.coordinator.SetupClients(path) // connection on chainB is in INIT @@ -313,19 +362,26 @@ func (suite *KeeperTestSuite) TestRecvPacket() { channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"timeout height passed", func() { + expError = types.ErrPacketTimeout suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), disabledTimeoutTimestamp) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"timeout timestamp passed", func() { + expError = types.ErrPacketTimeout suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, disabledTimeoutHeight, uint64(suite.chainB.GetContext().BlockTime().UnixNano())) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"next receive sequence is not found", func() { - path := ibctesting.NewPath(suite.chainA, suite.chainB) + expError = types.ErrSequenceReceiveNotFound suite.coordinator.SetupConnections(path) + path.EndpointA.ChannelID = ibctesting.FirstChannelID + path.EndpointB.ChannelID = ibctesting.FirstChannelID + // manually creating channel prevents next recv sequence from being set suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( suite.chainB.GetContext(), @@ -336,19 +392,26 @@ func (suite *KeeperTestSuite) TestRecvPacket() { packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) // manually set packet commitment - suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence(), ibctesting.MockPacketData) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence(), types.CommitPacket(suite.chainA.App.AppCodec(), packet)) suite.chainB.CreateChannelCapability(suite.chainB.GetSimApp().ScopedIBCMockKeeper, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + path.EndpointA.UpdateClient() + path.EndpointB.UpdateClient() }, false}, {"receipt already stored", func() { + expError = types.ErrPacketReceived suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) path.EndpointA.SendPacket(packet) suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, 1) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"validation failed", func() { + // skip error code check, downstream error code is used from light-client implementations + // packet commitment not set resulting in invalid proof suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -360,13 +423,14 @@ func (suite *KeeperTestSuite) TestRecvPacket() { tc := tc suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { suite.SetupTest() // reset + expError = nil // must explicitly set for failed cases path = ibctesting.NewPath(suite.chainA, suite.chainB) tc.malleate() // get proof of packet commitment from chainA packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - proof, proofHeight := suite.chainA.QueryProof(packetKey) + proof, proofHeight := path.EndpointA.QueryProof(packetKey) err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacket(suite.chainB.GetContext(), channelCap, packet, proof, proofHeight) @@ -388,6 +452,11 @@ func (suite *KeeperTestSuite) TestRecvPacket() { } } else { suite.Require().Error(err) + + // only check if expError is set, since not all error codes can be known + if expError != nil { + suite.Require().True(errors.Is(err, expError)) + } } }) } @@ -488,6 +557,7 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { ack = ibcmock.MockAcknowledgement channelCap *capabilitytypes.Capability + expError *sdkerrors.Error ) testCases := []testCase{ @@ -520,12 +590,55 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, true}, + {"packet already acknowledged ordered channel", func() { + expError = types.ErrPacketCommitmentNotFound + + path.SetChannelOrdered() + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + // create packet commitment + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + err = path.EndpointA.AcknowledgePacket(packet, ack.Acknowledgement()) + suite.Require().NoError(err) + }, false}, + {"packet already acknowledged unordered channel", func() { + expError = types.ErrPacketCommitmentNotFound + + // setup uses an UNORDERED channel + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + + // create packet commitment + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + err = path.EndpointA.AcknowledgePacket(packet, ack.Acknowledgement()) + suite.Require().NoError(err) + }, false}, {"channel not found", func() { + expError = types.ErrChannelNotFound + // use wrong channel naming suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) }, false}, {"channel not open", func() { + expError = types.ErrInvalidChannelState + suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -534,8 +647,11 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"capability authentication failed ORDERED", func() { + expError = types.ErrInvalidChannelCapability + path.SetChannelOrdered() suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) // create packet commitment err := path.EndpointA.SendPacket(packet) @@ -548,29 +664,37 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { channelCap = capabilitytypes.NewCapability(3) }, false}, {"packet destination port ≠ channel counterparty port", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) + // use wrong port for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"packet destination channel ID ≠ channel counterparty channel ID", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) + // use wrong channel for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, ibctesting.InvalidID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"connection not found", func() { + expError = connectiontypes.ErrConnectionNotFound + suite.coordinator.Setup(path) + // pass channel check - suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( - suite.chainB.GetContext(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, - types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{connIDB}, path.EndpointB.ChannelConfig.Version), + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{"connection-1000"}, path.EndpointA.ChannelConfig.Version), ) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"connection not OPEN", func() { + expError = connectiontypes.ErrInvalidConnectionState suite.coordinator.SetupClients(path) // connection on chainA is in INIT err := path.EndpointA.ConnOpenInit() @@ -587,12 +711,16 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"packet hasn't been sent", func() { + expError = types.ErrPacketCommitmentNotFound + // packet commitment never written suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, {"packet ack verification failed", func() { + // skip error code check since error occurs in light-clients + // ack never written suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -601,25 +729,56 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { path.EndpointA.SendPacket(packet) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) }, false}, + {"packet commitment bytes do not match", func() { + expError = types.ErrInvalidPacket + + // setup uses an UNORDERED channel + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + + // create packet commitment + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + packet.Data = []byte("invalid packet commitment") + }, false}, {"next ack sequence not found", func() { - path := ibctesting.NewPath(suite.chainA, suite.chainB) + expError = types.ErrSequenceAckNotFound suite.coordinator.SetupConnections(path) - packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + + path.EndpointA.ChannelID = ibctesting.FirstChannelID + path.EndpointB.ChannelID = ibctesting.FirstChannelID + // manually creating channel prevents next sequence acknowledgement from being set suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version), ) + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) // manually set packet commitment - suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence(), ibctesting.MockPacketData) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence(), types.CommitPacket(suite.chainA.App.AppCodec(), packet)) // manually set packet acknowledgement and capability - suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, packet.GetSequence(), ibctesting.MockAcknowledgement) + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, packet.GetSequence(), types.CommitAcknowledgement(ack.Acknowledgement())) + suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) channelCap = suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + suite.coordinator.CommitBlock(path.EndpointA.Chain, path.EndpointB.Chain) + + path.EndpointA.UpdateClient() + path.EndpointB.UpdateClient() }, false}, {"next ack sequence mismatch ORDERED", func() { + expError = types.ErrPacketSequenceOutOfOrder path.SetChannelOrdered() suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -641,12 +800,13 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { tc := tc suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { suite.SetupTest() // reset + expError = nil // must explcitly set error for failed cases path = ibctesting.NewPath(suite.chainA, suite.chainB) tc.malleate() packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - proof, proofHeight := suite.chainB.QueryProof(packetKey) + proof, proofHeight := path.EndpointB.QueryProof(packetKey) err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.AcknowledgePacket(suite.chainA.GetContext(), channelCap, packet, ack.Acknowledgement(), proof, proofHeight) pc := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) @@ -665,6 +825,10 @@ func (suite *KeeperTestSuite) TestAcknowledgePacket() { } } else { suite.Error(err) + // only check if expError is set, since not all error codes can be known + if expError != nil { + suite.Require().True(errors.Is(err, expError)) + } } }) } diff --git a/modules/core/04-channel/keeper/timeout.go b/modules/core/04-channel/keeper/timeout.go index 1f6357d34d3..08d10f83612 100644 --- a/modules/core/04-channel/keeper/timeout.go +++ b/modules/core/04-channel/keeper/timeout.go @@ -80,6 +80,10 @@ func (k Keeper) TimeoutPacket( commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if len(commitment) == 0 { + return sdkerrors.Wrapf(types.ErrPacketCommitmentNotFound, "packet with sequence (%d) has been acknowledged or timed out. In rare cases the packet was never sent or the packet sequence is incorrect", packet.GetSequence()) + } + packetCommitment := types.CommitPacket(k.cdc, packet) // verify we sent the packet and haven't cleared it out yet @@ -92,7 +96,7 @@ func (k Keeper) TimeoutPacket( // check that packet has not been received if nextSequenceRecv > packet.GetSequence() { return sdkerrors.Wrapf( - types.ErrInvalidPacket, + types.ErrPacketReceived, "packet already received, next sequence receive > packet sequence (%d > %d)", nextSequenceRecv, packet.GetSequence(), ) } diff --git a/modules/core/04-channel/keeper/timeout_test.go b/modules/core/04-channel/keeper/timeout_test.go index ab6c4e49d2c..460e6097632 100644 --- a/modules/core/04-channel/keeper/timeout_test.go +++ b/modules/core/04-channel/keeper/timeout_test.go @@ -1,10 +1,14 @@ package keeper_test import ( + "errors" "fmt" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/modules/core/03-connection/types" "github.com/cosmos/ibc-go/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/modules/core/24-host" "github.com/cosmos/ibc-go/modules/core/exported" @@ -20,6 +24,7 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { packet types.Packet nextSeqRecv uint64 ordered bool + expError *sdkerrors.Error ) testCases := []testCase{ @@ -42,12 +47,41 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { // need to update chainA's client representing chainB to prove missing ack path.EndpointA.UpdateClient() }, true}, + {"packet already timed out: ORDERED", func() { + expError = types.ErrInvalidChannelState + ordered = true + path.SetChannelOrdered() + + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), uint64(suite.chainB.GetContext().BlockTime().UnixNano())) + path.EndpointA.SendPacket(packet) + // need to update chainA's client representing chainB to prove missing ack + path.EndpointA.UpdateClient() + + err := path.EndpointA.TimeoutPacket(packet) + suite.Require().NoError(err) + }, false}, + {"packet already timed out: UNORDERED", func() { + expError = types.ErrPacketCommitmentNotFound + ordered = false + + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), disabledTimeoutTimestamp) + path.EndpointA.SendPacket(packet) + // need to update chainA's client representing chainB to prove missing ack + path.EndpointA.UpdateClient() + + err := path.EndpointA.TimeoutPacket(packet) + suite.Require().NoError(err) + }, false}, {"channel not found", func() { + expError = types.ErrChannelNotFound // use wrong channel naming suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) }, false}, {"channel not open", func() { + expError = types.ErrInvalidChannelState suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) @@ -55,16 +89,19 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { suite.Require().NoError(err) }, false}, {"packet destination port ≠ channel counterparty port", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) // use wrong port for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) }, false}, {"packet destination channel ID ≠ channel counterparty channel ID", func() { + expError = types.ErrInvalidPacket suite.coordinator.Setup(path) // use wrong channel for dest packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, ibctesting.InvalidID, timeoutHeight, disabledTimeoutTimestamp) }, false}, {"connection not found", func() { + expError = connectiontypes.ErrConnectionNotFound // pass channel check suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( suite.chainA.GetContext(), @@ -74,12 +111,14 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) }, false}, {"timeout", func() { + expError = types.ErrPacketTimeout suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) path.EndpointA.SendPacket(packet) path.EndpointA.UpdateClient() }, false}, {"packet already received ", func() { + expError = types.ErrPacketReceived ordered = true path.SetChannelOrdered() @@ -91,6 +130,7 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { path.EndpointA.UpdateClient() }, false}, {"packet hasn't been sent", func() { + expError = types.ErrPacketCommitmentNotFound ordered = true path.SetChannelOrdered() @@ -99,6 +139,8 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { path.EndpointA.UpdateClient() }, false}, {"next seq receive verification failed", func() { + // skip error check, error occurs in light-clients + // set ordered to false resulting in wrong proof provided ordered = false @@ -110,6 +152,8 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { path.EndpointA.UpdateClient() }, false}, {"packet ack verification failed", func() { + // skip error check, error occurs in light-clients + // set ordered to true resulting in wrong proof provided ordered = true @@ -129,6 +173,7 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { ) suite.SetupTest() // reset + expError = nil // must be expliticly changed by failed cases nextSeqRecv = 1 // must be explicitly changed path = ibctesting.NewPath(suite.chainA, suite.chainB) @@ -149,6 +194,11 @@ func (suite *KeeperTestSuite) TestTimeoutPacket() { suite.Require().NoError(err) } else { suite.Require().Error(err) + // only check if expError is set, since not all error codes can be known + if expError != nil { + suite.Require().True(errors.Is(err, expError)) + } + } }) } diff --git a/modules/core/04-channel/types/errors.go b/modules/core/04-channel/types/errors.go index 82cf773057d..30293eddf07 100644 --- a/modules/core/04-channel/types/errors.go +++ b/modules/core/04-channel/types/errors.go @@ -21,8 +21,13 @@ var ( ErrPacketTimeout = sdkerrors.Register(SubModuleName, 14, "packet timeout") ErrTooManyConnectionHops = sdkerrors.Register(SubModuleName, 15, "too many connection hops") ErrInvalidAcknowledgement = sdkerrors.Register(SubModuleName, 16, "invalid acknowledgement") - ErrPacketCommitmentNotFound = sdkerrors.Register(SubModuleName, 17, "packet commitment not found") - ErrPacketReceived = sdkerrors.Register(SubModuleName, 18, "packet already received") - ErrAcknowledgementExists = sdkerrors.Register(SubModuleName, 19, "acknowledgement for packet already exists") - ErrInvalidChannelIdentifier = sdkerrors.Register(SubModuleName, 20, "invalid channel identifier") + ErrAcknowledgementExists = sdkerrors.Register(SubModuleName, 17, "acknowledgement for packet already exists") + ErrInvalidChannelIdentifier = sdkerrors.Register(SubModuleName, 18, "invalid channel identifier") + + // packets already relayed errors + ErrPacketReceived = sdkerrors.Register(SubModuleName, 19, "packet already received") + ErrPacketCommitmentNotFound = sdkerrors.Register(SubModuleName, 20, "packet commitment not found") // may occur for already received acknowledgements or timeouts and in rare cases for packets never sent + + // ORDERED channel error + ErrPacketSequenceOutOfOrder = sdkerrors.Register(SubModuleName, 21, "packet sequence is out of order") ) diff --git a/modules/core/23-commitment/types/merkle.go b/modules/core/23-commitment/types/merkle.go index 597a1ac9983..72b3de47b4f 100644 --- a/modules/core/23-commitment/types/merkle.go +++ b/modules/core/23-commitment/types/merkle.go @@ -253,8 +253,8 @@ func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs value = subroot case *ics23.CommitmentProof_Nonexist: return sdkerrors.Wrapf(ErrInvalidProof, - "chained membership proof contains nonexistence proof at index %d. If this is unexpected, please ensure that proof was queried from the height that contained the value in store and was queried with the correct key.", - i) + "chained membership proof contains nonexistence proof at index %d. If this is unexpected, please ensure that proof was queried from a height that contained the value in store and was queried with the correct key. The key used: %s", + i, keys) default: return sdkerrors.Wrapf(ErrInvalidProof, "expected proof type: %T, got: %T", &ics23.CommitmentProof_Exist{}, proofs[i].Proof) diff --git a/modules/light-clients/07-tendermint/types/client_state.go b/modules/light-clients/07-tendermint/types/client_state.go index 06bbb902ea7..4884c679df0 100644 --- a/modules/light-clients/07-tendermint/types/client_state.go +++ b/modules/light-clients/07-tendermint/types/client_state.go @@ -545,7 +545,7 @@ func produceVerificationArgs( if cs.GetLatestHeight().LT(height) { return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrapf( sdkerrors.ErrInvalidHeight, - "client state height < proof height (%d < %d)", cs.GetLatestHeight(), height, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.GetLatestHeight(), height, ) } diff --git a/testing/endpoint.go b/testing/endpoint.go index e32d0cdfbf7..bc03c5482e6 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -403,8 +403,6 @@ func (endpoint *Endpoint) WriteAcknowledgement(ack exported.Acknowledgement, pac } // AcknowledgePacket sends a MsgAcknowledgement to the channel associated with the endpoint. -// TODO: add a query for the acknowledgement by events -// - https://github.com/cosmos/cosmos-sdk/issues/6509 func (endpoint *Endpoint) AcknowledgePacket(packet channeltypes.Packet, ack []byte) error { // get proof of acknowledgement on counterparty packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) @@ -415,6 +413,32 @@ func (endpoint *Endpoint) AcknowledgePacket(packet channeltypes.Packet, ack []by return endpoint.Chain.sendMsgs(ackMsg) } +// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { + // get proof for timeout based on channel order + var packetKey []byte + + switch endpoint.ChannelConfig.Order { + case channeltypes.ORDERED: + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + case channeltypes.UNORDERED: + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + default: + return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + } + + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + nextSeqRecv, found := endpoint.Counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) + require.True(endpoint.Chain.t, found) + + timeoutMsg := channeltypes.NewMsgTimeout( + packet, nextSeqRecv, + proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.sendMsgs(timeoutMsg) +} + // SetChannelClosed sets a channel state to CLOSED. func (endpoint *Endpoint) SetChannelClosed() error { channel := endpoint.GetChannel()