Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: channel handshake version improvements #1283

Merged
merged 23 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5b0bc50
refactor: returning version from OnChanOpenInit
seantking Apr 21, 2022
b7088f8
refactor: update tests and add version to proto resp
seantking Apr 21, 2022
6f20ad9
refactor: adding version to msg server resp
seantking Apr 21, 2022
31a2831
refactor: remove unncessary if & update version on Endpoint.Ack
seantking Apr 26, 2022
0178c09
fix: ics29 OnChanOpenInit remake versionMetaData before returning
seantking Apr 26, 2022
8f8ce78
chore: update godoc
seantking Apr 26, 2022
f83b472
Merge branch 'main' into sean/issue#722-channel-handshake-improvements
seantking Apr 26, 2022
2b0cca4
test: adding check for expected version string
seantking Apr 26, 2022
2d7f0b8
test: adding test case for passing empty version string to ics20 onCh…
seantking Apr 26, 2022
534e00c
Update modules/apps/29-fee/ibc_module.go
seantking Apr 26, 2022
8f08760
chore: comment
seantking Apr 26, 2022
7d6246c
Merge branch 'main' into sean/issue#722-channel-handshake-improvements
seantking Apr 26, 2022
a03916d
chore: changelog
seantking Apr 27, 2022
0db8c86
Merge branch 'main' into sean/issue#722-channel-handshake-improvements
seantking Apr 27, 2022
ce656c4
fix: ica now discards auth module version
seantking Apr 27, 2022
092d3fe
chore: update changelog
seantking Apr 27, 2022
d30f887
adding default version for ics29
seantking Apr 29, 2022
1a44c0b
fix: using transfer module directly rather than calling full middlewa…
seantking May 4, 2022
15d7092
fix testing bug
colin-axner May 4, 2022
35f71cf
Merge branch 'main' into sean/issue#722-channel-handshake-improvements
seantking May 4, 2022
0b85196
refactor: test now uses bool for isFeeEnabled rather than direct check
seantking May 4, 2022
0286c64
docs: updating comment and migration docs
seantking May 4, 2022
28d5622
Merge branch 'main' into sean/issue#722-channel-handshake-improvements
seantking May 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking

* (transfer) [\#1250](https://github.com/cosmos/ibc-go/pull/1250) Deprecate `GetTransferAccount` since the `transfer` module account is never used.
* (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).
colin-axner marked this conversation as resolved.
Show resolved Hide resolved


### State Machine Breaking

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,21 @@ func (im IBCMiddleware) OnChanOpenInit(
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
) (string, error) {
if !im.keeper.IsControllerEnabled(ctx) {
return types.ErrControllerSubModuleDisabled
return "", types.ErrControllerSubModuleDisabled
}

if err := im.keeper.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version); err != nil {
return err
return "", err
}

// call underlying app's OnChanOpenInit callback with the passed in version
if _, err := im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version); err != nil {
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
return "", err
}

// call underlying app's OnChanOpenInit callback with the appVersion
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID,
chanCap, counterparty, version)
return version, nil
}

// OnChanOpenTry implements the IBCMiddleware interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string, chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty, version string,
) error {
return fmt.Errorf("mock ica auth fails")
) (string, error) {
return "", fmt.Errorf("mock ica auth fails")
}
}, false,
},
Expand Down Expand Up @@ -197,11 +197,24 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
suite.Require().True(ok)

err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(),
)

if tc.expPass {
expMetadata := icatypes.NewMetadata(
icatypes.Version,
path.EndpointA.ConnectionID,
path.EndpointB.ConnectionID,
"",
icatypes.EncodingProtobuf,
icatypes.TxTypeSDKMultiMsg,
)

expBytes, err := icatypes.ModuleCdc.MarshalJSON(&expMetadata)
suite.Require().NoError(err)

suite.Require().Equal(version, string(expBytes))
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
Expand Down
4 changes: 2 additions & 2 deletions modules/apps/27-interchain-accounts/host/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func (im IBCModule) OnChanOpenInit(
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
return sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain")
) (string, error) {
return "", sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain")
}

// OnChanOpenTry implements the IBCModule interface
Expand Down
44 changes: 32 additions & 12 deletions modules/apps/29-fee/ibc_middleware.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fee

import (
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
Expand Down Expand Up @@ -39,25 +41,44 @@ func (im IBCMiddleware) OnChanOpenInit(
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
) (string, error) {
var versionMetadata types.Metadata
if err := types.ModuleCdc.UnmarshalJSON([]byte(version), &versionMetadata); err != nil {
// Since it is valid for fee version to not be specified, the above middleware version may be for a middleware
// lower down in the stack. Thus, if it is not a fee version we pass the entire version string onto the underlying
// application.
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID,
chanCap, counterparty, version)

if strings.TrimSpace(version) == "" {
// default version
versionMetadata = types.Metadata{
FeeVersion: types.Version,
AppVersion: "",
}
} else {
if err := types.ModuleCdc.UnmarshalJSON([]byte(version), &versionMetadata); err != nil {
// Since it is valid for fee version to not be specified, the above middleware version may be for a middleware
// lower down in the stack. Thus, if it is not a fee version we pass the entire version string onto the underlying
// application.
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID,
chanCap, counterparty, version)
}
}

if versionMetadata.FeeVersion != types.Version {
return sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.Version, versionMetadata.FeeVersion)
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.Version, versionMetadata.FeeVersion)
}

