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

Flexible Channel Handshake Selection #7439

Merged
merged 7 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
24 changes: 13 additions & 11 deletions proto/ibc/core/channel/v1/channel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ message MsgChannelOpenTry {

string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
Channel channel = 3 [(gogoproto.nullable) = false];
string counterparty_version = 4 [(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""];
ibc.core.client.v1.Height proof_height = 6
string proved_channel_id = 3 [(gogoproto.moretags) = "yaml:\"proved_channel_id\""];
Channel channel = 4 [(gogoproto.nullable) = false];
string counterparty_version = 5 [(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_init = 6 [(gogoproto.moretags) = "yaml:\"proof_init\""];
ibc.core.client.v1.Height proof_height = 7
[(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false];
string signer = 7;
string signer = 8;
}

// MsgChannelOpenAck defines a msg sent by a Relayer to Chain A to acknowledge
Expand All @@ -40,13 +41,14 @@ message MsgChannelOpenAck {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string counterparty_version = 3 [(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_try = 4 [(gogoproto.moretags) = "yaml:\"proof_try\""];
ibc.core.client.v1.Height proof_height = 5
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string counterparty_channel_id = 3 [(gogoproto.moretags) = "yaml:\"counterparty_channel_id\""];
string counterparty_version = 4 [(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_try = 5 [(gogoproto.moretags) = "yaml:\"proof_try\""];
ibc.core.client.v1.Height proof_height = 6
[(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false];
string signer = 6;
string signer = 7;
}

// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain B to
Expand Down
7 changes: 3 additions & 4 deletions x/ibc/core/03-connection/keeper/handshake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (suite *KeeperTestSuite) TestConnOpenTry() {
// retrieve client state of chainA to pass as counterpartyClient
counterpartyClient = suite.chainA.GetClientState(clientA)
}, true},
{"success with empty provedID", func() {
{"invalid provedID", func() {
clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint)
connA, _, err := suite.coordinator.ConnOpenInit(suite.chainA, suite.chainB, clientA, clientB)
suite.Require().NoError(err)
Expand Down Expand Up @@ -256,7 +256,6 @@ func (suite *KeeperTestSuite) TestConnOpenTry() {

// retrieve client state of chainA to pass as counterpartyClient
counterpartyClient = suite.chainA.GetClientState(clientA)

}, false},
{"invalid previous connection has invalid versions", func() {
clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint)
Expand Down Expand Up @@ -364,7 +363,7 @@ func (suite *KeeperTestSuite) TestConnOpenAck() {
// retrieve client state of chainB to pass as counterpartyClient
counterpartyClient = suite.chainB.GetClientState(clientB)
}, true},
{"success with empty counterparty connection ID", func() {
{"success with empty stored counterparty connection ID", func() {
clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint)
connA, connB, err := suite.coordinator.ConnOpenInit(suite.chainA, suite.chainB, clientA, clientB)
suite.Require().NoError(err)
Expand Down Expand Up @@ -412,7 +411,7 @@ func (suite *KeeperTestSuite) TestConnOpenAck() {
// retrieve client state of chainB to pass as counterpartyClient
counterpartyClient = suite.chainB.GetClientState(clientB)
}, true},
{"success from tryopen with empty string", func() {
{"success from tryopen with empty stored connection id", func() {
// chainA is in TRYOPEN, chainB is in TRYOPEN
clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, ibctesting.Tendermint)
connB, connA, err := suite.coordinator.ConnOpenInit(suite.chainB, suite.chainA, clientB, clientA)
Expand Down
28 changes: 15 additions & 13 deletions x/ibc/core/04-channel/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ func NewChannelOpenInitCmd() *cobra.Command {
// NewChannelOpenTryCmd returns the command to create a MsgChannelOpenTry transaction
func NewChannelOpenTryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "open-try [port-id] [channel-id] [counterparty-port-id] [counterparty-channel-id] [connection-hops] [/path/to/proof_init.json] [proof-height]",
Use: "open-try [port-id] [channel-id] [proved-channel-id] [counterparty-port-id] [counterparty-channel-id] [connection-hops] [/path/to/proof_init.json] [proof-height]",
Short: "Creates and sends a ChannelOpenTry message",
Args: cobra.ExactArgs(7),
Args: cobra.ExactArgs(8),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
Expand All @@ -76,26 +76,27 @@ func NewChannelOpenTryCmd() *cobra.Command {

portID := args[0]
channelID := args[1]
counterpartyPortID := args[2]
counterpartyChannelID := args[3]
hops := strings.Split(args[4], "/")
provedChannelID := args[2]
counterpartyPortID := args[3]
counterpartyChannelID := args[4]
hops := strings.Split(args[5], "/")
order := channelOrder(cmd.Flags())

// TODO: Differentiate between channel and counterparty versions.
version, _ := cmd.Flags().GetString(FlagIBCVersion)

proofInit, err := connectionutils.ParseProof(clientCtx.LegacyAmino, args[5])
proofInit, err := connectionutils.ParseProof(clientCtx.LegacyAmino, args[6])
if err != nil {
return err
}

proofHeight, err := clienttypes.ParseHeight(args[6])
proofHeight, err := clienttypes.ParseHeight(args[7])
if err != nil {
return err
}

msg := types.NewMsgChannelOpenTry(
portID, channelID, version, order, hops,
portID, channelID, provedChannelID, version, order, hops,
counterpartyPortID, counterpartyChannelID, version,
proofInit, proofHeight, clientCtx.GetFromAddress(),
)
Expand All @@ -117,9 +118,9 @@ func NewChannelOpenTryCmd() *cobra.Command {
// NewChannelOpenAckCmd returns the command to create a MsgChannelOpenAck transaction
func NewChannelOpenAckCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "open-ack [port-id] [channel-id] [/path/to/proof_try.json] [proof-height]",
Use: "open-ack [port-id] [channel-id] [counterparty-channel-id] [/path/to/proof_try.json] [proof-height]",
Short: "Creates and sends a ChannelOpenAck message",
Args: cobra.ExactArgs(4),
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
Expand All @@ -129,22 +130,23 @@ func NewChannelOpenAckCmd() *cobra.Command {

portID := args[0]
channelID := args[1]
counterpartyChannelID := args[2]

// TODO: Differentiate between channel and counterparty versions.
version, _ := cmd.Flags().GetString(FlagIBCVersion)

proofTry, err := connectionutils.ParseProof(clientCtx.LegacyAmino, args[2])
proofTry, err := connectionutils.ParseProof(clientCtx.LegacyAmino, args[3])
if err != nil {
return err
}

proofHeight, err := clienttypes.ParseHeight(args[3])
proofHeight, err := clienttypes.ParseHeight(args[4])
if err != nil {
return err
}

msg := types.NewMsgChannelOpenAck(
portID, channelID, version, proofTry, proofHeight, clientCtx.GetFromAddress(),
portID, channelID, counterpartyChannelID, version, proofTry, proofHeight, clientCtx.GetFromAddress(),
)
if err := msg.ValidateBasic(); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions x/ibc/core/04-channel/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func HandleMsgChannelOpenInit(ctx sdk.Context, k keeper.Keeper, portCap *capabil

// HandleMsgChannelOpenTry defines the sdk.Handler for MsgChannelOpenTry
func HandleMsgChannelOpenTry(ctx sdk.Context, k keeper.Keeper, portCap *capabilitytypes.Capability, msg *types.MsgChannelOpenTry) (*sdk.Result, *capabilitytypes.Capability, error) {
capKey, err := k.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.ChannelId,
capKey, err := k.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.ChannelId, msg.ProvedChannelId,
portCap, msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight,
)
if err != nil {
Expand Down Expand Up @@ -70,7 +70,7 @@ func HandleMsgChannelOpenTry(ctx sdk.Context, k keeper.Keeper, portCap *capabili
// HandleMsgChannelOpenAck defines the sdk.Handler for MsgChannelOpenAck
func HandleMsgChannelOpenAck(ctx sdk.Context, k keeper.Keeper, channelCap *capabilitytypes.Capability, msg *types.MsgChannelOpenAck) (*sdk.Result, error) {
err := k.ChanOpenAck(
ctx, msg.PortId, msg.ChannelId, channelCap, msg.CounterpartyVersion, msg.ProofTry, msg.ProofHeight,
ctx, msg.PortId, msg.ChannelId, channelCap, msg.CounterpartyVersion, msg.CounterpartyChannelId, msg.ProofTry, msg.ProofHeight,
)
if err != nil {
return nil, sdkerrors.Wrap(err, "channel handshake open ack failed")
Expand Down
29 changes: 25 additions & 4 deletions x/ibc/core/04-channel/keeper/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ func (k Keeper) ChanOpenTry(
order types.Order,
connectionHops []string,
portID,
channelID string,
channelID,
provedChannelID string,
Comment on lines +99 to +100
Copy link
Member

Choose a reason for hiding this comment

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

Personally this is very confusing for me to read. It's not immediately clear what these mean without poring through the spec+comments.

I would prefer channelID => desiredChannelID and provedChannelID => counterpartyChosenChannelID

cc: @cwgoes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree on the confusion. Why not just desiredChannelID and chosenChannelID? Using counterparty can be vague to whether the counterparty chose the channel ID for this chain or if the counterparty chose their own.

Then the logic would be

// empty chosen channel id allows the desired channel id to be used for identifier selection
if chosenChannelID != "" && chosenChannelID != desiredChannelID {
return err
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with this renaming, I can update the spec as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

upon further reflection I think counterpartyChosenChannelID works fine since in most cases the Counterparty will be a different field containing the counterparty.ChannelId

Copy link
Contributor

Choose a reason for hiding this comment

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

OK!

portCap *capabilitytypes.Capability,
counterparty types.Counterparty,
version,
Expand Down Expand Up @@ -147,6 +148,15 @@ func (k Keeper) ChanOpenTry(
)
}

// empty-string is a sentinel value for "allow any identifier" to be selected by
// the counterparty channel
if provedChannelID != channelID && provedChannelID != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit, I know the result is the same but if we change the order the second clause won't need to be evaluated as the entire condition will return false. Usually, in JS you would use if object && object.field... to check for undefined objects.

Suggested change
if provedChannelID != channelID && provedChannelID != "" {
if provedChannelID != "" && provedChannelID != channelID {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea I agree which is why I did this in msgs.go. I left how the spec ordered it in handshake for consistency. This is also how it is in connection handshake so I'd prefer to update this in a separate pr since it isn't api breaking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc/ @cwgoes should we apply this to the spec? I think it reads slightly better

Copy link
Contributor

Choose a reason for hiding this comment

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

Applied to the spec in cosmos/ibc@890e3d0

return nil, sdkerrors.Wrapf(
types.ErrInvalidChannelIdentifier,
"proved channel identifier (%s) must equal channel identifier (%s) or be empty", provedChannelID, channelID,
)
}

// NOTE: this step has been switched with the one below to reverse the connection
// hops
channel := types.NewChannel(types.TRYOPEN, order, counterparty, connectionHops, version)
Expand All @@ -159,7 +169,7 @@ func (k Keeper) ChanOpenTry(

// expectedCounterpaty is the counterparty of the counterparty's channel end
// (i.e self)
expectedCounterparty := types.NewCounterparty(portID, channelID)
expectedCounterparty := types.NewCounterparty(portID, provedChannelID)
expectedChannel := types.NewChannel(
types.INIT, channel.Ordering, expectedCounterparty,
counterpartyHops, counterpartyVersion,
Expand Down Expand Up @@ -194,7 +204,8 @@ func (k Keeper) ChanOpenAck(
portID,
channelID string,
chanCap *capabilitytypes.Capability,
counterpartyVersion string,
counterpartyVersion,
counterpartyChannelID string,
proofTry []byte,
proofHeight exported.Height,
) error {
Expand All @@ -214,6 +225,15 @@ func (k Keeper) ChanOpenAck(
return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
}

// empty-string is a sentinel value for "allow any identifier" to be selected by
// the counterparty channel
if counterpartyChannelID != channel.Counterparty.ChannelId && channel.Counterparty.ChannelId != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Suggested change
if counterpartyChannelID != channel.Counterparty.ChannelId && channel.Counterparty.ChannelId != "" {
if channel.Counterparty.ChannelId != "" && counterpartyChannelID != channel.Counterparty.ChannelId {

Copy link
Contributor

Choose a reason for hiding this comment

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

Also applied to the spec in cosmos/ibc@890e3d0

return sdkerrors.Wrapf(
types.ErrInvalidChannelIdentifier,
"counterparty channel identifier (%s) must be empty or equal to stored channel ID for counterparty (%s)", counterpartyChannelID, channel.Counterparty.ChannelId,
)
}

connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
Expand Down Expand Up @@ -241,7 +261,7 @@ func (k Keeper) ChanOpenAck(

if err := k.connectionKeeper.VerifyChannelState(
ctx, connectionEnd, proofHeight, proofTry,
channel.Counterparty.PortId, channel.Counterparty.ChannelId,
channel.Counterparty.PortId, counterpartyChannelID,
expectedChannel,
); err != nil {
return err
Expand All @@ -251,6 +271,7 @@ func (k Keeper) ChanOpenAck(

channel.State = types.OPEN
channel.Version = counterpartyVersion
channel.Counterparty.ChannelId = counterpartyChannelID
k.SetChannel(ctx, portID, channelID, channel)

return nil
Expand Down
Loading