From 76ad03e97dda3e90d753fd321babf106b82af2ed Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 19 Jul 2021 17:46:17 +0200 Subject: [PATCH] Reject Redundant Tx Antedecorator (#235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * writeup simple antedecorator * only do antehandler on checkTx * Update modules/core/04-channel/ante.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * enable 2 antehandler strategies, and write tests * remove strict decorator and pass on non-packet/update type * move ante logic into its own package * changelog Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + modules/core/04-channel/types/errors.go | 3 + modules/core/ante/ante.go | 72 ++++ modules/core/ante/ante_test.go | 485 ++++++++++++++++++++++++ 4 files changed, 561 insertions(+) create mode 100644 modules/core/ante/ante.go create mode 100644 modules/core/ante/ante_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c2de271a6..2daf3e4c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (core/04-channel) [\#197](https://github.com/cosmos/ibc-go/pull/197) Introduced a `packet_ack_hex` attribute to emit the hex-encoded acknowledgement in events. This allows for raw binary (proto-encoded message) to be sent over events and decoded correctly on relayer. Original `packet_ack` is DEPRECATED. All relayers and IBC event consumers are encouraged to switch to `packet_ack_hex` as soon as possible. * (modules/light-clients/07-tendermint) [\#125](https://github.com/cosmos/ibc-go/pull/125) Implement efficient iteration of consensus states and pruning of earliest expired consensus state on UpdateClient. * (modules/light-clients/07-tendermint) [\#141](https://github.com/cosmos/ibc-go/pull/141) Return early in case there's a duplicate update call to save Gas. +* (modules/core/ante) [\#235](https://github.com/cosmos/ibc-go/pull/235) Introduces a new IBC Antedecorator that will reject transactions that only contain redundant packet messages (and accompany UpdateClient msgs). This will prevent relayers from wasting fees by submitting messages for packets that have already been processed by previous relayer(s). The Antedecorator is only applied on CheckTx and RecheckTx and is therefore optional for each node. ### Features diff --git a/modules/core/04-channel/types/errors.go b/modules/core/04-channel/types/errors.go index 30293eddf0..74043730bf 100644 --- a/modules/core/04-channel/types/errors.go +++ b/modules/core/04-channel/types/errors.go @@ -30,4 +30,7 @@ var ( // ORDERED channel error ErrPacketSequenceOutOfOrder = sdkerrors.Register(SubModuleName, 21, "packet sequence is out of order") + + // Antehandler error + ErrRedundantTx = sdkerrors.Register(SubModuleName, 22, "packet messages are redundant") ) diff --git a/modules/core/ante/ante.go b/modules/core/ante/ante.go new file mode 100644 index 0000000000..aa579027d7 --- /dev/null +++ b/modules/core/ante/ante.go @@ -0,0 +1,72 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" + channelkeeper "github.com/cosmos/ibc-go/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" +) + +type AnteDecorator struct { + k channelkeeper.Keeper +} + +func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} + +// AnteDecorator returns an error if a multiMsg tx only contains packet messages (Recv, Ack, Timeout) and additional update messages and all packet messages +// are redundant. If the transaction is just a single UpdateClient message, or the multimsg transaction contains some other message type, then the antedecorator returns no error +// and continues processing to ensure these transactions are included. +// This will ensure that relayers do not waste fees on multiMsg transactions when another relayer has already submitted all packets, by rejecting the tx at the mempool layer. +func (ad AnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // do not run redundancy check on DeliverTx or simulate + if (ctx.IsCheckTx() || ctx.IsReCheckTx()) && !simulate { + // keep track of total packet messages and number of redundancies across `RecvPacket`, `AcknowledgePacket`, and `TimeoutPacket/OnClose` + redundancies := 0 + packetMsgs := 0 + for _, m := range tx.GetMsgs() { + switch msg := m.(type) { + case *channeltypes.MsgRecvPacket: + if _, found := ad.k.GetPacketReceipt(ctx, msg.Packet.GetDestPort(), msg.Packet.GetDestChannel(), msg.Packet.GetSequence()); found { + redundancies += 1 + } + packetMsgs += 1 + + case *channeltypes.MsgAcknowledgement: + if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + redundancies += 1 + } + packetMsgs += 1 + + case *channeltypes.MsgTimeout: + if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + redundancies += 1 + } + packetMsgs += 1 + + case *channeltypes.MsgTimeoutOnClose: + if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + redundancies += 1 + } + packetMsgs += 1 + + case *clienttypes.MsgUpdateClient: + // do nothing here, as we want to avoid updating clients if it is batched with only redundant messages + + default: + // if the multiMsg tx has a msg that is not a packet msg or update msg, then we will not return error + // regardless of if all packet messages are redundant. This ensures that non-packet messages get processed + // even if they get batched with redundant packet messages. + return next(ctx, tx, simulate) + } + + } + + // only return error if all packet messages are redundant + if redundancies == packetMsgs && packetMsgs > 0 { + return ctx, channeltypes.ErrRedundantTx + } + } + return next(ctx, tx, simulate) +} diff --git a/modules/core/ante/ante_test.go b/modules/core/ante/ante_test.go new file mode 100644 index 0000000000..0b99768528 --- /dev/null +++ b/modules/core/ante/ante_test.go @@ -0,0 +1,485 @@ +package ante_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/modules/core/ante" + ibctesting "github.com/cosmos/ibc-go/testing" + "github.com/cosmos/ibc-go/testing/mock" + "github.com/stretchr/testify/suite" +) + +type AnteTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *AnteTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) + suite.coordinator.CommitNBlocks(suite.chainA, 2) + suite.coordinator.CommitNBlocks(suite.chainB, 2) + suite.path = ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(suite.path) +} + +// TestAnteTestSuite runs all the tests within this package. +func TestAnteTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} + +func (suite *AnteTestSuite) TestAnteDecorator() { + testCases := []struct { + name string + malleate func(suite *AnteTestSuite) []sdk.Msg + expPass bool + }{ + { + "success on single msg", + func(suite *AnteTestSuite) []sdk.Msg { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), 1, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + return []sdk.Msg{channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")} + }, + true, + }, + { + "success on multiple msgs", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 5; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + true, + }, + { + "success on multiple msgs: 1 fresh recv packet", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 5; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + err := suite.path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // receive all sequences except packet 3 + if i != 3 { + err = suite.path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + } + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + + return msgs + }, + true, + }, + { + "success on multiple mixed msgs", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + err := suite.path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i <= 6; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + true, + }, + { + "success on multiple mixed msgs: 1 fresh packet of each type", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + err := suite.path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // receive all sequences except packet 3 + if i != 3 { + + err := suite.path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + } + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + // receive all acks except ack 2 + if i != 2 { + err = suite.path.EndpointA.RecvPacket(packet) + suite.Require().NoError(err) + err = suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + suite.Require().NoError(err) + } + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i <= 6; i++ { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + // timeout packet + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + // timeout packets except sequence 5 + if i != 5 { + suite.path.EndpointB.UpdateClient() + err = suite.path.EndpointB.TimeoutPacket(packet) + suite.Require().NoError(err) + } + + msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + true, + }, + { + "success on multiple mixed msgs: only 1 fresh msg in total", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all packets + suite.path.EndpointA.SendPacket(packet) + suite.path.EndpointB.RecvPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all acks + suite.path.EndpointB.SendPacket(packet) + suite.path.EndpointA.RecvPacket(packet) + suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i < 5; i++ { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + // do not timeout packet, timeout msg is fresh + suite.path.EndpointB.SendPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + true, + }, + { + "success on single update client msg", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{&clienttypes.MsgUpdateClient{}} + }, + true, + }, + { + "success on multiple update clients", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} + }, + true, + }, + { + "success on multiple update clients and fresh packet message", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} + + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), 1, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + return append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + }, + true, + }, + { + "success of tx with different msg type even if all packet messages are redundant", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}} + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all packets + suite.path.EndpointA.SendPacket(packet) + suite.path.EndpointB.RecvPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all acks + suite.path.EndpointB.SendPacket(packet) + suite.path.EndpointA.RecvPacket(packet) + suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i < 6; i++ { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + // timeout packet + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + suite.path.EndpointB.UpdateClient() + suite.path.EndpointB.TimeoutPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + } + + // append non packet and update message to msgs to ensure multimsg tx should pass + msgs = append(msgs, &clienttypes.MsgSubmitMisbehaviour{}) + + return msgs + }, + true, + }, + { + "no success on multiple mixed message: all are redundant", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all packets + suite.path.EndpointA.SendPacket(packet) + suite.path.EndpointB.RecvPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all acks + suite.path.EndpointB.SendPacket(packet) + suite.path.EndpointA.RecvPacket(packet) + suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i < 6; i++ { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + // timeout packet + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + suite.path.EndpointB.UpdateClient() + suite.path.EndpointB.TimeoutPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + false, + }, + { + "no success if msgs contain update clients and redundant packet messages", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} + + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all packets + suite.path.EndpointA.SendPacket(packet) + suite.path.EndpointB.RecvPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 1; i <= 3; i++ { + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + // receive all acks + suite.path.EndpointB.SendPacket(packet) + suite.path.EndpointA.RecvPacket(packet) + suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + + msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + } + for i := 4; i < 6; i++ { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + // timeout packet + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + suite.path.EndpointB.UpdateClient() + suite.path.EndpointB.TimeoutPacket(packet) + + msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + } + return msgs + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + + k := suite.chainB.App.GetIBCKeeper().ChannelKeeper + decorator := ante.NewAnteDecorator(k) + + msgs := tc.malleate(suite) + + deliverCtx := suite.chainB.GetContext().WithIsCheckTx(false) + checkCtx := suite.chainB.GetContext().WithIsCheckTx(true) + + // create multimsg tx + txBuilder := suite.chainB.TxConfig.NewTxBuilder() + err := txBuilder.SetMsgs(msgs...) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + + next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil } + + _, err = decorator.AnteHandle(deliverCtx, tx, false, next) + suite.Require().NoError(err, "antedecorator should not error on DeliverTx") + + _, err = decorator.AnteHandle(checkCtx, tx, false, next) + if tc.expPass { + suite.Require().NoError(err, "non-strict decorator did not pass as expected") + } else { + suite.Require().Error(err, "non-strict antehandler did not return error as expected") + } + }) + } +}