Skip to content

Commit 15a3280

Browse files
feat: adding fee middleware support to ics27 interchain accounts (#1432)
* updating simapp to include ics29 fee in ica stacks * updating RegisterInterchainAccount to pass through version arg and support fee middleware functionality * updating tests to support additional version arg in RegisterInterchainAccount * adding migration docs for ICS27 fee middleware support * remove unnecessary spacing * fixing typo in godoc * adding changelog entry * Apply suggestions from code review Co-authored-by: Carlos Rodriguez <carlos@interchain.io> Co-authored-by: Carlos Rodriguez <carlos@interchain.io>
1 parent 042d818 commit 15a3280

File tree

9 files changed

+111
-47
lines changed

9 files changed

+111
-47
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4646
* (channel) [\#1283](https://github.com/cosmos/ibc-go/pull/1283) The `OnChanOpenInit` application callback now returns a version string in line with the latest [spec changes](https://github.com/cosmos/ibc/pull/629).
4747
* (modules/29-fee)[\#1338](https://github.com/cosmos/ibc-go/pull/1338) Renaming `Result` field in `IncentivizedAcknowledgement` to `AppAcknowledgement`.
4848
* (modules/29-fee)[\#1343](https://github.com/cosmos/ibc-go/pull/1343) Renaming `KeyForwardRelayerAddress` to `KeyRelayerAddressForAsyncAck`, and `ParseKeyForwardRelayerAddress` to `ParseKeyRelayerAddressForAsyncAck`.
49+
* (apps/27-interchain-accounts)[\#1432](https://github.com/cosmos/ibc-go/pull/1432) Updating `RegisterInterchainAccount` to include an additional `version` argument, supporting ICS29 fee middleware functionality in ICS27 interchain accounts.
4950

5051
### State Machine Breaking
5152

docs/migrations/v3-to-v4.md

+59-1
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,72 @@ No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-g
1818

1919
## Chains
2020

21-
### IS04 - Channel
21+
### ICS04 - Channel
2222

2323
The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly.
2424
This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`.
2525

2626
The `OnChanOpenInit` application callback has been modified.
2727
The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629).
2828

29+
### ICS27 - Interchain Accounts
30+
31+
The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middelware, for relayer incentivization of ICS27 packets.
32+
Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly.
33+
This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`.
34+
35+
The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring:
36+
37+
```go
38+
icaMetadata := icatypes.Metadata{
39+
Version: icatypes.Version,
40+
ControllerConnectionId: controllerConnectionID,
41+
HostConnectionId: hostConnectionID,
42+
Encoding: icatypes.EncodingProtobuf,
43+
TxType: icatypes.TxTypeSDKMultiMsg,
44+
}
45+
46+
appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata)
47+
if err != nil {
48+
return err
49+
}
50+
51+
if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil {
52+
return err
53+
}
54+
```
55+
56+
Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type:
57+
58+
```go
59+
icaMetadata := icatypes.Metadata{
60+
Version: icatypes.Version,
61+
ControllerConnectionId: controllerConnectionID,
62+
HostConnectionId: hostConnectionID,
63+
Encoding: icatypes.EncodingProtobuf,
64+
TxType: icatypes.TxTypeSDKMultiMsg,
65+
}
66+
67+
appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata)
68+
if err != nil {
69+
return err
70+
}
71+
72+
feeMetadata := feetypes.Metadata{
73+
AppVersion: string(appVersion),
74+
FeeVersion: feetypes.Version,
75+
}
76+
77+
feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata)
78+
if err != nil {
79+
return err
80+
}
81+
82+
if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil {
83+
return err
84+
}
85+
```
86+
2987
## Relayers
3088

3189
When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in.

modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"github.com/stretchr/testify/suite"
1010
"github.com/tendermint/tendermint/crypto"
1111

12-
icacontroller "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller"
1312
"github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types"
1413
icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types"
14+
fee "github.com/cosmos/ibc-go/v3/modules/apps/29-fee"
1515
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
1616
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
1717
host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
@@ -83,7 +83,7 @@ func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) erro
8383

8484
channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext())
8585

86-
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner); err != nil {
86+
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion); err != nil {
8787
return err
8888
}
8989

@@ -714,9 +714,8 @@ func (suite *InterchainAccountsTestSuite) TestGetAppVersion() {
714714
cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
715715
suite.Require().True(ok)
716716

717-
controllerModule := cbs.(icacontroller.IBCMiddleware)
718-
719-
appVersion, found := controllerModule.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
717+
controllerStack := cbs.(fee.IBCMiddleware)
718+
appVersion, found := controllerStack.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
720719
suite.Require().True(found)
721720
suite.Require().Equal(path.EndpointA.ChannelConfig.Version, appVersion)
722721
}

modules/apps/27-interchain-accounts/controller/keeper/account.go

+9-27
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99
host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
1010
)
1111

12-
// RegisterInterchainAccount is the entry point to registering an interchain account.
13-
// It generates a new port identifier using the owner address. It will bind to the
14-
// port identifier and call 04-channel 'ChanOpenInit'. An error is returned if the port
15-
// identifier is already in use. Gaining access to interchain accounts whose channels
16-
// have closed cannot be done with this function. A regular MsgChanOpenInit must be used.
17-
func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner string) error {
12+
// RegisterInterchainAccount is the entry point to registering an interchain account:
13+
// - It generates a new port identifier using the provided owner string, binds to the port identifier and claims the associated capability.
14+
// - Callers are expected to provide the appropriate application version string.
15+
// - For example, this could be an ICS27 encoded metadata type or an ICS29 encoded metadata type with a nested application version.
16+
// - A new MsgChannelOpenInit is routed through the MsgServiceRouter, executing the OnOpenChanInit callback stack as configured.
17+
// - An error is returned if the port identifier is already in use. Gaining access to interchain accounts whose channels
18+
// have closed cannot be done with this function. A regular MsgChannelOpenInit must be used.
19+
func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner, version string) error {
1820
portID, err := icatypes.NewControllerPortID(owner)
1921
if err != nil {
2022
return err
@@ -36,27 +38,7 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner s
3638
}
3739
}
3840

