Skip to content

Commit 14d5486

Browse files
feat(transfer): use registered error code for error acks in token forwarding (#6648)
* Add typed errors to packet forwarding * Use forward errors in tests * Make ack construction consistent
1 parent 007dee1 commit 14d5486

File tree

4 files changed

+55
-50
lines changed

4 files changed

+55
-50
lines changed

modules/apps/transfer/keeper/forwarding.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package keeper
22

33
import (
4-
"errors"
5-
64
errorsmod "cosmossdk.io/errors"
75

86
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -64,7 +62,7 @@ func (k Keeper) ackForwardPacketError(ctx sdk.Context, prevPacket channeltypes.P
6462
return err
6563
}
6664

67-
forwardAck := channeltypes.NewErrorAcknowledgement(errors.New("forwarded packet failed"))
65+
forwardAck := channeltypes.NewErrorAcknowledgement(types.ErrForwardedPacketFailed)
6866
return k.acknowledgeForwardedPacket(ctx, prevPacket, forwardAck)
6967
}
7068

@@ -74,7 +72,7 @@ func (k Keeper) ackForwardPacketTimeout(ctx sdk.Context, prevPacket channeltypes
7472
return err
7573
}
7674

77-
forwardAck := channeltypes.NewErrorAcknowledgement(errors.New("forwarded packet timed out"))
75+
forwardAck := channeltypes.NewErrorAcknowledgement(types.ErrForwardedPacketTimedOut)
7876
return k.acknowledgeForwardedPacket(ctx, prevPacket, forwardAck)
7977
}
8078

modules/apps/transfer/keeper/relay_forwarding_test.go

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package keeper_test
22