appVersion, err := im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, versionMetadata.AppVersion)
if err != nil {
return "", err
}

versionMetadata.AppVersion = appVersion
versionBytes, err := types.ModuleCdc.MarshalJSON(&versionMetadata)
if err != nil {
return "", err
}

im.keeper.SetFeeEnabled(ctx, portID, channelID)

// call underlying app's OnChanOpenInit callback with the appVersion
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID,
chanCap, counterparty, versionMetadata.AppVersion)
return string(versionBytes), nil
}

// OnChanOpenTry implements the IBCMiddleware interface
Expand Down Expand Up @@ -94,7 +115,6 @@ func (im IBCMiddleware) OnChanOpenTry(
}

versionMetadata.AppVersion = appVersion

versionBytes, err := types.ModuleCdc.MarshalJSON(&versionMetadata)
if err != nil {
return "", err
Expand All @@ -116,7 +136,7 @@ func (im IBCMiddleware) OnChanOpenAck(
if im.keeper.IsFeeEnabled(ctx, portID, channelID) {
var versionMetadata types.Metadata
if err := types.ModuleCdc.UnmarshalJSON([]byte(counterpartyVersion), &versionMetadata); err != nil {
return sdkerrors.Wrap(types.ErrInvalidVersion, "failed to unmarshal ICS29 counterparty version metadata")
return sdkerrors.Wrapf(err, "failed to unmarshal ICS29 counterparty version metadata: %s", counterpartyVersion)
}

if versionMetadata.FeeVersion != types.Version {
Expand Down
30 changes: 26 additions & 4 deletions modules/apps/29-fee/ibc_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ func (suite *FeeTestSuite) TestOnChanOpenInit() {
types.Version,
false,
},
{
"passing an empty string returns default version",
"",
true,
},
}

for _, tc := range testCases {
Expand All @@ -68,11 +73,11 @@ func (suite *FeeTestSuite) TestOnChanOpenInit() {
suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
portID, channelID string, chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty, version string,
) error {
) (string, error) {
if version != ibcmock.Version {
return fmt.Errorf("incorrect mock version")
return "", fmt.Errorf("incorrect mock version")
}
return nil
return ibcmock.Version, nil
}

suite.path.EndpointA.ChannelID = ibctesting.FirstChannelID
Expand All @@ -95,13 +100,30 @@ func (suite *FeeTestSuite) TestOnChanOpenInit() {
cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
suite.Require().True(ok)

err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, chanCap, counterparty, channel.Version)

if tc.expPass {
// check if the channel is fee enabled. If so version string should include metaData
isFeeEnabled := suite.chainA.GetSimApp().IBCFeeKeeper.IsFeeEnabled(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
if isFeeEnabled {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a strange way to test here because you're relying on the result of the callback execution to create your expected value. If the callback incorrectly creates a fee version and incorrectly sets fee enabled you won't catch it here. Similarly if the callback incorrectly doesn't set fee version and incorrectly doesn't set fee enabled you won't catch it.

I think we should add a boolean in the test cases that says feeEnabled. You then can add the expected value for each test case and then check if the feeEnabled flag is true and check if the version contains a fee version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup good catch. Thanks for the feedback 🤝

versionMetadata := types.Metadata{
FeeVersion: types.Version,
AppVersion: ibcmock.Version,
}

versionBytes, err := types.ModuleCdc.MarshalJSON(&versionMetadata)
suite.Require().NoError(err)

suite.Require().Equal(version, string(versionBytes))
} else {
suite.Require().Equal(ibcmock.Version, version)
}

suite.Require().NoError(err, "unexpected error from version: %s", tc.version)
} else {
suite.Require().Error(err, "error not returned for version: %s", tc.version)
suite.Require().Equal("", version)
}
})
}
Expand Down
1 change: 0 additions & 1 deletion modules/apps/29-fee/transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,4 @@ func (suite *FeeTestSuite) TestFeeTransfer() {
fee.AckFee.Add(fee.TimeoutFee...), // ack fee paid, timeout fee refunded
sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), ibctesting.TestCoin.Denom)).Sub(originalChainASenderAccountBalance),
)

}
15 changes: 10 additions & 5 deletions modules/apps/transfer/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package transfer
import (
"fmt"
"math"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -70,21 +71,25 @@ func (im IBCModule) OnChanOpenInit(
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
) (string, error) {
if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil {
return err
return "", err
}

if strings.TrimSpace(version) == "" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add a test case for this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't all of the applications/middleware have a similar code block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responded below

version = types.Version
}

if version != types.Version {
return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version)
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version)
}

