Skip to content

Commit 6c0d3cd

Browse files
colin-axnermergify[bot]
authored andcommitted
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 <carlos@interchain.io> * imp: use cached ctx for consistency * refactor: change added test to use expected errors * lint --------- Co-authored-by: Carlos Rodriguez <carlos@interchain.io> (cherry picked from commit 56ae97d) # Conflicts: # modules/core/04-channel/keeper/packet.go # modules/core/ante/ante.go
1 parent 90dbe6e commit 6c0d3cd

File tree

6 files changed

+280
-2
lines changed

6 files changed

+280
-2
lines changed

CHANGELOG.md

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

4747
* (apps/27-interchain-accounts) [\#6144](https://github.com/cosmos/ibc-go/pull/6144) Emit an event signalling that the host submodule is disabled.
4848
* (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.
49+
* (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.
4950

5051
### Features
5152

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/v8/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/v8/modules/core/04-channel/types"
5+
ibctesting "github.com/cosmos/ibc-go/v8/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+
path.Setup()
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,43 @@ func (k Keeper) RecvPacket(
201201
packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
202202
commitment,
203203
); err != nil {
204+
<<<<<<< HEAD
204205
return sdkerrors.Wrap(err, "couldn't verify counterparty packet commitment")
206+
=======
207+
return errorsmod.Wrap(err, "couldn't verify counterparty packet commitment")
208+
}
209+
210+
if err := k.applyReplayProtection(ctx, packet, channel); err != nil {
211+
return err
212+
}
213+
214+
// log that a packet has been received & executed
215+
k.Logger(ctx).Info(
216+
"packet received",
217+
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
218+
"src_port", packet.GetSourcePort(),
219+
"src_channel", packet.GetSourceChannel(),
220+
"dst_port", packet.GetDestPort(),
221+
"dst_channel", packet.GetDestChannel(),
222+
)
223+
224+
// emit an event that the relayer can query for
225+
emitRecvPacketEvent(ctx, packet, channel)
226+
227+
return nil
228+
}
229+
230+
// applyReplayProtection ensures a packet has not already been received
231+
// and performs the necessary state changes to ensure it cannot be received again.
232+
func (k *Keeper) applyReplayProtection(ctx sdk.Context, packet types.Packet, channel types.Channel) error {
233+
// REPLAY PROTECTION: The recvStartSequence will prevent historical proofs from allowing replay
234+
// attacks on packets processed in previous lifecycles of a channel. After a successful channel
235+
// upgrade all packets under the recvStartSequence will have been processed and thus should be
236+
// rejected.
237+
recvStartSequence, _ := k.GetRecvStartSequence(ctx, packet.GetDestPort(), packet.GetDestChannel())
238+
if packet.GetSequence() < recvStartSequence {
239+
return errorsmod.Wrap(types.ErrPacketReceived, "packet already processed in previous channel upgrade")
240+
>>>>>>> 56ae97d8 (perf: minimize logic on rechecktx for recvpacket (#6280))
205241
}
206242

207243
switch channel.Ordering {
@@ -257,6 +293,7 @@ func (k Keeper) RecvPacket(
257293

258294
}
259295

296+
<<<<<<< HEAD
260297
// log that a packet has been received & executed
261298
k.Logger(ctx).Info(
262299
"packet received",
@@ -270,6 +307,8 @@ func (k Keeper) RecvPacket(
270307
// emit an event that the relayer can query for
271308
EmitRecvPacketEvent(ctx, packet, channel)
272309

310+
=======
311+
>>>>>>> 56ae97d8 (perf: minimize logic on rechecktx for recvpacket (#6280))
273312
return nil
274313
}
275314

modules/core/ante/ante.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ 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+
<<<<<<< HEAD
4243
response, err = rrd.k.RecvPacket(sdk.WrapSDKContext(ctx), msg)
44+
=======
45+
response, err = rrd.recvPacketReCheckTx(ctx, msg)
46+
>>>>>>> 56ae97d8 (perf: minimize logic on rechecktx for recvpacket (#6280))
4347
} else {
4448
response, err = rrd.recvPacketCheckTx(ctx, msg)
4549
}
@@ -129,3 +133,57 @@ func (rrd RedundantRelayDecorator) recvPacketCheckTx(ctx sdk.Context, msg *chann
129133

130134
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
131135
}
136+
<<<<<<< HEAD
137+
=======
138+
139+
// recvPacketReCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler.
140+
// It only performs core IBC receiving logic and skips any application logic.
141+
func (rrd RedundantRelayDecorator) recvPacketReCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) {
142+
// If the packet was already received, perform a no-op
143+
// Use a cached context to prevent accidental state changes
144+
cacheCtx, writeFn := ctx.CacheContext()
145+
err := rrd.k.ChannelKeeper.RecvPacketReCheckTx(cacheCtx, msg.Packet)
146+
147+
switch err {
148+
case nil:
149+
writeFn()
150+
case channeltypes.ErrNoOpMsg:
151+
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil
152+
default:
153+
return nil, errorsmod.Wrap(err, "receive packet verification failed")
154+
}
155+
156+
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
157+
}
158+
159+
// updateClientCheckTx runs a subset of ibc client update logic to be used specifically within the RedundantRelayDecorator AnteHandler.
160+
// The following function performs ibc client message verification for CheckTx only and state updates in both CheckTx and ReCheckTx.
161+
// Note that misbehaviour checks are omitted.
162+
func (rrd RedundantRelayDecorator) updateClientCheckTx(ctx sdk.Context, msg *clienttypes.MsgUpdateClient) error {
163+
clientMsg, err := clienttypes.UnpackClientMessage(msg.ClientMessage)
164+
if err != nil {
165+
return err
166+
}
167+
168+
if status := rrd.k.ClientKeeper.GetClientStatus(ctx, msg.ClientId); status != exported.Active {
169+
return errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot update client (%s) with status %s", msg.ClientId, status)
170+
}
171+
172+
clientModule, found := rrd.k.ClientKeeper.Route(msg.ClientId)
173+
if !found {
174+
return errorsmod.Wrap(clienttypes.ErrRouteNotFound, msg.ClientId)
175+
}
176+
177+
if !ctx.IsReCheckTx() {
178+
if err := clientModule.VerifyClientMessage(ctx, msg.ClientId, clientMsg); err != nil {
179+
return err
180+
}
181+
}
182+
183+
heights := clientModule.UpdateState(ctx, msg.ClientId, clientMsg)
184+
185+
ctx.Logger().With("module", "x/"+exported.ModuleName).Debug("ante ibc client update", "consensusHeights", heights)
186+
187+
return nil
188+
}
189+
>>>>>>> 56ae97d8 (perf: minimize logic on rechecktx for recvpacket (#6280))

modules/core/ante/ante_test.go

Lines changed: 93 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,95 @@ 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+
"success on app callback error, app callbacks are skipped for performance",
536+
func(suite *AnteTestSuite) []sdk.Msg {
537+
suite.chainB.GetSimApp().IBCMockModule.IBCApp.OnRecvPacket = func(
538+
ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress,
539+
) exported.Acknowledgement {
540+
panic(fmt.Errorf("failed OnRecvPacket mock callback"))
541+
}
542+
543+
// the RecvPacket message has not been submitted to the chain yet, so it will succeed
544+
return []sdk.Msg{suite.createRecvPacketMessage(false)}
545+
},
546+
nil,
547+
},
548+
{
549+
"no success on one redundant RecvPacket message",
550+
func(suite *AnteTestSuite) []sdk.Msg {
551+
return []sdk.Msg{suite.createRecvPacketMessage(true)}
552+
},
553+
channeltypes.ErrRedundantTx,
554+
},
555+
}
556+
557+
for _, tc := range testCases {
558+
tc := tc
559+
560+
suite.Run(tc.name, func() {
561+
// reset suite
562+
suite.SetupTest()
563+
564+
k := suite.chainB.App.GetIBCKeeper()
565+
decorator := ante.NewRedundantRelayDecorator(k)
566+
567+
msgs := tc.malleate(suite)
568+
569+
deliverCtx := suite.chainB.GetContext().WithIsCheckTx(false)
570+
reCheckCtx := suite.chainB.GetContext().WithIsReCheckTx(true)
571+
572+
// create multimsg tx
573+
txBuilder := suite.chainB.TxConfig.NewTxBuilder()
574+
err := txBuilder.SetMsgs(msgs...)
575+
suite.Require().NoError(err)
576+
tx := txBuilder.GetTx()
577+
578+
next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil }
579+
580+
_, err = decorator.AnteHandle(deliverCtx, tx, false, next)
581+
suite.Require().NoError(err, "antedecorator should not error on DeliverTx")
582+
583+
_, err = decorator.AnteHandle(reCheckCtx, tx, false, next)
584+
if tc.expError == nil {
585+
suite.Require().NoError(err, "non-strict decorator did not pass as expected")
586+
} else {
587+
suite.Require().ErrorIs(err, tc.expError, "non-strict antehandler did not return error as expected")
588+
}
589+
})
590+
}
591+
}

0 commit comments

Comments
 (0)