-
Notifications
You must be signed in to change notification settings - Fork 629
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
Changes from 14 commits
5b0bc50
b7088f8
6f20ad9
31a2831
0178c09
8f8ce78
f83b472
2b0cca4
2d7f0b8
534e00c
8f08760
7d6246c
a03916d
0db8c86
ce656c4
092d3fe
d30f887
1a44c0b
15d7092
35f71cf
0b85196
0286c64
28d5622
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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) `OnChanOpenInit` now returns a version string in line with the latest [spec changes](https://github.com/cosmos/ibc/pull/629). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some sugar coating?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the first part but left out the version selection. I found that term confusing personally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add that it may return a default version string if relayers pass in empty version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left this out as I don't think the default is applicable to all modules. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hence the word |
||||||
|
||||||
|
||||||
### State Machine Breaking | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -42,16 +42,16 @@ func (im IBCModule) 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 appVersion | ||||||||||||||||||||||||
// call underlying app's OnChanOpenInit callback with the passed in version | ||||||||||||||||||||||||
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, | ||||||||||||||||||||||||
chanCap, counterparty, version) | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This gives an opportunity to the interchain accounts auth module to modify the version string, or perhaps mistakenly return the incorrect string, right? Is this something we want to support? i.e. providing the auth module the ability to mutate this? The code below would complain about
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really following your point with the code example. What would be the alternative here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed if ibc-auth is expected not to change the version at all. Then we should ignore it's return value. And if relayer passes in empty string, then just return our default version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry if that was confusing, like @AdityaSripal says, we should ignore the
But for ICA a relayer is never expected to initiate the channel handshake, it should be from some on-chain mechanism, correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In our implementation of the controller module, yes. The version string will always be the Metadata struct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated this. Good catch 👍 |
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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, | ||||||
}, | ||||||
|
@@ -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( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor nit: casing
Suggested change
|
||||||
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) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ func (im IBCModule) 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not enable fee by default. If provided string is empty then, get default version from underlying app and wrap with fee version. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to enable fees by default? Otherwise there's no way to only use the default version of the base application. I'd think middleware applications must be explicitly activated or, the versionMetadata should be specified with an empty fee version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this was my assumption also. What is the benefit of enabling the fee middleware by default with an empty version passed in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think fees should be enabled by default. My preference is that relayers get the default version of the stack if they don't explicitly specify otherwise. Some middleware will want to be activated by default and some may want to be explicitly activated. For adoption purposes, I think it would be good to make incentivization the default rather than having to explicitly enable it. Because as I think the number of applications scale, most relayers will only be passing in empty strings and use non-empty strings only in very special cases (otherwise they would need to have context on every single port they relay for). So a relayer that always defaults to what the application prefers will be opening incentivized channels. Of course, if the relayer wants to explicitly disable fees then they can by passing in a version without the feeVersion. But we should design for a future where relayers are rarely passing in specific strings There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me. In favor of @AdityaSripal's approach. It should just be well documented that ics29 is an exceptional middleware application that we believe should be enabled by default, but that other applications (who might be copy/pasting our code) should give good consideration to whether their application should be enabled by default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation @AdityaSripal. Makes sense to me! |
||
// Since it is valid for fee version to not be specified, the above middleware version may be for a middleware | ||
|
@@ -47,14 +47,24 @@ func (im IBCModule) OnChanOpenInit( | |
} | ||
|
||
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) | ||
damiannolan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was the call to underlying app's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its moved to above on line 53 so that the returned application version (for example: {
"AppVersion": "ics20-1",
"FeeVersion": "ics29-1",
} Does that make sense? |
||
} | ||
|
||
// OnChanOpenTry implements the IBCModule interface | ||
|
@@ -113,7 +123,7 @@ func (im IBCModule) 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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,11 +68,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 | ||
|
@@ -95,10 +95,27 @@ 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 { | ||
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(version, ibcmock.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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also assert |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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) == "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can add a test case for this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't all of the applications/middleware have a similar code block? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,11 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { | |
{ | ||
"success", func() {}, true, | ||
}, | ||
{ | ||
"empty version string", func() { | ||
channel.Version = "" | ||
}, true, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also add a test case when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have this test case already, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
@@ -85,12 +90,13 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { | |
|
||
tc.malleate() // explicitly change fields in channel and testChannel | ||
|
||
err = cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), | ||
version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), | ||
colin-axner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto on empty |
||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing this causes the fee tests to break. Looking into it 👀 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Open to improvements on the changelog message.