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

chore: allow ics27 to select and return default JSON encoded metadata #1550

Merged
merged 21 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
20d1003
adding distribution logic with new registered payees
damiannolan Jun 10, 2022
c25ca5f
updating ack test cases
damiannolan Jun 10, 2022
2d8d25f
removing commented out test
damiannolan Jun 10, 2022
3941f55
adding additional testcase to TestOnAcknowledgementPacket
damiannolan Jun 10, 2022
ae9fef6
Merge branch 'main' into damian/1477-implement-payee-distribution
chatton Jun 13, 2022
3247100
cleaning up distribution logic and adapting tests
damiannolan Jun 13, 2022
44c7293
updating timeout test cases
damiannolan Jun 13, 2022
0f3f132
Merge branch 'damian/1477-implement-payee-distribution' of github.com…
damiannolan Jun 13, 2022
64dab2c
updating timeout logic and adding application callback failure test c…
damiannolan Jun 13, 2022
76d0eef
Merge branch 'main' into damian/1477-implement-payee-distribution
damiannolan Jun 13, 2022
af201e0
Apply suggestions from code review
damiannolan Jun 15, 2022
f8928f6
Merge branch 'main' into damian/1477-implement-payee-distribution
damiannolan Jun 15, 2022
bf866c8
WIP handling empty version string for ics27
damiannolan Jun 16, 2022
909f2e5
Merge branch 'main' into damian/handle-ica-empty-version
damiannolan Jun 20, 2022
ffdfc56
updating testcases
damiannolan Jun 20, 2022
8975dd2
Merge branch 'main' into damian/handle-ica-empty-version
damiannolan Jun 20, 2022
9a50acc
adding test with auth module modifying channel version
damiannolan Jun 21, 2022
31e7455
Merge branch 'main' into damian/handle-ica-empty-version
damiannolan Jun 22, 2022
e078e9d
adding inline comments re. ignored channel version from auth module
damiannolan Jun 22, 2022
f08c217
updating with deduplication suggestion
damiannolan Jun 23, 2022
d9cbb0e
Merge branch 'main' into damian/handle-ica-empty-version
damiannolan Jun 23, 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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func (im IBCMiddleware) OnChanOpenInit(
return "", types.ErrControllerSubModuleDisabled
}

if err := im.keeper.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version); err != nil {
version, err := im.keeper.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version)
Copy link
Contributor

Choose a reason for hiding this comment

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

nice readability improvement 🥇

if err != nil {
return "", err
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
{
"success", func() {}, true,
},
{
"ICA auth module modification of channel version is ignored", func() {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
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,
) (string, error) {
return "invalid-version", nil
}
}, true,
},
{
"controller submodule disabled", func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false))
Expand Down Expand Up @@ -200,19 +210,9 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() {
)

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

expBytes, err := icatypes.ModuleCdc.MarshalJSON(&expMetadata)
suite.Require().NoError(err)
expMetadata := icatypes.NewDefaultMetadataString(path.EndpointA.ConnectionID, path.EndpointB.ConnectionID)

suite.Require().Equal(version, string(expBytes))
suite.Require().Equal(expMetadata, version)
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,35 @@ func (k Keeper) OnChanOpenInit(
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the implication of changing an OnChanOpenInit signature like this? Are we free to do this without causing any issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

In this instance yes, the OnChanOpenInit callback was already updated to include a second version return arg. The calling func in controller/ibc_middleware.go already contains this function signature which is the entrypoint of the module handshake essentially.

In other situations we may need to more careful changing function sigs but this doesn't really cause any issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

to add to that: in general, we would also update the ibc spec for changes to these fn sigs. But like @damiannolan said the calling func already has this signature.

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 !strings.HasPrefix(portID, icatypes.PortPrefix) {
return sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.PortPrefix, portID)
return "", sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.PortPrefix, portID)
}

