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: ics29 json encoded version metadata #883

Merged
merged 6 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
36 changes: 36 additions & 0 deletions docs/ibc/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
- [GenesisState](#ibc.applications.fee.v1.GenesisState)
- [RegisteredRelayerAddress](#ibc.applications.fee.v1.RegisteredRelayerAddress)

- [ibc/applications/fee/v1/metadata.proto](#ibc/applications/fee/v1/metadata.proto)
- [Metadata](#ibc.applications.fee.v1.Metadata)

- [ibc/applications/fee/v1/query.proto](#ibc/applications/fee/v1/query.proto)
- [QueryIncentivizedPacketRequest](#ibc.applications.fee.v1.QueryIncentivizedPacketRequest)
- [QueryIncentivizedPacketResponse](#ibc.applications.fee.v1.QueryIncentivizedPacketResponse)
Expand Down Expand Up @@ -812,6 +815,39 @@ RegisteredRelayerAddress contains the address and counterparty address for a spe



<!-- end messages -->

<!-- end enums -->

<!-- end HasExtensions -->

<!-- end services -->



<a name="ibc/applications/fee/v1/metadata.proto"></a>
<p align="right"><a href="#top">Top</a></p>

## ibc/applications/fee/v1/metadata.proto



<a name="ibc.applications.fee.v1.Metadata"></a>

### Metadata
Metadata defines the ICS29 channel specific metadata encoded into the channel version bytestring
See ICS004: https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#Versioning


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `version` | [string](#string) | | version defines the ICS29 fee version |
| `app_version` | [string](#string) | | app_version defines the underlying application version, which may or may not be a JSON encoded bytestring |





<!-- end messages -->

<!-- end enums -->
Expand Down
2 changes: 1 addition & 1 deletion modules/apps/29-fee/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (suite *FeeTestSuite) SetupTest() {
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))

path := ibctesting.NewPath(suite.chainA, suite.chainB)
feeTransferVersion := channeltypes.MergeChannelVersions(types.Version, transfertypes.Version)
feeTransferVersion := string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version}))
path.EndpointA.ChannelConfig.Version = feeTransferVersion
path.EndpointB.ChannelConfig.Version = feeTransferVersion
path.EndpointA.ChannelConfig.PortID = transfertypes.PortID
Expand Down
76 changes: 43 additions & 33 deletions modules/apps/29-fee/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,24 @@ func (im IBCModule) OnChanOpenInit(
counterparty channeltypes.Counterparty,
version string,
) error {
mwVersion, appVersion := channeltypes.SplitChannelVersion(version)
// 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.
// If an invalid fee version was passed, we expect the underlying application to fail on its version negotiation.
if mwVersion == types.Version {
im.keeper.SetFeeEnabled(ctx, portID, channelID)
} else {
// middleware version is not the expected version for this midddleware. Pass the full version string along,
// if it not valid version for any other lower middleware, an error will be returned by base application.
appVersion = version
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 versionMetadata.Version != types.Version {
return sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.Version, versionMetadata.Version)
}

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, appVersion)
chanCap, counterparty, versionMetadata.AppVersion)
}

// OnChanOpenTry implements the IBCModule interface
Expand All @@ -68,31 +70,34 @@ func (im IBCModule) OnChanOpenTry(
counterparty channeltypes.Counterparty,
counterpartyVersion string,
) (string, error) {
cpMwVersion, cpAppVersion := channeltypes.SplitChannelVersion(counterpartyVersion)
var versionMetadata types.Metadata
if err := types.ModuleCdc.UnmarshalJSON([]byte(counterpartyVersion), &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.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion)
}

// 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.
// If an invalid fee version was passed, we expect the underlying application to fail on its version negotiation.
if cpMwVersion == types.Version {
im.keeper.SetFeeEnabled(ctx, portID, channelID)
} else {
// middleware versions are not the expected version for this midddleware. Pass the full version strings along,
// if it not valid version for any other lower middleware, an error will be returned by base application.
cpAppVersion = counterpartyVersion
if versionMetadata.Version != types.Version {
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "expected %s, got %s", types.Version, versionMetadata.Version)
}

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

// call underlying app's OnChanOpenTry callback with the app versions
appVersion, err := im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, cpAppVersion)
appVersion, err := im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, versionMetadata.AppVersion)
if err != nil {
return "", err
}

if !im.keeper.IsFeeEnabled(ctx, portID, channelID) {
return appVersion, nil
versionMetadata.AppVersion = appVersion

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

return channeltypes.MergeChannelVersions(types.Version, appVersion), nil
return string(versionBytes), nil
}

// OnChanOpenAck implements the IBCModule interface
Expand All @@ -104,17 +109,22 @@ func (im IBCModule) OnChanOpenAck(
) error {
// If handshake was initialized with fee enabled it must complete with fee enabled.
// If handshake was initialized with fee disabled it must complete with fee disabled.
cpAppVersion := counterpartyVersion
if im.keeper.IsFeeEnabled(ctx, portID, channelID) {
var cpFeeVersion string
cpFeeVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion)
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")
}

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

// call underlying app's OnChanOpenAck callback with the counterparty app version.
return im.app.OnChanOpenAck(ctx, portID, channelID, versionMetadata.AppVersion)
}

