Skip to content

Commit

Permalink
fix(axelarnet)!: handle IBC timeout for GMP (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
haiyizxx authored and Talal Ashraf committed Jul 18, 2023
1 parent 42f78ce commit f16cad4
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 14 deletions.
50 changes: 37 additions & 13 deletions x/axelarnet/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/axelarnetwork/axelar-core/x/axelarnet/keeper"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported"
"github.com/axelarnetwork/utils/funcs"
)

var (
Expand Down Expand Up @@ -312,7 +313,7 @@ func (am AppModule) OnAcknowledgementPacket(
return err
}

return setRoutedPacketFailed(ctx, am.keeper, am.nexus, port, channel, sequence)
return am.setRoutedPacketFailed(ctx, packet)
}
}

Expand All @@ -329,15 +330,15 @@ func (am AppModule) OnTimeoutPacket(

// IBC timeout packets, by convention, use the source port/channel to represent native chain -> counterparty chain channel id
// https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#definitions
port, channel, sequence := packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()
port, channel := packet.GetSourcePort(), packet.GetSourceChannel()

// Timeout causes a refund of the token (i.e unlock from the escrow address/mint of token depending on whether it's native to chain).
// Hence, it's rate limited on the Incoming direction (tokens coming in to Axelar hub).
if err := am.rateLimiter.RateLimitPacket(ctx, packet, nexus.Incoming, types.NewIBCPath(port, channel)); err != nil {
return err
}

return setRoutedPacketFailed(ctx, am.keeper, am.nexus, port, channel, sequence)
return am.setRoutedPacketFailed(ctx, packet)
}

// GetAppVersion implements the ChannelKeeper interface
Expand Down Expand Up @@ -385,28 +386,51 @@ func setRoutedPacketCompleted(ctx sdk.Context, k keeper.Keeper, n types.Nexus, p
return nil
}

func setRoutedPacketFailed(ctx sdk.Context, k keeper.Keeper, n types.Nexus, portID, channelID string, seq uint64) error {
func (am AppModule) setRoutedPacketFailed(ctx sdk.Context, packet channeltypes.Packet) error {
// IBC ack/timeout packets, by convention, use the source port/channel to represent native chain -> counterparty chain channel id
// https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#definitions
port, channel, sequence := packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()

// check if the packet is Axelar routed cross chain transfer
transferID, ok := getSeqIDMapping(ctx, k, portID, channelID, seq)
transferID, ok := getSeqIDMapping(ctx, am.keeper, port, channel, sequence)
if ok {
events.Emit(ctx,
&types.IBCTransferFailed{
ID: transferID,
Sequence: seq,
PortID: portID,
ChannelID: channelID,
Sequence: sequence,
PortID: port,
ChannelID: channel,
})
k.Logger(ctx).Info(fmt.Sprintf("set IBC transfer %d failed", transferID))
am.keeper.Logger(ctx).Info(fmt.Sprintf("set IBC transfer %d failed", transferID))

return k.SetTransferFailed(ctx, transferID)
return am.keeper.SetTransferFailed(ctx, transferID)
}

// check if the packet is Axelar routed general message
messageID, ok := getSeqMessageIDMapping(ctx, k, portID, channelID, seq)
messageID, ok := getSeqMessageIDMapping(ctx, am.keeper, port, channel, sequence)
if ok {
k.Logger(ctx).Debug("set general message status to failed", "messageID", messageID)
return n.SetMessageFailed(ctx, messageID)
coin, err := keeper.NewCoin(ctx, am.ibcK, am.nexus, extractTokenFromAckOrTimeoutPacket(packet))
if err != nil {
return err
}

err = coin.Lock(am.bank, types.AxelarGMPAccount)
if err != nil {
return err
}

am.keeper.Logger(ctx).Debug("set general message status to failed", "messageID", messageID)
return am.nexus.SetMessageFailed(ctx, messageID)
}

return nil
}

func extractTokenFromAckOrTimeoutPacket(packet channeltypes.Packet) sdk.Coin {
data := funcs.Must(types.ToICS20Packet(packet))

trace := ibctransfertypes.ParseDenomTrace(data.Denom)
amount := funcs.MustOk(sdk.NewIntFromString(data.Amount))

return sdk.NewCoin(trace.IBCDenom(), amount)
}
34 changes: 33 additions & 1 deletion x/axelarnet/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/axelarnetwork/axelar-core/x/axelarnet/types/mock"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types/testutils"
"github.com/axelarnetwork/axelar-core/x/nexus/exported"
nexustestutils "github.com/axelarnetwork/axelar-core/x/nexus/exported/testutils"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
. "github.com/axelarnetwork/utils/test"
Expand All @@ -37,9 +38,11 @@ func TestGetMigrationHandler(t *testing.T) {
appModule axelarnet.AppModule
k keeper.Keeper
n *mock.NexusMock
bankK *mock.BankKeeperMock

ack channeltypes.Acknowledgement
transfer types.IBCTransfer
message exported.GeneralMessage
transfers []types.IBCTransfer
)

Expand Down Expand Up @@ -72,10 +75,14 @@ func TestGetMigrationHandler(t *testing.T) {
},
}

bankK := &mock.BankKeeperMock{
bankK = &mock.BankKeeperMock{
SendCoinsFunc: func(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error {
return nil
},
SendCoinsFromAccountToModuleFunc: func(sdk.Context, sdk.AccAddress, string, sdk.Coins) error {
return nil
},
BurnCoinsFunc: func(sdk.Context, string, sdk.Coins) error { return nil },
}

transferK := ibctransferkeeper.NewKeeper(encCfg.Codec, sdk.NewKVStoreKey("transfer"), transferSubspace, &mock.ChannelKeeperMock{}, &mock.ChannelKeeperMock{}, &mock.PortKeeperMock{}, accountK, bankK, scopedTransferK)
Expand Down Expand Up @@ -111,6 +118,22 @@ func TestGetMigrationHandler(t *testing.T) {
funcs.MustNoErr(k.EnqueueIBCTransfer(ctx, transfer))
})

seqMapsToMessageID := When("packet seq maps to message ID", func() {
message = nexustestutils.RandomMessage(exported.Processing)
funcs.MustNoErr(k.SetSeqMessageIDMapping(ctx, ibctransfertypes.PortID, channelID, packetSeq, message.ID))

n.GetMessageFunc = func(sdk.Context, string) (exported.GeneralMessage, bool) { return message, true }
n.IsAssetRegisteredFunc = func(sdk.Context, exported.Chain, string) bool { return true }
n.SetMessageFailedFunc = func(ctx sdk.Context, id string) error {
if id == message.ID {
message.Status = exported.Failed
}

return nil
}
n.GetChainByNativeAssetFunc = func(sdk.Context, string) (exported.Chain, bool) { return exported.Chain{}, false }
})

whenOnAck := When("on acknowledgement", func() {
err := appModule.OnAcknowledgementPacket(ctx, packet, ack.Acknowledgement(), nil)
assert.NoError(t, err)
Expand Down Expand Up @@ -180,5 +203,14 @@ func TestGetMigrationHandler(t *testing.T) {
When2(whenChainIsActivated).
When2(whenOnTimeout).
Then2(shouldNotChangeTransferState),

whenGetValidAckError.
When2(whenChainIsActivated).
When2(seqMapsToMessageID).
When2(whenOnAck).
Then("should set message to failed", func(t *testing.T) {
assert.Equal(t, exported.Failed, message.Status)
assert.Len(t, bankK.SendCoinsFromAccountToModuleCalls(), 1)
}),
).Run(t)
}

0 comments on commit f16cad4

Please sign in to comment.