diff --git a/CHANGELOG.md b/CHANGELOG.md index 435e0e7330f..f977789ec8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +* (core) [\#650](https://github.com/cosmos/ibc-go/pull/650) Modify `OnChanOpenTry` IBC application module callback to return the negotiated app version. The version passed into the `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. * (core) [\#629](https://github.com/cosmos/ibc-go/pull/629) Removes the `GetProofSpecs` from the ClientState interface. This function was previously unused by core IBC. * (transfer) [\#517](https://github.com/cosmos/ibc-go/pull/517) Separates the ICS 26 callback functions from `AppModule` into a new type `IBCModule` for ICS 20 transfer. * (modules/core/02-client) [\#536](https://github.com/cosmos/ibc-go/pull/536) `GetSelfConsensusState` return type changed from bool to error. diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index 6a6b39ba8d7..bb2716fa0b1 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -71,9 +71,8 @@ OnChanOpenTry( channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos // (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry) // If the module can already authenticate the capability then the module already owns it so we don't need to claim @@ -88,8 +87,18 @@ OnChanOpenTry( // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake - err := checkArguments(args) - return err + if err := checkArguments(args); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil } // Called by IBC Handler on MsgOpenAck @@ -157,38 +166,11 @@ OnChanCloseConfirm( Application modules are expected to verify versioning used during the channel handshake procedure. * `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid -* `ChanOpenTry` callback should verify that the `MsgChanOpenTry.Version` is valid and that `MsgChanOpenTry.CounterpartyVersion` is valid. +* `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. * `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. -IBC expects application modules to implement the `NegotiateAppVersion` method from the `IBCModule` -interface. This method performs application version negotiation and returns the negotiated version. -If the version cannot be negotiated, an error should be returned. - -```go -// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. -// An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface -// may decide to return an error in the event of the proposed version being incompatible with it's own -NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (version string, err error) { - // do custom application version negotiation logic -} -``` - -This function `NegotiateAppVersion` returns the version to be used in the `ChanOpenTry` step -(`MsgChanOpenTry.Version`). The relayer chooses the initial version in the `ChanOpenInit` step -(this will likely be chosen by the user controlling the relayer or by the application that -triggers the `ChanOpenInit` step). - -The version submitted in the `ChanOpenInit` step (`MsgChanOpenInit.Version`) is passed as an -argument (`proposedVersion`) to the function `NegotiateAppVersion`. This function looks at -the `proposedVersion` and returns the matching version to be used in the `ChanOpenTry` step. -Applications can choose to implement this in however fashion they choose. +IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version +must be returned to core IBC. If the version cannot be negotiated, an error should be returned. Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release diff --git a/docs/ibc/middleware/develop.md b/docs/ibc/middleware/develop.md index b4cd037e5e6..1d75e3965a8 100644 --- a/docs/ibc/middleware/develop.md +++ b/docs/ibc/middleware/develop.md @@ -103,29 +103,32 @@ func OnChanOpenTry( channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { - // core/04-channel/types contains a helper function to split middleware and underlying app version - cpMiddlewareVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion) - middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) - if !isCompatible(cpMiddlewareVersion, middlewareVersion) { - return error - } - doCustomLogic() - - // call the underlying applications OnChanOpenTry callback - app.OnChanOpenTry( - ctx, - order, - connectionHops, - portID, - channelID, - channelCap, - counterparty, - cpAppVersion, // note we only pass counterparty app version here - appVersion, // only pass app version - ) +) (string, error) { + doCustomLogic() + + // core/04-channel/types contains a helper function to split middleware and underlying app version + cpMiddlewareVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion) + + // call the underlying applications OnChanOpenTry callback + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpAppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return err + } + + middlewareVersion := negotiateMiddlewareVersion(cpMiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version } func OnChanOpenAck( @@ -134,15 +137,15 @@ func OnChanOpenAck( channelID string, counterpartyVersion string, ) error { - // core/04-channel/types contains a helper function to split middleware and underlying app version - middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) - if !isCompatible(middlewareVersion) { - return error - } - doCustomLogic() + // core/04-channel/types contains a helper function to split middleware and underlying app version + middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) + if !isCompatible(middlewareVersion) { + return error + } + doCustomLogic() - // call the underlying applications OnChanOpenTry callback - app.OnChanOpenAck(ctx, portID, channelID, appVersion) + // call the underlying applications OnChanOpenTry callback + app.OnChanOpenAck(ctx, portID, channelID, appVersion) } func OnChanOpenConfirm( @@ -236,4 +239,4 @@ func SendPacket(appPacket channeltypes.Packet) { return ics4Keeper.SendPacket(packet) } -``` \ No newline at end of file +``` diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 7df1c617d1f..8baaa8eda1a 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -1819,14 +1819,15 @@ MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. ### MsgChannelOpenTry MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -on Chain B. +on Chain B. The version field within the Channel field has been deprecated. Its +value will be ignored by core IBC. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `port_id` | [string](#string) | | | | `previous_channel_id` | [string](#string) | | in the case of crossing hello's, when both chains call OpenInit, we need the channel identifier of the previous channel in state INIT | -| `channel` | [Channel](#ibc.core.channel.v1.Channel) | | | +| `channel` | [Channel](#ibc.core.channel.v1.Channel) | | NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. | | `counterparty_version` | [string](#string) | | | | `proof_init` | [bytes](#bytes) | | | | `proof_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | | diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 06a1cb938da..c847a9d3bc9 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -19,9 +19,26 @@ No genesis or in-place migrations are required when upgrading from v1 or v2 of i ## Chains ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../app_modules/interchain-accounts/overview.md) for more information. ## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negoitation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + ### Channel state will not be set before application callback The channel handshake logic has been reorganized within core IBC. @@ -42,7 +59,10 @@ Please review the [mock](../../testing/mock/ibc_module.go) and [transfer](../../ ## Relayers -- No relevant changes were made in this release. +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. ## IBC Light Clients diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module.go b/modules/apps/27-interchain-accounts/controller/ibc_module.go index 164d725eb93..1aa362a4247 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module.go @@ -65,10 +65,9 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion 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") } // OnChanOpenAck implements the IBCModule interface diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index 96cd7589464..5137606e764 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -236,12 +236,13 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenTry() { chanCap, found := suite.chainA.App.GetScopedIBCKeeper().GetCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) suite.Require().True(found) - err = cbs.OnChanOpenTry( + version, err := cbs.OnChanOpenTry( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, - counterparty, path.EndpointA.ChannelConfig.Version, path.EndpointB.ChannelConfig.Version, + counterparty, path.EndpointB.ChannelConfig.Version, ) suite.Require().Error(err) + suite.Require().Equal("", version) } func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index 3cdaabc1447..bf847349c69 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -47,14 +47,13 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { if !im.keeper.IsHostEnabled(ctx) { - return types.ErrHostSubModuleDisabled + return "", types.ErrHostSubModuleDisabled } - return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) + return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) } // OnChanOpenAck implements the IBCModule interface diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index e0f7f82e3d6..1f76d661a6c 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -147,16 +147,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { // mock module callback should not be called on host side suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenTry = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, version, counterpartyVersion string, - ) error { - return fmt.Errorf("mock ica auth fails") + counterparty channeltypes.Counterparty, counterpartyVersion string, + ) (string, error) { + return "", fmt.Errorf("mock ica auth fails") } }, true, }, { - "ICA callback fails - invalid version", func() { - channel.Version = icatypes.VersionPrefix + "ICA callback fails - invalid channel order", func() { + channel.Ordering = channeltypes.UNORDERED }, false, }, } @@ -181,7 +181,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { Ordering: channeltypes.ORDERED, Counterparty: counterparty, ConnectionHops: []string{path.EndpointB.ConnectionID}, - Version: TestVersion, + Version: "", } tc.malleate() @@ -198,14 +198,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { cbs, ok := suite.chainB.App.GetIBCKeeper().Router.GetRoute(module) suite.Require().True(ok) - err = cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), path.EndpointA.ChannelConfig.Version, + version, err := cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, path.EndpointA.ChannelConfig.Version, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(TestVersion, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go index 0b06ad0042e..bb65da4a33b 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -14,6 +14,8 @@ import ( // OnChanOpenTry performs basic validation of the ICA channel // and registers a new interchain account (if it doesn't exist). +// The version returned will include the registered interchain +// account address. func (k Keeper) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -22,60 +24,47 @@ func (k Keeper) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { if order != channeltypes.ORDERED { - return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) + return "", sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) } if portID != icatypes.PortID { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", icatypes.PortID, portID) + return "", sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", icatypes.PortID, portID) } connSequence, err := icatypes.ParseHostConnSequence(counterparty.PortId) if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) + return "", sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) } counterpartyConnSequence, err := icatypes.ParseControllerConnSequence(counterparty.PortId) if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) + return "", sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) } if err := k.validateControllerPortParams(ctx, connectionHops, connSequence, counterpartyConnSequence); err != nil { - return sdkerrors.Wrapf(err, "failed to validate controller port %s", counterparty.PortId) - } - - if err := icatypes.ValidateVersion(version); err != nil { - return sdkerrors.Wrap(err, "version validation failed") + return "", sdkerrors.Wrapf(err, "failed to validate controller port %s", counterparty.PortId) } if counterpartyVersion != icatypes.VersionPrefix { - return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.VersionPrefix, version) + return "", sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.VersionPrefix, counterpartyVersion) } // On the host chain the capability may only be claimed during the OnChanOpenTry // The capability being claimed in OpenInit is for a controller chain (the port is different) if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return sdkerrors.Wrapf(err, "failed to claim capability for channel %s on port %s", channelID, portID) + return "", sdkerrors.Wrapf(err, "failed to claim capability for channel %s on port %s", channelID, portID) } - // Check to ensure that the version string contains the expected address generated from the Counterparty portID accAddr := icatypes.GenerateAddress(k.accountKeeper.GetModuleAddress(icatypes.ModuleName), counterparty.PortId) - parsedAddr, err := icatypes.ParseAddressFromVersion(version) - if err != nil { - return sdkerrors.Wrapf(err, "expected format , got %s", icatypes.Delimiter, version) - } - - if parsedAddr != accAddr.String() { - return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "version contains invalid account address: expected %s, got %s", parsedAddr, accAddr) - } // Register interchain account if it does not already exist k.RegisterInterchainAccount(ctx, accAddr, counterparty.PortId) + version := icatypes.NewAppVersion(icatypes.VersionPrefix, accAddr.String()) - return nil + return version, nil } // OnChanOpenConfirm completes the handshake process by setting the active channel in state on the host chain diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go index 03346efa1a1..48179f1a8a0 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -81,14 +81,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "invalid version", - func() { - channel.Version = "version" - path.EndpointB.SetChannel(*channel) - }, - false, - }, { "invalid counterparty version", func() { @@ -106,17 +98,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "invalid account address", - func() { - portID, err := icatypes.GeneratePortID("invalid-owner-addr", "connection-0", "connection-0") - suite.Require().NoError(err) - - channel.Counterparty.PortId = portID - path.EndpointB.SetChannel(*channel) - }, - false, - }, } for _, tc := range testCases { @@ -151,15 +132,16 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { tc.malleate() // malleate mutates test data - err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), - counterpartyVersion, + version, err := suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, counterpartyVersion, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(TestVersion, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) } diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index e8cffcdb640..102cb278ebe 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -50,7 +50,7 @@ func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED path.EndpointA.ChannelConfig.Version = icatypes.VersionPrefix - path.EndpointB.ChannelConfig.Version = TestVersion + path.EndpointB.ChannelConfig.Version = icatypes.VersionPrefix return path } diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go index b8e4ec54ba7..ef8709b5ce1 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -250,6 +250,8 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { transferPath := ibctesting.NewPath(suite.chainB, suite.chainC) transferPath.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort transferPath.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + transferPath.EndpointA.ChannelConfig.Version = transfertypes.Version + transferPath.EndpointB.ChannelConfig.Version = transfertypes.Version suite.coordinator.Setup(transferPath) diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index a9c7cdc63e5..1ad67d16a85 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -37,7 +37,6 @@ func ValidateTransferChannelParams( order channeltypes.Order, portID string, channelID string, - version string, ) error { // NOTE: for escrow address security only 2^32 channels are allowed to be created // Issue: https://github.com/cosmos/cosmos-sdk/issues/7737 @@ -58,9 +57,6 @@ func ValidateTransferChannelParams( return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) } - if version != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) - } return nil } @@ -75,10 +71,14 @@ func (im IBCModule) OnChanOpenInit( counterparty channeltypes.Counterparty, version string, ) error { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID, version); err != nil { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { return err } + if 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 @@ -87,7 +87,7 @@ func (im IBCModule) OnChanOpenInit( return nil } -// OnChanOpenTry implements the IBCModule interface +// OnChanOpenTry implements the IBCModule interface. func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -96,15 +96,14 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID, version); err != nil { - return err +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err } if counterpartyVersion != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) } // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos @@ -114,11 +113,11 @@ func (im IBCModule) OnChanOpenTry( if !im.keeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) { // Only claim channel capability passed back by IBC module if we do not already own it if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err + return "", err } } - return nil + return types.Version, nil } // OnChanOpenAck implements the IBCModule interface diff --git a/modules/apps/transfer/ibc_module_test.go b/modules/apps/transfer/ibc_module_test.go index 7ab67b0af06..9ffd7ddcf4f 100644 --- a/modules/apps/transfer/ibc_module_test.go +++ b/modules/apps/transfer/ibc_module_test.go @@ -135,11 +135,6 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { path.EndpointA.ChannelConfig.PortID = ibctesting.MockPort }, false, }, - { - "invalid version", func() { - channel.Version = "version" - }, false, - }, { "invalid counterparty version", func() { counterpartyVersion = "version" @@ -178,14 +173,16 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { tc.malleate() // explicitly change fields in channel and testChannel - err = cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), counterpartyVersion, + version, err := cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, counterpartyVersion, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(types.Version, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) diff --git a/modules/apps/transfer/keeper/keeper_test.go b/modules/apps/transfer/keeper/keeper_test.go index ef748757588..0fdb1121387 100644 --- a/modules/apps/transfer/keeper/keeper_test.go +++ b/modules/apps/transfer/keeper/keeper_test.go @@ -40,6 +40,8 @@ func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { path := ibctesting.NewPath(chainA, chainB) path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointA.ChannelConfig.Version = types.Version + path.EndpointB.ChannelConfig.Version = types.Version return path } diff --git a/modules/apps/transfer/transfer_test.go b/modules/apps/transfer/transfer_test.go index d2f822c83a2..fc16aa39a28 100644 --- a/modules/apps/transfer/transfer_test.go +++ b/modules/apps/transfer/transfer_test.go @@ -34,6 +34,8 @@ func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { path := ibctesting.NewPath(chainA, chainB) path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointA.ChannelConfig.Version = types.Version + path.EndpointB.ChannelConfig.Version = types.Version return path } diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go index e35ae4bd851..a9c5046c099 100644 --- a/modules/core/04-channel/keeper/handshake.go +++ b/modules/core/04-channel/keeper/handshake.go @@ -118,7 +118,6 @@ func (k Keeper) ChanOpenTry( previousChannelID string, portCap *capabilitytypes.Capability, counterparty types.Counterparty, - version, counterpartyVersion string, proofInit []byte, proofHeight exported.Height, @@ -148,7 +147,7 @@ func (k Keeper) ChanOpenTry( previousChannel.Counterparty.PortId == counterparty.PortId && previousChannel.Counterparty.ChannelId == "" && previousChannel.ConnectionHops[0] == connectionHops[0] && // ChanOpenInit will only set a single connection hop - previousChannel.Version == version) { + previousChannel.Version == counterpartyVersion) { return "", nil, sdkerrors.Wrap(types.ErrInvalidChannel, "channel fields mismatch previous channel fields") } diff --git a/modules/core/04-channel/keeper/handshake_test.go b/modules/core/04-channel/keeper/handshake_test.go index 1ed17c6d3fc..a9c24beb0aa 100644 --- a/modules/core/04-channel/keeper/handshake_test.go +++ b/modules/core/04-channel/keeper/handshake_test.go @@ -166,6 +166,19 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { previousChannelID = path.EndpointB.ChannelID portCap = suite.chainB.GetPortCapability(ibctesting.MockPort) }, true}, + {"previous channel with invalid version, crossing hello", func() { + suite.coordinator.SetupConnections(path) + path.SetChannelOrdered() + + // modify channel version + path.EndpointA.ChannelConfig.Version = "invalid version" + + err := suite.coordinator.ChanOpenInitOnBothChains(path) + suite.Require().NoError(err) + + previousChannelID = path.EndpointB.ChannelID + portCap = suite.chainB.GetPortCapability(ibctesting.MockPort) + }, false}, {"previous channel with invalid state", func() { suite.coordinator.SetupConnections(path) @@ -272,7 +285,7 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { channelID, cap, err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.ChanOpenTry( suite.chainB.GetContext(), types.ORDERED, []string{path.EndpointB.ConnectionID}, - path.EndpointB.ChannelConfig.PortID, previousChannelID, portCap, counterparty, path.EndpointB.ChannelConfig.Version, path.EndpointA.ChannelConfig.Version, + path.EndpointB.ChannelConfig.PortID, previousChannelID, portCap, counterparty, path.EndpointA.ChannelConfig.Version, proof, malleateHeight(proofHeight, heightDiff), ) diff --git a/modules/core/04-channel/types/msgs.go b/modules/core/04-channel/types/msgs.go index d940eb9ae8c..b6e625ff32e 100644 --- a/modules/core/04-channel/types/msgs.go +++ b/modules/core/04-channel/types/msgs.go @@ -61,6 +61,8 @@ func (msg MsgChannelOpenInit) GetSigners() []sdk.AccAddress { var _ sdk.Msg = &MsgChannelOpenTry{} // NewMsgChannelOpenTry creates a new MsgChannelOpenTry instance +// The version string is deprecated and will be ignored by core IBC. +// It is left as an argument for go API backwards compatibility. // nolint:interfacer func NewMsgChannelOpenTry( portID, previousChannelID, version string, channelOrder Order, connectionHops []string, diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index 1a007540a64..2d3c75345ce 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -108,12 +108,14 @@ func (m *MsgChannelOpenInitResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgChannelOpenInitResponse proto.InternalMessageInfo // MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -// on Chain B. +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. type MsgChannelOpenTry struct { PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty" yaml:"port_id"` // in the case of crossing hello's, when both chains call OpenInit, we need // the channel identifier of the previous channel in state INIT - PreviousChannelId string `protobuf:"bytes,2,opt,name=previous_channel_id,json=previousChannelId,proto3" json:"previous_channel_id,omitempty" yaml:"previous_channel_id"` + PreviousChannelId string `protobuf:"bytes,2,opt,name=previous_channel_id,json=previousChannelId,proto3" json:"previous_channel_id,omitempty" yaml:"previous_channel_id"` + // NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. Channel Channel `protobuf:"bytes,3,opt,name=channel,proto3" json:"channel"` CounterpartyVersion string `protobuf:"bytes,4,opt,name=counterparty_version,json=counterpartyVersion,proto3" json:"counterparty_version,omitempty" yaml:"counterparty_version"` ProofInit []byte `protobuf:"bytes,5,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty" yaml:"proof_init"` diff --git a/modules/core/05-port/types/module.go b/modules/core/05-port/types/module.go index 5d580365760..9c7442a9d76 100644 --- a/modules/core/05-port/types/module.go +++ b/modules/core/05-port/types/module.go @@ -11,6 +11,10 @@ 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( ctx sdk.Context, order channeltypes.Order, @@ -22,6 +26,14 @@ type IBCModule interface { version string, ) error + // OnChanOpenTry will verify the relayer-chosen parameters along with the + // counterparty-chosen version string and perform custom TRY logic. + // If the relayer-chosen parameters are invalid, the callback must return + // an error to abort the handshake. If the counterparty-chosen version is not + // compatible with this modules supported versions, the callback must return + // an error to abort the handshake. If the versions are compatible, the try callback + // must select the final version string and return it to core IBC. + // OnChanOpenTry may also perform custom initialization logic OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -30,10 +42,11 @@ type IBCModule interface { channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, - ) error + ) (version string, err error) + // OnChanOpenAck will error if the counterparty selected version string + // is invalid to abort the handshake. It may also perform custom ACK logic. OnChanOpenAck( ctx sdk.Context, portID, @@ -41,6 +54,7 @@ type IBCModule interface { counterpartyVersion string, ) error + // OnChanOpenConfirm will perform custom CONFIRM logic and may error to abort the handshake. OnChanOpenConfirm( ctx sdk.Context, portID, diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index 039167da7a0..dbc4ac07812 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -312,19 +312,20 @@ func (k Keeper) ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChann // Perform 04-channel verification channelID, cap, err := k.ChannelKeeper.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.PreviousChannelId, - portCap, msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight, + portCap, msg.Channel.Counterparty, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight, ) if err != nil { return nil, sdkerrors.Wrap(err, "channel handshake open try failed") } // Perform application logic callback - if err = cbs.OnChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion); err != nil { + version, err := cbs.OnChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.CounterpartyVersion) + if err != nil { return nil, sdkerrors.Wrap(err, "channel open try callback failed") } // Write channel into state - k.ChannelKeeper.WriteOpenTryChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, msg.Channel.Version) + k.ChannelKeeper.WriteOpenTryChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version) return &channeltypes.MsgChannelOpenTryResponse{}, nil } diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index c9322c1def7..4f28418c9a2 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -57,7 +57,8 @@ message MsgChannelOpenInit { message MsgChannelOpenInitResponse {} // MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -// on Chain B. +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. message MsgChannelOpenTry { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; @@ -65,7 +66,8 @@ message MsgChannelOpenTry { string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; // in the case of crossing hello's, when both chains call OpenInit, we need // the channel identifier of the previous channel in state INIT - string previous_channel_id = 2 [(gogoproto.moretags) = "yaml:\"previous_channel_id\""]; + string previous_channel_id = 2 [(gogoproto.moretags) = "yaml:\"previous_channel_id\""]; + // NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. Channel channel = 3 [(gogoproto.nullable) = false]; string counterparty_version = 4 [(gogoproto.moretags) = "yaml:\"counterparty_version\""]; bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""]; diff --git a/testing/endpoint.go b/testing/endpoint.go index a2ab014fe99..4c3804879c9 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -306,6 +306,10 @@ func (endpoint *Endpoint) ChanOpenTry() error { require.NoError(endpoint.Chain.t, err) } + // update version to selected app version + // NOTE: this update must be performed after the endpoint channelID is set + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + return nil } diff --git a/testing/mock/ibc_app.go b/testing/mock/ibc_app.go index 1ba3d7b7378..a3f2db3bc6d 100644 --- a/testing/mock/ibc_app.go +++ b/testing/mock/ibc_app.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/ibc-go/v3/modules/core/exported" ) +// MockIBCApp contains IBC application module callbacks as defined in 05-port. type MockIBCApp struct { OnChanOpenInit func( ctx sdk.Context, @@ -28,9 +29,8 @@ type MockIBCApp struct { channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, - ) error + ) (version string, err error) OnChanOpenAck func( ctx sdk.Context, diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index 36f4bcd3182..84e57b8f25d 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -48,18 +48,18 @@ func (im IBCModule) OnChanOpenInit( // OnChanOpenTry implements the IBCModule interface. func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, - channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version, counterpartyVersion string, -) error { + channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { if im.IBCApp.OnChanOpenTry != nil { - return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) } // Claim channel capability passed back by IBC module if err := im.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err + return "", err } - return nil + return Version, nil } // OnChanOpenAck implements the IBCModule interface. diff --git a/testing/values.go b/testing/values.go index e341b98fd50..8dbfa66d3ab 100644 --- a/testing/values.go +++ b/testing/values.go @@ -29,7 +29,7 @@ const ( MaxClockDrift time.Duration = time.Second * 10 DefaultDelayPeriod uint64 = 0 - DefaultChannelVersion = ibctransfertypes.Version + DefaultChannelVersion = mock.Version InvalidID = "IDisInvalid" // Application Ports