Skip to content

Commit b31bf93

Browse files
feat: channel aliasing with migration (#8473)
Co-authored-by: Gjermund Garaba <[email protected]>
1 parent 9e4cf68 commit b31bf93

File tree

27 files changed

+1159
-113
lines changed

27 files changed

+1159
-113
lines changed

docs/docs/02-apps/01-transfer/10-IBCv2-transfer.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,27 @@ Because IBC v2 no longer uses channels, it is no longer possible to rely on a fi
6363
## Changes to the application module interface
6464

6565
Instead of implementing token transfer for `port.IBCModule`, IBC v2 uses the new application interface `api.IBCModule`. More information on the interface differences can be found in the [application section](../../01-ibc/03-apps/00-ibcv2apps.md).
66+
67+
## MsgTransfer Entrypoint
68+
69+
The `MsgTransfer` entrypoint has been retained in order to retain support for the common entrypoint integrated in most existing frontends.
70+
71+
If `MsgTransfer` is used with a clientID as the `msg.SourceChannel` then the handler will automatically use the IBC v2 protocol. It will internally call the `MsgSendPacket` endpoint so that the execution flow is the same in the state machine for all IBC v2 packets while still presenting the same endpoint for users.
72+
73+
Of course, we want to still retain support for sending v2 packets on existing channels. The denominations of tokens once they leave the origin chain are prefixed by the port and channel ID in IBC v1. Moreover, the transfer escrow accounts holding the original tokens are generated from the channel IDs. Thus, if we wish to interact these remote tokens using IBC v2, we must still use the v1 channel identifiers that they were originally sent with.
74+
75+
Thus, `MsgTransfer` has an additional `UseAliasing` boolean field to indicate that we wish to use IBC v2 protocol while still using the old v1 channel identifiers. This enables users to interact with the same tokens, DEX pools, and cross-chain DEFI protocols using the same denominations that they had previously with the IBC v2 protocol. To use the `MsgTransfer` with aliasing we can submit the message like so:
76+
77+
```go
78+
MsgTransfer{
79+
SourcePort: "transfer",
80+
SourceChannel: "channel-4", //note: we are using an existing v1 channel identiifer
81+
Token: "uatom",
82+
Sender: {senderAddr},
83+
Receiver: {receiverAddr},
84+
TimeoutHeight: ZeroHeight, // note: IBC v2 does not use timeout height
85+
TimeoutTimestamp: 100_000_000,
86+
Memo: "",
87+
UseAliasing: true, // set aliasing to true so the handler uses IBC v2 instead of IBC v1
88+
}
89+
```

modules/apps/transfer/keeper/msg_server.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,21 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.
5353
return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1)
5454
}
5555

56-
// if a channel exists with source channel, then use IBC V1 protocol
57-
// otherwise use IBC V2 protocol
58-
channel, isIBCV1 := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel)
56+
// if the channel does not exist, or we are using channel aliasing then use IBC V2 protocol
57+
// otherwise use IBC V1 protocol
58+
channel, hasChannel := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel)
59+
isIBCV2 := !hasChannel || msg.UseAliasing
5960

6061
var sequence uint64
61-
if isIBCV1 {
62+
if isIBCV2 {
63+
// otherwise try to send an IBC V2 packet, if the sourceChannel is not a IBC V2 client
64+
// then core IBC will return a CounterpartyNotFound error
65+
sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData)
66+
} else {
6267
// if a V1 channel exists for the source channel, then use IBC V1 protocol
6368
sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, packetData)
6469
// telemetry for transfer occurs here, in IBC V2 this is done in the onSendPacket callback
6570
telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, channel.Counterparty.PortId, channel.Counterparty.ChannelId, token)
66-
} else {
67-
// otherwise try to send an IBC V2 packet, if the sourceChannel is not a IBC V2 client
68-
// then core IBC will return a CounterpartyNotFound error
69-
sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData)
7071
}
7172
if err != nil {
7273
return nil, err

modules/apps/transfer/types/msgs.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
sdk "github.com/cosmos/cosmos-sdk/types"
99

1010
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
11+
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
1112
host "github.com/cosmos/ibc-go/v10/modules/core/24-host"
1213
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
1314
)
@@ -61,13 +62,35 @@ func NewMsgTransfer(
6162
}
6263
}
6364