33
import (
4-
"errors"
54
"fmt"
65
"time"
76

87
sdkmath "cosmossdk.io/math"
98

109
sdk "github.com/cosmos/cosmos-sdk/types"
1110

12-
"github.com/cosmos/ibc-go/v8/modules/apps/transfer/internal"
1311
"github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
1412
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
1513
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
@@ -403,14 +401,14 @@ func (suite *KeeperTestSuite) TestAcknowledgementFailureScenario5Forwarding() {
403401
suite.Require().NoError(err) // message committed
404402

405403
// parse the packet from result events and recv packet on chainB
406-
packet, err := ibctesting.ParsePacketFromEvents(result.Events)
404+
packetFromAtoB, err := ibctesting.ParsePacketFromEvents(result.Events)
407405
suite.Require().NoError(err)
408-
suite.Require().NotNil(packet)
406+
suite.Require().NotNil(packetFromAtoB)
409407

410408
err = path1.EndpointB.UpdateClient()
411409
suite.Require().NoError(err)
412410

413-
result, err = path1.EndpointB.RecvPacketWithResult(packet)
411+
result, err = path1.EndpointB.RecvPacketWithResult(packetFromAtoB)
414412
suite.Require().NoError(err)
415413
suite.Require().NotNil(result)
416414

@@ -447,14 +445,14 @@ func (suite *KeeperTestSuite) TestAcknowledgementFailureScenario5Forwarding() {
447445
suite.Require().NoError(err) // message committed
448446

449447
// parse the packet from result events and recv packet on chainB
450-
packet, err = ibctesting.ParsePacketFromEvents(result.Events)
448+
packetFromBtoC, err := ibctesting.ParsePacketFromEvents(result.Events)
451449
suite.Require().NoError(err)
452-
suite.Require().NotNil(packet)
450+
suite.Require().NotNil(packetFromBtoC)
453451

454452
err = path2.EndpointB.UpdateClient()
455453
suite.Require().NoError(err)
456454

457-
result, err = path2.EndpointB.RecvPacketWithResult(packet)
455+
result, err = path2.EndpointB.RecvPacketWithResult(packetFromBtoC)
458456
suite.Require().NoError(err)
459457
suite.Require().NotNil(result)
460458

@@ -503,22 +501,22 @@ func (suite *KeeperTestSuite) TestAcknowledgementFailureScenario5Forwarding() {
503501
suite.Require().Equal(sdkmath.NewInt(0), postCoinOnC.Amount, "Vouchers have not been burned")
504502

505503
// parse the packet from result events and recv packet on chainB
506-
packet, err = ibctesting.ParsePacketFromEvents(result.Events)
504+
packetFromCtoB, err := ibctesting.ParsePacketFromEvents(result.Events)
507505
suite.Require().NoError(err)
508-
suite.Require().NotNil(packet)
506+
suite.Require().NotNil(packetFromCtoB)
509507

510508
err = path2.EndpointA.UpdateClient()
511509
suite.Require().NoError(err)
512510

513-
result, err = path2.EndpointA.RecvPacketWithResult(packet)
511+
result, err = path2.EndpointA.RecvPacketWithResult(packetFromCtoB)
514512
suite.Require().NoError(err)
515513
suite.Require().NotNil(result)
516514

517515
// We have successfully received the packet on B and forwarded it to A.
518516
// Lets try to retrieve it in order to save it
519-
forwardedPacket, found := suite.chainB.GetSimApp().TransferKeeper.GetForwardedPacket(suite.chainB.GetContext(), path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID, packet.Sequence)
517+
forwardedPacket, found := suite.chainB.GetSimApp().TransferKeeper.GetForwardedPacket(suite.chainB.GetContext(), path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID, packetFromCtoB.Sequence)
520518
suite.Require().True(found)
521-
suite.Require().Equal(packet, forwardedPacket)
519+
suite.Require().Equal(packetFromCtoB, forwardedPacket)
522520

523521
// Voucher have been burned on chain B
524522
coin = sdk.NewCoin(denomAB.IBCDenom(), amount)
@@ -530,68 +528,62 @@ func (suite *KeeperTestSuite) TestAcknowledgementFailureScenario5Forwarding() {
530528
// of denom
531529

532530
// parse the packet from result events and recv packet on chainA
533-
packet, err = ibctesting.ParsePacketFromEvents(result.Events)
531+
packetFromBtoA, err := ibctesting.ParsePacketFromEvents(result.Events)
534532
suite.Require().NoError(err)
535-
suite.Require().NotNil(packet)
533+
suite.Require().NotNil(packetFromBtoA)
536534

537-
// manipulate escrow account for denom on chain A
538-
coin = sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(99))
539-
suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), coin)
540-
totalEscrowChainA = suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainA.GetContext(), coin.GetDenom())
541-
suite.Require().Equal(sdkmath.NewInt(99), totalEscrowChainA.Amount)
535+
// turn off receive on chain A to trigger an error
536+
suite.chainA.GetSimApp().TransferKeeper.SetParams(suite.chainA.GetContext(), types.Params{
537+
SendEnabled: true,
538+
ReceiveEnabled: false,
539+
})
542540

543541
err = path1.EndpointA.UpdateClient()
544542
suite.Require().NoError(err)
545-
// suite.Require().Equal(packet, forwardedPacket)
546543

547-
result, err = path1.EndpointA.RecvPacketWithResult(packet)
548-
suite.Require().Error(err)
549-
suite.Require().Nil(result)
550-
// In theory now an error ack should have been written on chain A
551-
// NOW WE HAVE TO SEND ACK TO B, PROPAGTE ACK TO C, CHECK FINAL RESULTS
544+
result, err = path1.EndpointA.RecvPacketWithResult(packetFromBtoA)
545+
suite.Require().NoError(err)
552546

553-
// Reconstruct packet data
554-
data, err := internal.UnmarshalPacketData(packet.Data, types.V2)
547+
// An error ack has been written on chainA
548+
// Now we need to propagate it back to chainB and chainC
549+
packetSequenceOnA, err := ibctesting.ParsePacketSequenceFromEvents(result.Events)
555550
suite.Require().NoError(err)
556551

552+
errorAckOnA := channeltypes.NewErrorAcknowledgement(types.ErrReceiveDisabled)
553+
errorAckCommitmentOnA := channeltypes.CommitAcknowledgement(errorAckOnA.Acknowledgement())
554+
ackOnC, found := suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(suite.chainA.GetContext(), path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, packetSequenceOnA)
555+
suite.Require().True(found)
556+
suite.Require().Equal(errorAckCommitmentOnA, ackOnC)
557+
557558
err = path1.EndpointB.UpdateClient()
558559
suite.Require().NoError(err)
559-
ack := channeltypes.NewErrorAcknowledgement(fmt.Errorf("failed packet transfer"))
560560

561-
// err = path1.EndpointA.AcknowledgePacket(packetRecv, ack.Acknowledgement())
562-
err = suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, data, ack)
561+
err = path1.EndpointB.AcknowledgePacket(packetFromBtoA, errorAckOnA.Acknowledgement())
563562
suite.Require().NoError(err)
564563

565564
// Check that Escrow B has been refunded amount
566565
coin = sdk.NewCoin(denomAB.IBCDenom(), amount)
567566
totalEscrowChainB = suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom())
568567
suite.Require().Equal(sdkmath.NewInt(100), totalEscrowChainB.Amount)
569568

