Skip to content

Commit 6fbc8c9

Browse files
perf: minimize logic on rechecktx for recvpacket (backport #6280) (#6343)
* perf: minimize logic on rechecktx for recvpacket (#6280) * perf: minimize logic on rechecktx for recvpacket * refactor: rework layout for recvpacket rechecktx * test: add tests for 04-channel rechecktx func * test: add tests for core ante handler * chore: add comment explaining is rechecktx usage * linter appeasement * chore: add changelog entry * Update modules/core/ante/ante.go Co-authored-by: Carlos Rodriguez <[email protected]> * imp: use cached ctx for consistency * refactor: change added test to use expected errors * lint --------- Co-authored-by: Carlos Rodriguez <[email protected]> (cherry picked from commit 56ae97d) # Conflicts: # modules/core/04-channel/keeper/packet.go # modules/core/ante/ante.go * fix: merge conflicts --------- Co-authored-by: colin axnér <[email protected]>
1 parent a1ed61c commit 6fbc8c9

File tree

6 files changed

+213
-16
lines changed

6 files changed

+213
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4545
### Improvements
4646

4747
* (core/ante) [\#6302](https://github.com/cosmos/ibc-go/pull/6302) Performance: Skip app callbacks during RecvPacket execution in checkTx within the redundant relay ante handler.
48+
* (core/ante) [\#6280](https://github.com/cosmos/ibc-go/pull/6280) Performance: Skip redundant proof checking in RecvPacket execution in reCheckTx within the redundant relay ante handler.
4849

4950
### Features
5051

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package keeper
2+
3+
import (
4+
errorsmod "cosmossdk.io/errors"
5+
6+
sdk "github.com/cosmos/cosmos-sdk/types"
7+
8+
"github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
9+
)
10+
11+
// RecvPacketReCheckTx applies replay protection ensuring that when relay messages are
12+
// re-executed in ReCheckTx, we can appropriately filter out redundant relay transactions.
13+
func (k *Keeper) RecvPacketReCheckTx(ctx sdk.Context, packet types.Packet) error {
14+
channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel())
15+
if !found {
16+
return errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel())
17+
}
18+
19+
if err := k.applyReplayProtection(ctx, packet, channel); err != nil {
20+
return err
21+
}
22+
23+
return nil
24+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package keeper_test
2+
3+
import (
4+
"github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
5+
ibctesting "github.com/cosmos/ibc-go/v7/testing"
6+
)
7+
8+
func (suite *KeeperTestSuite) TestRecvPacketReCheckTx() {
9+
var (
10+
path *ibctesting.Path
11+
packet types.Packet
12+
)
13+
14+
testCases := []struct {
15+
name string
16+
malleate func()
17+
expError error
18+
}{
19+
{
20+
"success",
21+
func() {},
22+
nil,
23+
},
24+
{
25+
"channel not found",
26+
func() {
27+
packet.DestinationPort = "invalid-port" //nolint:goconst
28+
},
29+
types.ErrChannelNotFound,
30+
},
31+
{
32+
"redundant relay",
33+
func() {
34+
err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacketReCheckTx(suite.chainB.GetContext(), packet)
35+
suite.Require().NoError(err)
36+
},
37+
types.ErrNoOpMsg,
38+
},
39+
}
40+
41+
for _, tc := range testCases {
42+
tc := tc
43+
suite.Run(tc.name, func() {
44+
suite.SetupTest() // reset
45+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
46+
suite.coordinator.Setup(path)
47+
48+
sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData)
49+
suite.Require().NoError(err)
50+
packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp)
51+
52+
tc.malleate()
53+
54+
err = suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacketReCheckTx(suite.chainB.GetContext(), packet)
55+
56+
expPass := tc.expError == nil
57+
if expPass {
58+
suite.Require().NoError(err)
59+
} else {
60+
suite.Require().ErrorIs(err, tc.expError)
61+
}
62+
})
63+
}
64+
}

modules/core/04-channel/keeper/packet.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,29 @@ func (k Keeper) RecvPacket(
204204
return sdkerrors.Wrap(err, "couldn't verify counterparty packet commitment")
205205
}
206206

207+
if err := k.applyReplayProtection(ctx, packet, channel); err != nil {
208+
return err
209+
}
210+
211+
// log that a packet has been received & executed
212+
k.Logger(ctx).Info(
213+
"packet received",
214+
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
215+
"src_port", packet.GetSourcePort(),
216+
"src_channel", packet.GetSourceChannel(),
217+
"dst_port", packet.GetDestPort(),
218+
"dst_channel", packet.GetDestChannel(),
219+
)
220+
221+
// emit an event that the relayer can query for
222+
EmitRecvPacketEvent(ctx, packet, channel)
223+
224+
return nil
225+
}
226+
227+
// applyReplayProtection ensures a packet has not already been received
228+
// and performs the necessary state changes to ensure it cannot be received again.
229+
func (k *Keeper) applyReplayProtection(ctx sdk.Context, packet exported.PacketI, channel types.Channel) error {
207230
switch channel.Ordering {
208231
case types.UNORDERED:
209232
// check if the packet receipt has been received already for unordered channels
@@ -257,19 +280,6 @@ func (k Keeper) RecvPacket(
257280

258281
}
259282

260-
// log that a packet has been received & executed
261-
k.Logger(ctx).Info(
262-
"packet received",
263-
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
264-
"src_port", packet.GetSourcePort(),
265-
"src_channel", packet.GetSourceChannel(),
266-
"dst_port", packet.GetDestPort(),
267-
"dst_channel", packet.GetDestChannel(),
268-
)
269-
270-
// emit an event that the relayer can query for
271-
EmitRecvPacketEvent(ctx, packet, channel)
272-
273283
return nil
274284
}
275285

modules/core/ante/ante.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ func (rrd RedundantRelayDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
3636
err error
3737
)
3838
// when we are in ReCheckTx mode, ctx.IsCheckTx() will also return true
39-
// there we must start the if statement on ctx.IsReCheckTx() to correctly
39+
// therefore we must start the if statement on ctx.IsReCheckTx() to correctly
4040
// determine which mode we are in
4141
if ctx.IsReCheckTx() {
42-
response, err = rrd.k.RecvPacket(sdk.WrapSDKContext(ctx), msg)
42+
response, err = rrd.recvPacketReCheckTx(ctx, msg)
4343
} else {
4444
response, err = rrd.recvPacketCheckTx(ctx, msg)
4545
}
@@ -129,3 +129,23 @@ func (rrd RedundantRelayDecorator) recvPacketCheckTx(ctx sdk.Context, msg *chann
129129

130130
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
131131
}
132+
133+
// recvPacketReCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler.
134+
// It only performs core IBC receiving logic and skips any application logic.
135+
func (rrd RedundantRelayDecorator) recvPacketReCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) {
136+
// If the packet was already received, perform a no-op
137+
// Use a cached context to prevent accidental state changes
138+
cacheCtx, writeFn := ctx.CacheContext()
139+
err := rrd.k.ChannelKeeper.RecvPacketReCheckTx(cacheCtx, msg.Packet)
140+
141+
switch err {
142+
case nil:
143+
writeFn()
144+
case channeltypes.ErrNoOpMsg:
145+
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil
146+
default:
147+
return nil, sdkerrors.Wrap(err, "receive packet verification failed")
148+
}
149+
150+
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
151+
}