39-
connectionEnd, err := k.channelKeeper.GetConnection(ctx, connectionID)
40-
if err != nil {
41-
return err
42-
}
43-
44-
// NOTE: An empty string is provided for accAddress, to be fulfilled upon OnChanOpenTry handshake step
45-
metadata := icatypes.NewMetadata(
46-
icatypes.Version,
47-
connectionID,
48-
connectionEnd.GetCounterparty().GetConnectionID(),
49-
"",
50-
icatypes.EncodingProtobuf,
51-
icatypes.TxTypeSDKMultiMsg,
52-
)
53-
54-
versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
55-
if err != nil {
56-
return err
57-
}
58-
59-
msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName)
41+
msg := channeltypes.NewMsgChannelOpenInit(portID, version, channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName)
6042
handler := k.msgRouter.Handler(msg)
6143

6244
res, err := handler(ctx, msg)

modules/apps/27-interchain-accounts/controller/keeper/account_test.go

+25-7
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (suite *KeeperTestSuite) TestRegisterInterchainAccount() {
7272

7373
tc.malleate() // malleate mutates test data
7474

75-
err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner)
75+
err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner, TestVersion)
7676

7777
if tc.expPass {
7878
suite.Require().NoError(err)
@@ -88,15 +88,33 @@ func (suite *KeeperTestSuite) TestRegisterSameOwnerMultipleConnections() {
8888

8989
owner := TestOwnerAddress
9090

91-
path := NewICAPath(suite.chainA, suite.chainB)
92-
suite.coordinator.SetupConnections(path)
91+
pathAToB := NewICAPath(suite.chainA, suite.chainB)
92+
suite.coordinator.SetupConnections(pathAToB)
9393

94-
path2 := NewICAPath(suite.chainA, suite.chainC)
95-
suite.coordinator.SetupConnections(path2)
94+
pathAToC := NewICAPath(suite.chainA, suite.chainC)
95+
suite.coordinator.SetupConnections(pathAToC)
9696

97-
err := suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner)
97+
// build ICS27 metadata with connection identifiers for path A->B
98+
metadata := &icatypes.Metadata{
99+
Version: icatypes.Version,
100+
ControllerConnectionId: pathAToB.EndpointA.ConnectionID,
101+
HostConnectionId: pathAToB.EndpointB.ConnectionID,
102+
Encoding: icatypes.EncodingProtobuf,
103+
TxType: icatypes.TxTypeSDKMultiMsg,
104+
}
105+
106+
err := suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), pathAToB.EndpointA.ConnectionID, owner, string(icatypes.ModuleCdc.MustMarshalJSON(metadata)))
98107
suite.Require().NoError(err)
99108

