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: move ica connection identifiers from port to version metadata bytestring #700

Merged
merged 18 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e13b08c
define and generate new metadata proto type
damiannolan Jan 7, 2022
a5e9947
refactor types pkg, remove version string parsers, add PortPrefix
damiannolan Jan 7, 2022
a79ab75
refactor ica entrypoint and handshake to handle connection ids in met…
damiannolan Jan 7, 2022
f14d4dd
fixing broken test cases
damiannolan Jan 7, 2022
b8b0cbb
adding controller port and metadata validation, adding new testcases
damiannolan Jan 7, 2022
d5380ee
updating proto doc and removing commented out code
damiannolan Jan 7, 2022
873d8cd
Merge branch 'main' into damian/615-refactor-ica-connection-ids
damiannolan Jan 7, 2022
07a506d
updating testcase names, adding metadata constructor func, updating P…
damiannolan Jan 10, 2022
dd95566
Merge branch 'main' into damian/615-refactor-ica-connection-ids
damiannolan Jan 10, 2022
264b307
adding ErrInvalidControllerPort and ErrInvalidHostPort
damiannolan Jan 10, 2022
777ab3b
Merge branch 'damian/615-refactor-ica-connection-ids' of github.com:c…
damiannolan Jan 10, 2022
1e5bf8a
Apply suggestions from code review
damiannolan Jan 11, 2022
a3d1660
Merge branch 'main' into damian/615-refactor-ica-connection-ids
damiannolan Jan 11, 2022
661502d
updating error msgs to use expected, got format
damiannolan Jan 11, 2022
d02efed
adding inline metadata documentation as per review, updating bz to ve…
damiannolan Jan 11, 2022
52863e2
Merge branch 'main' into damian/615-refactor-ica-connection-ids
damiannolan Jan 11, 2022
9ec698c
Merge branch 'main' into damian/615-refactor-ica-connection-ids
crodriguezvega Jan 13, 2022
2ce783e
Merge branch 'main' into damian/615-refactor-ica-connection-ids
damiannolan Jan 13, 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
38 changes: 38 additions & 0 deletions docs/ibc/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
- [HostGenesisState](#ibc.applications.interchain_accounts.v1.HostGenesisState)
- [RegisteredInterchainAccount](#ibc.applications.interchain_accounts.v1.RegisteredInterchainAccount)

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

- [ibc/applications/interchain_accounts/v1/packet.proto](#ibc/applications/interchain_accounts/v1/packet.proto)
- [CosmosTx](#ibc.applications.interchain_accounts.v1.CosmosTx)
- [InterchainAccountPacketData](#ibc.applications.interchain_accounts.v1.InterchainAccountPacketData)
Expand Down Expand Up @@ -388,6 +391,41 @@ RegisteredInterchainAccount contains a pairing of controller port ID and associa



<!-- end messages -->

<!-- end enums -->

<!-- end HasExtensions -->

<!-- end services -->



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

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



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

### Metadata
Metadata defines a set of protocol specific data encoded into the ICS27 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) | | |
| `controller_connection_id` | [string](#string) | | |
| `host_connection_id` | [string](#string) | | |
| `address` | [string](#string) | | |





<!-- end messages -->

<!-- end enums -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,24 @@ import (
)

var (
// TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module()
// https://github.com/cosmos/cosmos-sdk/issues/10225
//
// TestAccAddress defines a resuable bech32 address for testing purposes
// TODO: update crypto.AddressHash() when sdk uses address.Module()
TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), TestPortID)

// TestOwnerAddress defines a reusable bech32 address for testing purposes
TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs"

// TestPortID defines a resuable port identifier for testing purposes
TestPortID, _ = icatypes.GeneratePortID(TestOwnerAddress, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID)
TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress)

// TestVersion defines a resuable interchainaccounts version string for testing purposes
TestVersion = icatypes.NewAppVersion(icatypes.VersionPrefix, TestAccAddress.String())
TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{
Version: icatypes.Version,
ControllerConnectionId: ibctesting.FirstConnectionID,
HostConnectionId: ibctesting.FirstConnectionID,
}))
)

type InterchainAccountsTestSuite struct {
Expand Down Expand Up @@ -55,21 +64,21 @@ func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path {
path.EndpointB.ChannelConfig.PortID = icatypes.PortID
path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED
path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED
path.EndpointA.ChannelConfig.Version = icatypes.VersionPrefix
path.EndpointA.ChannelConfig.Version = TestVersion
path.EndpointB.ChannelConfig.Version = TestVersion

return path
}

func InitInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error {
portID, err := icatypes.GeneratePortID(owner, endpoint.ConnectionID, endpoint.Counterparty.ConnectionID)
portID, err := icatypes.NewControllerPortID(owner)
if err != nil {
return err
}

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

if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, owner); err != nil {
if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.InitInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner); err != nil {
return err
}

Expand Down Expand Up @@ -150,7 +159,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
suite.coordinator.SetupConnections(path)

// mock init interchain account
portID, err := icatypes.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID)
portID, err := icatypes.NewControllerPortID(TestOwnerAddress)
suite.Require().NoError(err)

portCap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), portID)
Expand All @@ -166,7 +175,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
Ordering: channeltypes.ORDERED,
Counterparty: counterparty,
ConnectionHops: []string{path.EndpointA.ConnectionID},
Version: icatypes.VersionPrefix,
Version: path.EndpointA.ChannelConfig.Version,
}