// Claim channel capability passed back by IBC module
if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return err
return "", err
}

return nil
return version, nil
}

// OnChanOpenTry implements the IBCModule interface.
Expand Down
18 changes: 11 additions & 7 deletions modules/apps/transfer/ibc_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"

"github.com/cosmos/ibc-go/v3/modules/apps/transfer"
"github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
Expand All @@ -28,6 +29,11 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() {
{
"success", func() {}, true,
},
{
"empty version string", func() {
channel.Version = ""
}, true,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test case when channel.Version is not empty but it's a string that does not match the expected types.Version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this test case already, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, sorry. I missed it.

{
"max channels reached", func() {
path.EndpointA.ChannelID = channeltypes.FormatChannelIdentifier(math.MaxUint32 + 1)
Expand Down Expand Up @@ -74,25 +80,23 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() {
Version: types.Version,
}

module, _, err := suite.chainA.App.GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.TransferPort)
suite.Require().NoError(err)

var err error
chanCap, err = suite.chainA.App.GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(ibctesting.TransferPort, path.EndpointA.ChannelID))
suite.Require().NoError(err)

cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
suite.Require().True(ok)

tc.malleate() // explicitly change fields in channel and testChannel

err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
transferModule := transfer.NewIBCModule(suite.chainA.GetSimApp().TransferKeeper)
version, err := transferModule.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, counterparty, channel.GetVersion(),
)

if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(types.Version, version)
} else {
suite.Require().Error(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on empty version assertion

suite.Require().Equal(version, "")
}

})
Expand Down
16 changes: 11 additions & 5 deletions modules/core/05-port/types/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import (
// IBCModule defines an interface that implements all the callbacks
// that modules must define as specified in ICS-26
type IBCModule interface {
// OnChanOpenInit will verify that the relayer-chosen parameters are
// valid and perform any custom INIT logic.It may return an error if
// the chosen parameters are invalid in which case the handshake is aborted.
// OnChanOpenInit should return an error if the provided version is invalid.
// OnChanOpenInit will verify that the relayer-chosen parameters
// are valid and perform any custom INIT logic.
// It may return an error if the chosen parameters are invalid
// in which case the handshake is aborted.
// If the provided version string is non-empty, OnChanOpenInit should return
// the version string if valid or an error if the provided version is invalid.
Comment on lines +18 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we require that Init returns the exact same version if version is non-empty? I don't believe that is the case. Applications may want to modify the version string even if it is non-empty

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took this directly from the spec: https://github.com/cosmos/ibc/pull/629/files#diff-508b3e7784a300436fbeb6940be22f31c74e958270a36bb851d06a8782ad7e7aR50

Should we update both? Agree with your assessment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree we should change the spec as well

// If the version string is empty, OnChanOpenInit is expected to
// return a default version string representing the version(s) it supports.
// If there is no default version string for the application,
// it should return an error if provided version is empty string.
OnChanOpenInit(
ctx sdk.Context,
order channeltypes.Order,
Expand All @@ -24,7 +30,7 @@ type IBCModule interface {
channelCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error
) (string, error)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved

// OnChanOpenTry will verify the relayer-chosen parameters along with the
// counterparty-chosen version string and perform custom TRY logic.
Expand Down
7 changes: 4 additions & 3 deletions modules/core/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,17 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan
}

// Perform application logic callback
if err = cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.Channel.Version); err != nil {
version, err := cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.Channel.Version)
if err != nil {
return nil, sdkerrors.Wrap(err, "channel open init callback failed")
}

// Write channel into state
k.ChannelKeeper.WriteOpenInitChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, msg.Channel.Version)
k.ChannelKeeper.WriteOpenInitChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing this causes the fee tests to break. Looking into it 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed: 0178c09


return &channeltypes.MsgChannelOpenInitResponse{
ChannelId: channelID,
Version: msg.Channel.Version,
Version: version,
}, nil
}

Expand Down
4 changes: 4 additions & 0 deletions testing/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ func (endpoint *Endpoint) ChanOpenInit() error {
endpoint.ChannelID, err = ParseChannelIDFromEvents(res.GetEvents())
require.NoError(endpoint.Chain.T, err)

// update version to selected app version
// NOTE: this update must be performed after SendMsgs()
endpoint.ChannelConfig.Version = endpoint.GetChannel().Version

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion testing/mock/ibc_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type MockIBCApp struct {
channelCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error
) (string, error)

OnChanOpenTry func(
ctx sdk.Context,
Expand Down
Loading