// call underlying app's OnChanOpenAck callback with the counterparty app version.
return im.app.OnChanOpenAck(ctx, portID, channelID, cpAppVersion)
return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyVersion)
}

// OnChanOpenConfirm implements the IBCModule interface
Expand Down
50 changes: 22 additions & 28 deletions modules/apps/29-fee/ibc_module_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package fee_test

import (
"fmt"

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

Expand All @@ -28,40 +26,30 @@ func (suite *FeeTestSuite) TestOnChanOpenInit() {
expPass bool
}{
{
"valid fee middleware and transfer version",
channeltypes.MergeChannelVersions(types.Version, transfertypes.Version),
"success - valid fee middleware and transfer version",
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version})),
true,
},
{
"fee version not included, only perform transfer logic",
"success - fee version not included, only perform transfer logic",
transfertypes.Version,
true,
},
{
"invalid fee middleware version",
channeltypes.MergeChannelVersions("otherfee28-1", transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: "invalid-ics29-1", AppVersion: transfertypes.Version})),
false,
},
{
"invalid transfer version",
channeltypes.MergeChannelVersions(types.Version, "wrongics20-1"),
false,
},
{
"incorrect wrapping delimiter",
fmt.Sprintf("%s//%s", types.Version, transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: "invalid-ics20-1"})),
false,
},
{
"transfer version not wrapped",
types.Version,
false,
},
{
"hanging delimiter",
fmt.Sprintf("%s:%s:", types.Version, transfertypes.Version),
false,
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -112,32 +100,32 @@ func (suite *FeeTestSuite) TestOnChanOpenTry() {
expPass bool
}{
{
"valid fee middleware version",
channeltypes.MergeChannelVersions(types.Version, transfertypes.Version),
"success - valid fee middleware version",
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version})),
false,
true,
},
{
"valid transfer version",
"success - valid transfer version",
transfertypes.Version,
false,
true,
},
{
"crossing hellos: valid fee middleware",
channeltypes.MergeChannelVersions(types.Version, transfertypes.Version),
"success - crossing hellos: valid fee middleware",
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version})),
true,
true,
},
{
"invalid fee middleware version",
channeltypes.MergeChannelVersions("wrongfee29-1", transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: "invalid-ics29-1", AppVersion: transfertypes.Version})),
false,
false,
},
{
"invalid transfer version",
channeltypes.MergeChannelVersions(types.Version, "wrongics20-1"),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: "invalid-ics20-1"})),
false,
false,
},
Expand Down Expand Up @@ -205,25 +193,31 @@ func (suite *FeeTestSuite) TestOnChanOpenAck() {
}{
{
"success",
channeltypes.MergeChannelVersions(types.Version, transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version})),
func(suite *FeeTestSuite) {},
true,
},
{
"invalid fee version",
channeltypes.MergeChannelVersions("fee29-A", transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: "invalid-ics29-1", AppVersion: transfertypes.Version})),
func(suite *FeeTestSuite) {},
false,
},
{
"invalid transfer version",
channeltypes.MergeChannelVersions(types.Version, "ics20-4"),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: "invalid-ics20-1"})),
func(suite *FeeTestSuite) {},
false,
},
{
"invalid version fails to unmarshal metadata",
"invalid-version",
func(suite *FeeTestSuite) {},
false,
},
{
"previous INIT set without fee, however counterparty set fee version", // note this can only happen with incompetent or malicious counterparty chain
channeltypes.MergeChannelVersions(types.Version, transfertypes.Version),
string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version})),
func(suite *FeeTestSuite) {
// do the first steps without fee version, then pass the fee version as counterparty version in ChanOpenACK
suite.path.EndpointA.ChannelConfig.Version = transfertypes.Version
Expand Down
2 changes: 1 addition & 1 deletion modules/apps/29-fee/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1))

path := ibctesting.NewPath(suite.chainA, suite.chainB)
feeTransferVersion := channeltypes.MergeChannelVersions(types.Version, transfertypes.Version)
feeTransferVersion := string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{Version: types.Version, AppVersion: transfertypes.Version}))
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we should add a MustMarshalJSON function to Metadata? or a function to return the marshaled metadata as a string (unsure what a good name would be)

Copy link
Member Author

Choose a reason for hiding this comment

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

Very much agree to be honest, this was on my mind when I was writing this. I'll think on it and add it in a follow up PR. It does start to look ugly seeing all the string conversions scattered around, I would prefer to just call some method on Metadata to do the job.

path.EndpointA.ChannelConfig.Version = feeTransferVersion
path.EndpointB.ChannelConfig.Version = feeTransferVersion
path.EndpointA.ChannelConfig.PortID = transfertypes.PortID
Expand Down
Loading