570-
denom := types.ExtractDenomFromPath(denomABC.Path())
571-
data = types.NewFungibleTokenPacketDataV2(
572-
[]types.Token{
573-
{
574-
Denom: denom,
575-
Amount: amount.String(),
576-
},
577-
}, suite.chainC.SenderAccounts[0].SenderAccount.GetAddress().String(), suite.chainA.SenderAccounts[0].SenderAccount.GetAddress().String(), "", types.Forwarding{})
578-
// suite.chainC.SenderAccounts[0].SenderAccount.GetAddress().String() This should be forward account of B
579-
packet = channeltypes.NewPacket(data.GetBytes(), 3, path2.EndpointB.ChannelConfig.PortID, path2.EndpointB.ChannelID, path2.EndpointA.ChannelConfig.PortID, path2.EndpointA.ChannelID, clienttypes.NewHeight(1, 100), 0)
580-
581569
err = path2.EndpointB.UpdateClient()
582570
suite.Require().NoError(err)
583571

572+
errorAckOnB := channeltypes.NewErrorAcknowledgement(types.ErrForwardedPacketFailed)
573+
errorAckCommitmentOnB := channeltypes.CommitAcknowledgement(errorAckOnB.Acknowledgement())
574+
ackOnB := suite.chainB.GetAcknowledgement(packetFromCtoB)
575+
suite.Require().Equal(errorAckCommitmentOnB, ackOnB)
576+
584577
// Check the status of account on chain C before executing ack.
585578
coin = sdk.NewCoin(denomABC.IBCDenom(), amount)
586579
postCoinOnC = suite.chainC.GetSimApp().BankKeeper.GetBalance(suite.chainC.GetContext(), suite.chainC.SenderAccounts[0].SenderAccount.GetAddress(), coin.GetDenom())
587580
suite.Require().Equal(sdkmath.NewInt(0), postCoinOnC.Amount, "Final Hop balance has been refunded before Ack execution")
588581

589582
// Execute ack
590-
err = suite.chainC.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainC.GetContext(), packet, data, ack)
591-
// err = path2.EndpointB.AcknowledgePacket(packet, ack.Acknowledgement())
583+
err = path2.EndpointB.AcknowledgePacket(packetFromCtoB, errorAckOnB.Acknowledgement())
592584
suite.Require().NoError(err)
593585

594-
// Check that everythig has been reverted
586+
// Check that everything has been reverted
595587
//
596588
// Check the vouchers transfer/channel-1/transfer/channel-0/denom have been refunded on C
597589
coin = sdk.NewCoin(denomABC.IBCDenom(), amount)
@@ -748,7 +740,7 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacketForwarding() {
748740
suite.Require().True(found, "chainB does not have an ack")
749741

750742
// And that this ack is of the type we expect (Error due to time out)
751-
ack := channeltypes.NewErrorAcknowledgement(errors.New("forwarded packet timed out"))
743+
ack := channeltypes.NewErrorAcknowledgement(types.ErrForwardedPacketTimedOut)
752744
ackbytes := channeltypes.CommitAcknowledgement(ack.Acknowledgement())
753745
suite.Require().Equal(ackbytes, storedAck)
754746

modules/apps/transfer/types/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ var (
1717
ErrInvalidAuthorization = errorsmod.Register(ModuleName, 10, "invalid transfer authorization")
1818
ErrInvalidMemo = errorsmod.Register(ModuleName, 11, "invalid memo")
1919
ErrInvalidForwarding = errorsmod.Register(ModuleName, 12, "invalid token forwarding")
20+
ErrForwardedPacketTimedOut = errorsmod.Register(ModuleName, 13, "forwarded packet timed out")
21+
ErrForwardedPacketFailed = errorsmod.Register(ModuleName, 14, "forwarded packet failed")
2022
)

testing/events.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ func ParseProposalIDFromEvents(events []abci.Event) (uint64, error) {
174174
return 0, fmt.Errorf("proposalID event attribute not found")
175175
}
176176

177+
// ParsePacketSequenceFromEvents parses events emitted from MsgRecvPacket and returns the packet sequence
178+
func ParsePacketSequenceFromEvents(events []abci.Event) (uint64, error) {
179+
for _, event := range events {
180+
for _, attribute := range event.Attributes {
181+
if attribute.Key == "packet_sequence" {
182+
return strconv.ParseUint(attribute.Value, 10, 64)
183+
}
184+
}
185+
}
186+
187+
return 0, fmt.Errorf("packet sequence event attribute not found")
188+
}
189+
177190
// AssertEvents asserts that expected events are present in the actual events.
178191
func AssertEvents(
179192
suite *testifysuite.Suite,

0 commit comments

Comments
 (0)