tc.malleate() // malleate mutates test data
Expand Down Expand Up @@ -219,7 +228,7 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenTry() {
proofInit, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)

// use chainA (controller) for ChanOpenTry
msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, TestVersion, channeltypes.ORDERED, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, icatypes.VersionPrefix, proofInit, proofHeight, icatypes.ModuleName)
msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, TestVersion, channeltypes.ORDERED, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, TestVersion, proofInit, proofHeight, icatypes.ModuleName)
handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainA.GetContext(), msg)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
// call 04-channel 'ChanOpenInit'. An error is returned if the port identifier is
// already in use. Gaining access to interchain accounts whose channels have closed
// cannot be done with this function. A regular MsgChanOpenInit must be used.
func (k Keeper) InitInterchainAccount(ctx sdk.Context, connectionID, counterpartyConnectionID, owner string) error {
portID, err := icatypes.GeneratePortID(owner, connectionID, counterpartyConnectionID)
func (k Keeper) InitInterchainAccount(ctx sdk.Context, connectionID, owner string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed counterpartyConnectionID as it can be retrieved using the channelKeeper below. This reduces the burden on developers implementing their own authentication modules

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice change. We need to update the spec should we change this @AdityaSripal @crodriguezvega

Likewise one of us should update the ICA repo + hub teams PR (the CLI commands take two connections etc)

Copy link
Contributor

Choose a reason for hiding this comment

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

We also need to update the spec to explain the json-encoded format of the version string, right?

portID, err := icatypes.NewControllerPortID(owner)
if err != nil {
return err
}
Expand All @@ -30,7 +30,18 @@ func (k Keeper) InitInterchainAccount(ctx sdk.Context, connectionID, counterpart
return sdkerrors.Wrap(err, "unable to bind to newly generated portID")
}

msg := channeltypes.NewMsgChannelOpenInit(portID, icatypes.VersionPrefix, channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName)
connectionEnd, err := k.channelKeeper.GetConnection(ctx, connectionID)
if err != nil {
return err
}

metadata := icatypes.NewMetadata(icatypes.Version, connectionID, connectionEnd.GetCounterparty().GetConnectionID(), "")
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, this is the new JSON metadata in the version field.

I get it and like the design

bz, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
Copy link
Member

Choose a reason for hiding this comment

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

nit: in this case I think it's more readable to name bz to versionBytes or something similar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

if err != nil {
return err
}

msg := channeltypes.NewMsgChannelOpenInit(portID, string(bz), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName)
handler := k.msgRouter.Handler(msg)

res, err := handler(ctx, msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (suite *KeeperTestSuite) TestInitInterchainAccount() {
{
"MsgChanOpenInit fails - channel is already active",
func() {
portID, err := icatypes.GeneratePortID(owner, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID)
portID, err := icatypes.NewControllerPortID(TestOwnerAddress)
suite.Require().NoError(err)

suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), portID, path.EndpointA.ChannelID)
Expand All @@ -59,7 +59,7 @@ func (suite *KeeperTestSuite) TestInitInterchainAccount() {

tc.malleate() // malleate mutates test data

err = suite.chainA.GetSimApp().ICAControllerKeeper.InitInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, path.EndpointB.ConnectionID, owner)
err = suite.chainA.GetSimApp().ICAControllerKeeper.InitInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner)

if tc.expPass {
suite.Require().NoError(err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

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 @@ -32,26 +34,25 @@ func (k Keeper) OnChanOpenInit(
return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order)
}

connSequence, err := icatypes.ParseControllerConnSequence(portID)
if err != nil {
return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, portID)
if !strings.HasPrefix(portID, icatypes.PortPrefix) {
return sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.PortPrefix, portID)
}

counterpartyConnSequence, err := icatypes.ParseHostConnSequence(portID)
if err != nil {
return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, portID)
if counterparty.PortId != icatypes.PortID {
return sdkerrors.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.PortID, counterparty.PortId)
}

if err := k.validateControllerPortParams(ctx, connectionHops, connSequence, counterpartyConnSequence); err != nil {
return sdkerrors.Wrapf(err, "failed to validate controller port %s", portID)
var metadata icatypes.Metadata
if err := icatypes.ModuleCdc.UnmarshalJSON([]byte(version), &metadata); err != nil {
return sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain accounts metadata")
}

if counterparty.PortId != icatypes.PortID {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", icatypes.PortID, counterparty.PortId)
if err := k.validateConnectionParams(ctx, connectionHops, metadata.ControllerConnectionId, metadata.HostConnectionId); err != nil {
return err
}

if version != icatypes.VersionPrefix {
return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.VersionPrefix, version)
if metadata.Version != icatypes.Version {
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 want to allow relayers to pass in empty string for version here? Or is this a future improvement?

ref: https://github.com/cosmos/ibc/pull/629/files

cc: @colin-axner

This can also be implemented in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Am I missing something here? OnChanOpenInit will never be invoked by relayers directly, right?
Otherwise, you'll potentially end up in a situation with an open channel with no port bound/capability claimed?

We should probably be adding a defensive check to OnChanOpenInit to error if a controller port is not bound.

Copy link
Contributor

Choose a reason for hiding this comment

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

OnChanOpenInit can be invoked directly by relayers in the case where the port is bound (InitInterchainAccount has been called) + a channel is not the active channel. If a channel closes, a relayer will be able to create a new channel on bound port.

We should probably be adding a defensive check to OnChanOpenInit to error if a controller port is not bound.

As far as I know, this is handled by core IBC already. You can't open a channel on a port that is not bound already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, it would be handled by core here in the case where a port is not bound 👍

return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.Version, metadata.Version)
}

activeChannelID, found := k.GetActiveChannelID(ctx, portID)
Expand All @@ -71,21 +72,28 @@ func (k Keeper) OnChanOpenAck(
counterpartyVersion string,
) error {
if portID == icatypes.PortID {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "portID cannot be host chain port ID: %s", icatypes.PortID)
return sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "portID cannot be host chain port ID: %s", icatypes.PortID)
}

if err := icatypes.ValidateVersion(counterpartyVersion); err != nil {
return sdkerrors.Wrap(err, "counterparty version validation failed")
if !strings.HasPrefix(portID, icatypes.PortPrefix) {
return sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.PortPrefix, portID)
}

k.SetActiveChannelID(ctx, portID, channelID)
var metadata icatypes.Metadata
Copy link
Contributor

Choose a reason for hiding this comment

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

Would there be value in adding a validateMetadata fn and using it here? Also wondering should we use a constructor fn for creating (add to types)

Copy link
Contributor Author

@damiannolan damiannolan Jan 10, 2022

Choose a reason for hiding this comment

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

I thought about this myself when I was implementing it, but I came to the conclusion that it wouldn't be so reusable as we need to carry out metadata validation in OnChanOpenAck as well (address/version), and we do not have a ref to connectionHops at that point in the handshake flow.

I've added NewMetadata(...) to types also!

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we still need to validate connection sequences here? It is possible the host chain chooses a bad metadata, followup issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I can address this in a followup PR, with the moving of validateConnectionParams to types.

I assume retrieving the channel from state using channelKeeper, and passing along it's connectionHops to ValidateConnectionParams should do the trick 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Figured it made more sense to move this to a ValidateMetadata function in the end, since connectionHops can be retrieved from the channel stored in state for ChanOpenAck. I can open a follow up PR with the changes when this is merged.

if err := icatypes.ModuleCdc.UnmarshalJSON([]byte(counterpartyVersion), &metadata); err != nil {
return sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain accounts metadata")
}

accAddr, err := icatypes.ParseAddressFromVersion(counterpartyVersion)
if err != nil {
return sdkerrors.Wrapf(err, "expected format <app-version%saccount-address>, got %s", icatypes.Delimiter, counterpartyVersion)
if err := icatypes.ValidateAccountAddress(metadata.Address); err != nil {
return err
}

k.SetInterchainAccountAddress(ctx, portID, accAddr)
if metadata.Version != icatypes.Version {
return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.Version, metadata.Version)
}

k.SetActiveChannelID(ctx, portID, channelID)
k.SetInterchainAccountAddress(ctx, portID, metadata.Address)

return nil
}
Expand All @@ -102,31 +110,20 @@ func (k Keeper) OnChanCloseConfirm(
return nil
}

// validateControllerPortParams asserts the provided connection sequence and counterparty connection sequence
// match that of the associated connection stored in state
func (k Keeper) validateControllerPortParams(ctx sdk.Context, connectionHops []string, connectionSeq, counterpartyConnectionSeq uint64) error {
// validateConnectionParams asserts the provided controller and host connection identifiers match that of the associated connection stored in state
func (k Keeper) validateConnectionParams(ctx sdk.Context, connectionHops []string, controllerConnectionID, hostConnectionID string) error {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
connectionID := connectionHops[0]
connection, err := k.channelKeeper.GetConnection(ctx, connectionID)
if err != nil {
return err
}

connSeq, err := connectiontypes.ParseConnectionSequence(connectionID)
if err != nil {
return sdkerrors.Wrapf(err, "failed to parse connection sequence %s", connectionID)
}

counterpartyConnSeq, err := connectiontypes.ParseConnectionSequence(connection.GetCounterparty().GetConnectionID())
if err != nil {
return sdkerrors.Wrapf(err, "failed to parse counterparty connection sequence %s", connection.GetCounterparty().GetConnectionID())
}

if connSeq != connectionSeq {
return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "sequence mismatch, expected %d, got %d", connSeq, connectionSeq)
if controllerConnectionID != connectionID {
return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "expected %s, got %s", connectionID, controllerConnectionID)
}

if counterpartyConnSeq != counterpartyConnectionSeq {
return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "counterparty sequence mismatch, expected %d, got %d", counterpartyConnSeq, counterpartyConnectionSeq)
if hostConnectionID != connection.GetCounterparty().GetConnectionID() {
return sdkerrors.Wrapf(connectiontypes.ErrInvalidConnection, "expected %s, got %s", connection.GetCounterparty().GetConnectionID(), hostConnectionID)
}

return nil
Expand Down
Loading