65+
// NewMsgTransferAliased creates a new MsgTransfer instance
66+
// with isV2 set to true, indicating that it is using the V2 protocol
67+
// with v1 channel identifiers.
68+
func NewMsgTransferAliased(
69+
sourcePort, sourceChannel string,
70+
token sdk.Coin, sender, receiver string,
71+
timeoutHeight clienttypes.Height, timeoutTimestamp uint64,
72+
memo string,
73+
) *MsgTransfer {
74+
return &MsgTransfer{
75+
SourcePort: sourcePort,
76+
SourceChannel: sourceChannel,
77+
Token: token,
78+
Sender: sender,
79+
Receiver: receiver,
80+
TimeoutHeight: timeoutHeight,
81+
TimeoutTimestamp: timeoutTimestamp,
82+
Memo: memo,
83+
UseAliasing: true, // This indicates that the message is using the V2 protocol with aliased channel identifiers
84+
}
85+
}
86+
6487
// NewMsgTransferWithEncoding creates a new MsgTransfer instance
6588
// with the provided encoding
6689
func NewMsgTransferWithEncoding(
6790
sourcePort, sourceChannel string,
6891
token sdk.Coin, sender, receiver string,
6992
timeoutHeight clienttypes.Height, timeoutTimestamp uint64,
70-
memo string, encoding string,
93+
memo string, encoding string, useAliasing bool,
7194
) *MsgTransfer {
7295
return &MsgTransfer{
7396
SourcePort: sourcePort,
@@ -79,6 +102,7 @@ func NewMsgTransferWithEncoding(
79102
TimeoutTimestamp: timeoutTimestamp,
80103
Memo: memo,
81104
Encoding: encoding,
105+
UseAliasing: useAliasing,
82106
}
83107
}
84108

@@ -118,8 +142,18 @@ func (msg MsgTransfer) validateIdentifiers() error {
118142
if err := host.PortIdentifierValidator(msg.SourcePort); err != nil {
119143
return errorsmod.Wrapf(err, "invalid source port ID %s", msg.SourcePort)
120144
}
121-
if err := host.ChannelIdentifierValidator(msg.SourceChannel); err != nil {
122-
return errorsmod.Wrapf(err, "invalid source channel ID %s", msg.SourceChannel)
145+
// if we are using aliasing, then the source channel must be in the channel id format
146+
// expected by ibc-go
147+
// otherwise, it may be either a client id using v2 directly or a channel id using ibc v1
148+
// thus, we perform a less strict check
149+
if msg.UseAliasing {
150+
if _, err := channeltypes.ParseChannelSequence(msg.SourceChannel); err != nil {
151+
return errorsmod.Wrapf(err, "invalid source channel ID %s", msg.SourceChannel)
152+
}
153+
} else {
154+
if err := host.ChannelIdentifierValidator(msg.SourceChannel); err != nil {
155+
return errorsmod.Wrapf(err, "invalid source channel ID %s", msg.SourceChannel)
156+
}
123157
}
124158

125159
return nil

modules/apps/transfer/types/msgs_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const (
2727
// 195 characters
2828
invalidLongPort = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales"
2929

30-
validChannel = "testchannel"
30+
validChannel = "channel-5"
3131
eurekaClient = "07-tendermint-0"
3232
invalidChannel = "(invalidchannel1)"
3333
invalidShortChannel = "invalid"
@@ -58,11 +58,13 @@ func TestMsgTransferValidation(t *testing.T) {
5858
expError error
5959
}{
6060
{"valid msg with base denom", types.NewMsgTransfer(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil},
61+
{"valid aliased channel", types.NewMsgTransferAliased(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil},
62+
{"valid aliased channel with encoding", types.NewMsgTransferWithEncoding(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json", true), nil},
6163
{"valid eureka msg with base denom", types.NewMsgTransfer(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil},
62-
{"valid eureka msg with base denom and encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil},
64+
{"valid eureka msg with base denom and encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json", false), nil},
6365
{"valid msg with trace hash", types.NewMsgTransfer(validPort, validChannel, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil},
6466
{"valid eureka msg with trace hash", types.NewMsgTransfer(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil},
65-
{"valid eureka msg with trace hash with encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil},
67+
{"valid eureka msg with trace hash with encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json", false), nil},
6668
{"invalid ibc denom", types.NewMsgTransfer(validPort, validChannel, invalidIBCCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins},
6769
{"too short port id", types.NewMsgTransfer(invalidShortPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID},
6870
{"too long port id", types.NewMsgTransfer(invalidLongPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID},
@@ -77,6 +79,7 @@ func TestMsgTransferValidation(t *testing.T) {
7779
{"missing recipient address", types.NewMsgTransfer(validPort, validChannel, coin, sender, "", clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidAddress},
7880
{"too long recipient address", types.NewMsgTransfer(validPort, validChannel, coin, sender, ibctesting.GenerateString(types.MaximumReceiverLength+1), clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidAddress},
7981
{"empty coin", types.NewMsgTransfer(validPort, validChannel, sdk.Coin{}, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins},
82+
{"invalid aliased channel", types.NewMsgTransferAliased(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID},
8083
}
8184

8285
for _, tc := range testCases {

modules/apps/transfer/types/tx.pb.go

Lines changed: 82 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)