100-
err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path2.EndpointA.ConnectionID, owner)
109+
// build ICS27 metadata with connection identifiers for path A->C
110+
metadata = &icatypes.Metadata{
111+
Version: icatypes.Version,
112+
ControllerConnectionId: pathAToC.EndpointA.ConnectionID,
113+
HostConnectionId: pathAToC.EndpointB.ConnectionID,
114+
Encoding: icatypes.EncodingProtobuf,
115+
TxType: icatypes.TxTypeSDKMultiMsg,
116+
}
117+
118+
err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), pathAToC.EndpointA.ConnectionID, owner, string(icatypes.ModuleCdc.MustMarshalJSON(metadata)))
101119
suite.Require().NoError(err)
102120
}

modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) erro
9595

9696
channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext())
9797

98-
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner); err != nil {
98+
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion); err != nil {
9999
return err
100100
}
101101

modules/apps/27-interchain-accounts/host/ibc_module_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) erro
8686

8787
channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext())
8888

89-
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner); err != nil {
89+
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion); err != nil {
9090
return err
9191
}
9292

modules/apps/27-interchain-accounts/host/keeper/keeper_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) erro
9595

9696
channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext())
9797

98-
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner); err != nil {
98+
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion); err != nil {
9999
return err
100100
}
101101

testing/simapp/app.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ func NewSimApp(
366366
// ICA Controller keeper
367367
app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper(
368368
appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName),
369-
app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee
369+
app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack
370370
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper,
371371
scopedICAControllerKeeper, app.MsgServiceRouter(),
372372
)
@@ -424,23 +424,29 @@ func NewSimApp(
424424

425425
// Create Interchain Accounts Stack
426426
// SendPacket, since it is originating from the application to core IBC:
427-
// icaAuthModuleKeeper.SendTx -> icaControllerKeeper.SendPacket -> channel.SendPacket
427+
// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket
428428

429429
// initialize ICA module with mock module as the authentication module on the controller side
430430
var icaControllerStack porttypes.IBCModule
431431
icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper))
432432
app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule)
433433
icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper)
434+
icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper)
434435

435-
icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper)
436+
// RecvPacket, message that originates from core IBC and goes down to app, the flow is:
437+
// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket
438+
439+
var icaHostStack porttypes.IBCModule
440+
icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper)
441+
icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper)
436442

437443
// Add host, controller & ica auth modules to IBC router
438444
ibcRouter.
439445
// the ICA Controller middleware needs to be explicitly added to the IBC Router because the
440446
// ICA controller module owns the port capability for ICA. The ICA authentication module
441447
// owns the channel capability.
442448
AddRoute(icacontrollertypes.SubModuleName, icaControllerStack).
443-
AddRoute(icahosttypes.SubModuleName, icaHostIBCModule).
449+
AddRoute(icahosttypes.SubModuleName, icaHostStack).
444450
AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack)
445451

446452
// Create Mock IBC Fee module stack for testing

0 commit comments

Comments
 (0)