if counterparty.PortId != icatypes.PortID {
return sdkerrors.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.PortID, counterparty.PortId)
return "", sdkerrors.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.PortID, counterparty.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 strings.TrimSpace(version) == "" {
connection, err := k.channelKeeper.GetConnection(ctx, connectionHops[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

why is connectionHops hardcoded to the first value of the array?

Copy link
Contributor

@seantking seantking Jun 23, 2022

Choose a reason for hiding this comment

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

The first value is the source chain connection end AFAIK

Copy link
Member Author

Choose a reason for hiding this comment

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

ConnectionHops is an ordered list of connection ids, so using connectionHops[0] here takes the first connection hop through which packets travel. There's a bunch of defensive checks in ibc core ensuring that this is always of length one at the moment. So on source chain connectionHops will contain the connection ID of self, in this instance its the controller chain. And likewise, the counterparty handlers would have a connectionHops array containing the connection of themself (host). Obviously in future with multi hop the semantics will change and ibc core will have to adapt for it. But due to the ordering, we can safely assume that the 0 index element is self.

Channel spec says:

The connectionHops stores the list of connection identifiers, in order, along which packets sent on this channel will travel. At the moment this list must be of length 1. In the future multi-hop channels may be supported.

if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

just a super tiny nit but you have the err statement in the second else clause in the ; err != nil setup, would be nice if they were both like this or both the other way just as a tiny remark on style

Copy link
Member Author

Choose a reason for hiding this comment

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

But we need to use the connection below outside the scope of the returned error.
Generally we use the below for function return sigs with one single error return:

if err := invokeFunc(); err != nil {
    // handle err
}

If we use the following then we can't use the connection below, outside the scope of the conditional:

if connection, err := k.channelKeeper.GetConnection(ctx, connectionHops[0]); err != nil {
   // connection and err are only available at this level of scope
}

Copy link
Contributor

Choose a reason for hiding this comment

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

ah yes, that makes sense 🙈

Copy link
Contributor

Choose a reason for hiding this comment

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

you should technically be able to if you put the usage in an else clause, but I personally prefer the way you have it now rather than adding additional else clauses!

return "", err
}

metadata = icatypes.NewDefaultMetadata(connectionHops[0], connection.GetCounterparty().GetConnectionID())
chatton marked this conversation as resolved.
Show resolved Hide resolved
} else {
if err := icatypes.ModuleCdc.UnmarshalJSON([]byte(version), &metadata); err != nil {
Comment on lines +52 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] rather than having nested if/else clauses it might be cleaner to have a helper function to return the metadata (using early returns)

Copy link
Member Author

Choose a reason for hiding this comment

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

I took a look at this and feel like there's not a massive difference making a helper function. You'd either need to return a new metadata type or take a pointer and a bunch of other args too... I'm pretty indifferent about it

return "", sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain accounts metadata")
}
}

if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, connectionHops, metadata); err != nil {
return err
return "", err
}

activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], portID)
Expand All @@ -58,15 +67,15 @@ func (k Keeper) OnChanOpenInit(
}

if channel.State == channeltypes.OPEN {
return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s is already OPEN", activeChannelID, portID)
return "", sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s is already OPEN", activeChannelID, portID)
}

if !icatypes.IsPreviousMetadataEqual(channel.Version, metadata) {
return sdkerrors.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version")
return "", sdkerrors.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version")
}
}

return nil
return string(icatypes.ModuleCdc.MustMarshalJSON(&metadata)), nil
Copy link
Contributor

Choose a reason for hiding this comment

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

just to make sure I understand, if this panics this is handled at the sdk level right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, its caught by the grpc recovery interceptor (essentially a middleware handler in grpc land)

}

// OnChanOpenAck sets the active channel for the interchain account/owner pair
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
true,
},
{
"success - previous active channel closed",
"success: previous active channel closed",
func() {
suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)

Expand All @@ -47,6 +47,13 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
},
true,
},
{
"success: empty channel version returns default metadata JSON string",
func() {
channel.Version = ""
},
true,
},
{
"invalid metadata - previous metadata is different",
func() {
Expand Down Expand Up @@ -138,6 +145,14 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
},
false,
},
{
"connection not found with default empty channel version",
func() {
channel.ConnectionHops = []string{"connection-10"}
channel.Version = ""
},
false,
},
{
"invalid controller connection ID",
func() {
Expand Down Expand Up @@ -214,7 +229,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {
path.EndpointA.ChannelConfig.PortID = portID

// default values
metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg)
metadata = icatypes.NewMetadata(icatypes.Version, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg)
versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata)
suite.Require().NoError(err)

Expand All @@ -232,12 +247,13 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() {

tc.malleate() // malleate mutates test data

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

if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(string(versionBytes), version)
} else {
suite.Require().Error(err)
}
Expand Down
16 changes: 15 additions & 1 deletion modules/apps/27-interchain-accounts/types/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,29 @@ func NewMetadata(version, controllerConnectionID, hostConnectionID, accAddress,
}
}

// NewDefaultMetadata creates and returns a new ICS27 Metadata instance containing the default ICS27 Metadata values
// with the provided controller and host connection identifiers
func NewDefaultMetadata(connectionConnectionID, hostConnectionID string) Metadata {
metadata := Metadata{
ControllerConnectionId: connectionConnectionID,
HostConnectionId: hostConnectionID,
Encoding: EncodingProtobuf,
TxType: TxTypeSDKMultiMsg,
Version: Version,
}

return metadata
}

// NewDefaultMetadataString creates and returns a new JSON encoded version string containing the default ICS27 Metadata values
// with the provided controller and host connection identifiers
func NewDefaultMetadataString(controllerConnectionID, hostConnectionID string) string {
metadata := Metadata{
Version: Version,
ControllerConnectionId: controllerConnectionID,
HostConnectionId: hostConnectionID,
Encoding: EncodingProtobuf,
TxType: TxTypeSDKMultiMsg,
Version: 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 you can replace now this whole object initialization with a call to the NewDefaultMetadata function above?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call!


return string(ModuleCdc.MustMarshalJSON(&metadata))
Expand Down