modules/core/ante/ante_test.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (suite *AnteTestSuite) createUpdateClientMessage() sdk.Msg {
174174
return msg
175175
}
176176

177-
func (suite *AnteTestSuite) TestAnteDecorator() {
177+
func (suite *AnteTestSuite) TestAnteDecoratorCheckTx() {
178178
testCases := []struct {
179179
name string
180180
malleate func(suite *AnteTestSuite) []sdk.Msg
@@ -497,3 +497,81 @@ func (suite *AnteTestSuite) TestAnteDecorator() {
497497
})
498498
}
499499
}
500+
501+
func (suite *AnteTestSuite) TestAnteDecoratorReCheckTx() {
502+
testCases := []struct {
503+
name string
504+
malleate func(suite *AnteTestSuite) []sdk.Msg
505+
expError error
506+
}{
507+
{
508+
"success on one new RecvPacket message",
509+
func(suite *AnteTestSuite) []sdk.Msg {
510+
// the RecvPacket message has not been submitted to the chain yet, so it will succeed
511+
return []sdk.Msg{suite.createRecvPacketMessage(false)}
512+
},
513+
nil,
514+
},
515+
{
516+
"success on one redundant and one new RecvPacket message",
517+
func(suite *AnteTestSuite) []sdk.Msg {
518+
return []sdk.Msg{
519+
suite.createRecvPacketMessage(true),
520+
suite.createRecvPacketMessage(false),
521+
}
522+
},
523+
nil,
524+
},
525+
{
526+
"success on invalid proof (proof checks occur in checkTx)",
527+
func(suite *AnteTestSuite) []sdk.Msg {
528+
msg := suite.createRecvPacketMessage(false)
529+
msg.ProofCommitment = []byte("invalid-proof")
530+
return []sdk.Msg{msg}
531+
},
532+
nil,
533+
},
534+
{
535+
"no success on one redundant RecvPacket message",
536+
func(suite *AnteTestSuite) []sdk.Msg {
537+
return []sdk.Msg{suite.createRecvPacketMessage(true)}
538+
},
539+
channeltypes.ErrRedundantTx,
540+
},
541+
}
542+
543+
for _, tc := range testCases {
544+
tc := tc
545+
546+
suite.Run(tc.name, func() {
547+
// reset suite
548+
suite.SetupTest()
549+
550+
k := suite.chainB.App.GetIBCKeeper()
551+
decorator := ante.NewRedundantRelayDecorator(k)
552+
553+
msgs := tc.malleate(suite)
554+
555+
deliverCtx := suite.chainB.GetContext().WithIsCheckTx(false)
556+
reCheckCtx := suite.chainB.GetContext().WithIsReCheckTx(true)
557+
558+
// create multimsg tx
559+
txBuilder := suite.chainB.TxConfig.NewTxBuilder()
560+
err := txBuilder.SetMsgs(msgs...)
561+
suite.Require().NoError(err)
562+
tx := txBuilder.GetTx()
563+
564+
next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil }
565+
566+
_, err = decorator.AnteHandle(deliverCtx, tx, false, next)
567+
suite.Require().NoError(err, "antedecorator should not error on DeliverTx")
568+
569+
_, err = decorator.AnteHandle(reCheckCtx, tx, false, next)
570+
if tc.expError == nil {
571+
suite.Require().NoError(err, "non-strict decorator did not pass as expected")
572+
} else {
573+
suite.Require().ErrorIs(err, tc.expError, "non-strict antehandler did not return error as expected")
574+
}
575+
})
576+
}
577+
}

0 commit comments

Comments
 (0)