From 5dfc4a2ec99eac3b672a6baa6a4dcf8599427605 Mon Sep 17 00:00:00 2001 From: colin axner <25233464+colin-axner@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:43:26 +0200 Subject: [PATCH] Standardize connection versioning + channel versioning docs (#6640) * update connection versions with feature set flag * make connection version modular to support channel versioning and registering * revert IBCVersion renaming, add channel versioning logic * fix channel version flag * remove unnecessary godoc * remove unused func * fix lint and version test * add test and fix error * revert changes * update docs * remove unnecessary godoc * Apply suggestions from code review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * update doc * add test cases for unchecked lines * go fmt * begin migration to standardized version * revert proto changes * restructure versioning to go from string to proto * update versionStr to encodedVersion naming * fix version test build * fix keeper tests * fix various tests * fixes from self review * update docs * fix lint * add more code cov * rename ToString funcs to Encode/DecodeVersion + GetCompatibleEncodedVersions renaming * update spec docs Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- docs/ibc/custom.md | 22 ++ proto/ibc/connection/connection.proto | 145 ++++--- x/ibc-transfer/handler_test.go | 2 +- x/ibc-transfer/keeper/keeper_test.go | 2 +- x/ibc-transfer/module.go | 2 +- x/ibc-transfer/types/errors.go | 2 +- x/ibc/03-connection/keeper/grpc_query_test.go | 8 +- x/ibc/03-connection/keeper/handshake.go | 22 +- x/ibc/03-connection/keeper/handshake_test.go | 27 +- x/ibc/03-connection/keeper/keeper_test.go | 8 +- x/ibc/03-connection/types/connection.go | 2 +- x/ibc/03-connection/types/connection.pb.go | 369 ++++++++++++++---- x/ibc/03-connection/types/connection_test.go | 9 +- x/ibc/03-connection/types/genesis_test.go | 9 +- x/ibc/03-connection/types/msgs.go | 8 +- x/ibc/03-connection/types/msgs_test.go | 39 +- x/ibc/03-connection/types/version.go | 264 ++++++++----- x/ibc/03-connection/types/version_test.go | 141 ++++--- x/ibc/04-channel/client/cli/tx.go | 8 +- x/ibc/04-channel/keeper/handshake_test.go | 25 +- x/ibc/04-channel/types/channel.go | 3 - x/ibc/24-host/validate.go | 27 -- x/ibc/24-host/validate_test.go | 28 -- x/ibc/genesis_test.go | 3 +- x/ibc/spec/01_concepts.md | 32 ++ x/ibc/testing/chain.go | 5 +- 26 files changed, 811 insertions(+), 401 deletions(-) diff --git a/docs/ibc/custom.md b/docs/ibc/custom.md index e14c14ee7d..0a66a13be2 100644 --- a/docs/ibc/custom.md +++ b/docs/ibc/custom.md @@ -142,6 +142,28 @@ OnChanCloseConfirm( } ``` +#### Channel Handshake Version Negotiation + +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. +* `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. + +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 +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct is used for this +scheme can be found in `03-connection/types`. + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already impelemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + ### Bind Ports Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` diff --git a/proto/ibc/connection/connection.proto b/proto/ibc/connection/connection.proto index 0bf2f17b5e..339d919f79 100644 --- a/proto/ibc/connection/connection.proto +++ b/proto/ibc/connection/connection.proto @@ -9,70 +9,97 @@ import "ibc/commitment/commitment.proto"; // MsgConnectionOpenInit defines the msg sent by an account on Chain A to // initialize a connection with Chain B. message MsgConnectionOpenInit { - string client_id = 1 [(gogoproto.customname) = "ClientID", (gogoproto.moretags) = "yaml:\"client_id\""]; - string connection_id = 2 [(gogoproto.customname) = "ConnectionID", (gogoproto.moretags) = "yaml:\"connection_id\""]; - Counterparty counterparty = 3 [(gogoproto.nullable) = false]; - bytes signer = 4 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + string client_id = 1 [ + (gogoproto.customname) = "ClientID", + (gogoproto.moretags) = "yaml:\"client_id\"" + ]; + string connection_id = 2 [ + (gogoproto.customname) = "ConnectionID", + (gogoproto.moretags) = "yaml:\"connection_id\"" + ]; + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + bytes signer = 4 + [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; } -// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a connection -// on Chain B. +// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a +// connection on Chain B. message MsgConnectionOpenTry { - string client_id = 1 [(gogoproto.customname) = "ClientID", (gogoproto.moretags) = "yaml:\"client_id\""]; - string connection_id = 2 [(gogoproto.customname) = "ConnectionID", (gogoproto.moretags) = "yaml:\"connection_id\""]; - Counterparty counterparty = 3 [(gogoproto.nullable) = false]; - repeated string counterparty_versions = 4 [(gogoproto.moretags) = "yaml:\"counterparty_versions\""]; - // proof of the initialization the connection on Chain A: `UNITIALIZED -> INIT` - bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""]; + string client_id = 1 [ + (gogoproto.customname) = "ClientID", + (gogoproto.moretags) = "yaml:\"client_id\"" + ]; + string connection_id = 2 [ + (gogoproto.customname) = "ConnectionID", + (gogoproto.moretags) = "yaml:\"connection_id\"" + ]; + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + repeated string counterparty_versions = 4 [ + (gogoproto.moretags) = "yaml:\"counterparty_versions\"" + ]; + // proof of the initialization the connection on Chain A: `UNITIALIZED -> + // INIT` + bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""]; uint64 proof_height = 6; // proof of client consensus state - bytes proof_consensus = 7 - [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; - uint64 consensus_height = 8 [(gogoproto.moretags) = "yaml:\"consensus_height\""]; - bytes signer = 9 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes proof_consensus = 7 [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; + uint64 consensus_height = 8 + [(gogoproto.moretags) = "yaml:\"consensus_height\""]; + bytes signer = 9 + [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; } -// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to acknowledge -// the change of connection state to TRYOPEN on Chain B. +// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to +// acknowledge the change of connection state to TRYOPEN on Chain B. message MsgConnectionOpenAck { - string connection_id = 1 [(gogoproto.customname) = "ConnectionID", (gogoproto.moretags) = "yaml:\"connection_id\""]; - string version = 2; - // proof of the initialization the connection on Chain B: `UNITIALIZED -> TRYOPEN` - bytes proof_try = 3 - [(gogoproto.moretags) = "yaml:\"proof_try\""]; + string connection_id = 1 [ + (gogoproto.customname) = "ConnectionID", + (gogoproto.moretags) = "yaml:\"connection_id\"" + ]; + string version = 2; + // proof of the initialization the connection on Chain B: `UNITIALIZED -> + // TRYOPEN` + bytes proof_try = 3 [(gogoproto.moretags) = "yaml:\"proof_try\""]; uint64 proof_height = 4 [(gogoproto.moretags) = "yaml:\"proof_height\""]; // proof of client consensus state - bytes proof_consensus = 5 - [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; - uint64 consensus_height = 6 [(gogoproto.moretags) = "yaml:\"consensus_height\""]; - bytes signer = 7 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes proof_consensus = 5 [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; + uint64 consensus_height = 6 + [(gogoproto.moretags) = "yaml:\"consensus_height\""]; + bytes signer = 7 + [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; } -// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to acknowledge -// the change of connection state to OPEN on Chain A. +// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of connection state to OPEN on Chain A. message MsgConnectionOpenConfirm { - string connection_id = 1 [(gogoproto.customname) = "ConnectionID", (gogoproto.moretags) = "yaml:\"connection_id\""]; + string connection_id = 1 [ + (gogoproto.customname) = "ConnectionID", + (gogoproto.moretags) = "yaml:\"connection_id\"" + ]; // proof for the change of the connection state on Chain A: `INIT -> OPEN` - bytes proof_ack = 2 - [(gogoproto.moretags) = "yaml:\"proof_ack\""]; + bytes proof_ack = 2 [(gogoproto.moretags) = "yaml:\"proof_ack\""]; uint64 proof_height = 3 [(gogoproto.moretags) = "yaml:\"proof_height\""]; - bytes signer = 4 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes signer = 4 + [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; } // ICS03 - Connection Data Structures as defined in // https://github.com/cosmos/ics/tree/master/spec/ics-003-connection-semantics#data-structures -// ConnectionEnd defines a stateful object on a chain connected to another separate -// one. -// NOTE: there must only be 2 defined ConnectionEnds to establish a connection -// between two chains. +// ConnectionEnd defines a stateful object on a chain connected to another +// separate one. NOTE: there must only be 2 defined ConnectionEnds to establish +// a connection between two chains. message ConnectionEnd { option (gogoproto.goproto_getters) = false; // connection identifier. - string id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = "yaml:\"id\""]; + string id = 1 + [(gogoproto.customname) = "ID", (gogoproto.moretags) = "yaml:\"id\""]; // client associated with this connection. - string client_id = 2 [(gogoproto.customname) = "ClientID", (gogoproto.moretags) = "yaml:\"client_id\""]; - // opaque string which can be utilised to determine encodings or protocols for + string client_id = 2 [ + (gogoproto.customname) = "ClientID", + (gogoproto.moretags) = "yaml:\"client_id\"" + ]; + // IBC version which can be utilised to determine encodings or protocols for // channels or packets utilising this connection repeated string versions = 3; // current state of the connection end. @@ -87,10 +114,12 @@ enum State { option (gogoproto.goproto_enum_prefix) = false; // Default State - STATE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; + STATE_UNINITIALIZED_UNSPECIFIED = 0 + [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; // A connection end has just started the opening handshake. STATE_INIT = 1 [(gogoproto.enumvalue_customname) = "INIT"]; - // A connection end has acknowledged the handshake step on the counterparty chain. + // A connection end has acknowledged the handshake step on the counterparty + // chain. STATE_TRYOPEN = 2 [(gogoproto.enumvalue_customname) = "TRYOPEN"]; // A connection end has completed the handshake. STATE_OPEN = 3 [(gogoproto.enumvalue_customname) = "OPEN"]; @@ -100,10 +129,18 @@ enum State { message Counterparty { option (gogoproto.goproto_getters) = false; - // identifies the client on the counterparty chain associated with a given connection. - string client_id = 1 [(gogoproto.customname) = "ClientID", (gogoproto.moretags) = "yaml:\"client_id\""]; - // identifies the connection end on the counterparty chain associated with a given connection. - string connection_id = 2 [(gogoproto.customname) = "ConnectionID", (gogoproto.moretags) = "yaml:\"connection_id\""]; + // identifies the client on the counterparty chain associated with a given + // connection. + string client_id = 1 [ + (gogoproto.customname) = "ClientID", + (gogoproto.moretags) = "yaml:\"client_id\"" + ]; + // identifies the connection end on the counterparty chain associated with a + // given connection. + string connection_id = 2 [ + (gogoproto.customname) = "ConnectionID", + (gogoproto.moretags) = "yaml:\"connection_id\"" + ]; // commitment merkle prefix of the counterparty chain ibc.commitment.MerklePrefix prefix = 3 [(gogoproto.nullable) = false]; } @@ -117,7 +154,21 @@ message ClientPaths { // ConnectionPaths define all the connection paths for a given client state. message ConnectionPaths { // client state unique identifier - string client_id = 1 [(gogoproto.customname) = "ClientID", (gogoproto.moretags) = "yaml:\"client_id\""]; + string client_id = 1 [ + (gogoproto.customname) = "ClientID", + (gogoproto.moretags) = "yaml:\"client_id\"" + ]; // list of connection paths repeated string paths = 2; } + +// Version defines the versioning scheme used to negotiate the IBC verison in +// the connection handshake. +message Version { + option (gogoproto.goproto_getters) = false; + + // unique version identifier + string identifier = 1; + // list of features compatible with the specified identifier + repeated string features = 2; +} diff --git a/x/ibc-transfer/handler_test.go b/x/ibc-transfer/handler_test.go index 21436a17f8..97153941f5 100644 --- a/x/ibc-transfer/handler_test.go +++ b/x/ibc-transfer/handler_test.go @@ -284,7 +284,7 @@ func (chain *TestChain) createConnection( State: state, ClientID: clientID, Counterparty: counterparty, - Versions: connectiontypes.GetCompatibleVersions(), + Versions: connectiontypes.GetCompatibleEncodedVersions(), } ctx := chain.GetContext() chain.App.IBCKeeper.ConnectionKeeper.SetConnection(ctx, connID, connection) diff --git a/x/ibc-transfer/keeper/keeper_test.go b/x/ibc-transfer/keeper/keeper_test.go index dd9f44e4ae..3ce58f0a79 100644 --- a/x/ibc-transfer/keeper/keeper_test.go +++ b/x/ibc-transfer/keeper/keeper_test.go @@ -259,7 +259,7 @@ func (chain *TestChain) createConnection( State: state, ClientID: clientID, Counterparty: counterparty, - Versions: connectiontypes.GetCompatibleVersions(), + Versions: connectiontypes.GetCompatibleEncodedVersions(), } ctx := chain.GetContext() chain.App.IBCKeeper.ConnectionKeeper.SetConnection(ctx, connID, connection) diff --git a/x/ibc-transfer/module.go b/x/ibc-transfer/module.go index b2a985a43a..8542c4b9e9 100644 --- a/x/ibc-transfer/module.go +++ b/x/ibc-transfer/module.go @@ -195,7 +195,7 @@ func (am AppModule) OnChanOpenInit( } if version != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "got: %s, expected %s", version, types.Version) + return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) } // Claim channel capability passed back by IBC module diff --git a/x/ibc-transfer/types/errors.go b/x/ibc-transfer/types/errors.go index a2d391f9e7..625f5cc776 100644 --- a/x/ibc-transfer/types/errors.go +++ b/x/ibc-transfer/types/errors.go @@ -9,5 +9,5 @@ var ( ErrInvalidPacketTimeout = sdkerrors.Register(ModuleName, 2, "invalid packet timeout") ErrOnlyOneDenomAllowed = sdkerrors.Register(ModuleName, 3, "only one denom allowed") ErrInvalidDenomForTransfer = sdkerrors.Register(ModuleName, 4, "invalid denomination for cross-chain transfer") - ErrInvalidVersion = sdkerrors.Register(ModuleName, 5, "invalid version") + ErrInvalidVersion = sdkerrors.Register(ModuleName, 5, "invalid ICS20 version") ) diff --git a/x/ibc/03-connection/keeper/grpc_query_test.go b/x/ibc/03-connection/keeper/grpc_query_test.go index f1340882d0..0afc941116 100644 --- a/x/ibc/03-connection/keeper/grpc_query_test.go +++ b/x/ibc/03-connection/keeper/grpc_query_test.go @@ -50,7 +50,7 @@ func (suite *KeeperTestSuite) TestQueryConnection() { connB := suite.chainB.GetFirstTestConnection(clientB, clientA) counterparty := types.NewCounterparty(clientB, connB.ID, suite.chainB.GetPrefix()) - expConnection = types.NewConnectionEnd(types.INIT, connA.ID, clientA, counterparty, types.GetCompatibleVersions()) + expConnection = types.NewConnectionEnd(types.INIT, connA.ID, clientA, counterparty, types.GetCompatibleEncodedVersions()) suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), connA.ID, expConnection) req = &types.QueryConnectionRequest{ @@ -119,9 +119,9 @@ func (suite *KeeperTestSuite) TestQueryConnections() { counterparty2 := types.NewCounterparty(clientB, connB1.ID, suite.chainB.GetPrefix()) counterparty3 := types.NewCounterparty(clientB1, connB2.ID, suite.chainB.GetPrefix()) - conn1 := types.NewConnectionEnd(types.OPEN, connA0.ID, clientA, counterparty1, types.GetCompatibleVersions()) - conn2 := types.NewConnectionEnd(types.INIT, connA1.ID, clientA, counterparty2, types.GetCompatibleVersions()) - conn3 := types.NewConnectionEnd(types.OPEN, connA2.ID, clientA1, counterparty3, types.GetCompatibleVersions()) + conn1 := types.NewConnectionEnd(types.OPEN, connA0.ID, clientA, counterparty1, types.GetCompatibleEncodedVersions()) + conn2 := types.NewConnectionEnd(types.INIT, connA1.ID, clientA, counterparty2, types.GetCompatibleEncodedVersions()) + conn3 := types.NewConnectionEnd(types.OPEN, connA2.ID, clientA1, counterparty3, types.GetCompatibleEncodedVersions()) expConnections = []*types.ConnectionEnd{&conn1, &conn2, &conn3} diff --git a/x/ibc/03-connection/keeper/handshake.go b/x/ibc/03-connection/keeper/handshake.go index 4d8bf9f31b..49c7c31dc4 100644 --- a/x/ibc/03-connection/keeper/handshake.go +++ b/x/ibc/03-connection/keeper/handshake.go @@ -26,7 +26,7 @@ func (k Keeper) ConnOpenInit( } // connection defines chain A's ConnectionEnd - connection := types.NewConnectionEnd(types.INIT, connectionID, clientID, counterparty, types.GetCompatibleVersions()) + connection := types.NewConnectionEnd(types.INIT, connectionID, clientID, counterparty, types.GetCompatibleEncodedVersions()) k.SetConnection(ctx, connectionID, connection) if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil { @@ -128,7 +128,7 @@ func (k Keeper) ConnOpenTry( func (k Keeper) ConnOpenAck( ctx sdk.Context, connectionID string, - version string, // version that ChainB chose in ConnOpenTry + encodedVersion string, // version that ChainB chose in ConnOpenTry proofTry []byte, // proof that connectionEnd was added to ChainB state in ConnOpenTry proofConsensus []byte, // proof that chainB has stored ConsensusState of chainA on its client proofHeight uint64, // height that relayer constructed proofTry @@ -156,7 +156,12 @@ func (k Keeper) ConnOpenAck( ) } - // Check that ChainB's proposed version number is supported by chainA + version, err := types.DecodeVersion(encodedVersion) + if err != nil { + return sdkerrors.Wrap(err, "version negotiation failed") + } + + // Check that ChainB's proposed version identifier is supported by chainA supportedVersion, found := types.FindSupportedVersion(version, types.GetCompatibleVersions()) if !found { return sdkerrors.Wrapf( @@ -166,11 +171,8 @@ func (k Keeper) ConnOpenAck( } // Check that ChainB's proposed feature set is supported by chainA - if !types.VerifyProposedFeatureSet(version, supportedVersion) { - return sdkerrors.Wrapf( - types.ErrVersionNegotiationFailed, - "connection version feature set provided (%s) is not supported (%s)", version, types.GetCompatibleVersions(), - ) + if err := supportedVersion.VerifyProposedVersion(version); err != nil { + return err } // Retrieve chainA's consensus state at consensusheight @@ -181,7 +183,7 @@ func (k Keeper) ConnOpenAck( prefix := k.GetCommitmentPrefix() expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes())) - expectedConnection := types.NewConnectionEnd(types.TRYOPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, []string{version}) + expectedConnection := types.NewConnectionEnd(types.TRYOPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, []string{encodedVersion}) // Ensure that ChainB stored expected connectionEnd in its state during ConnOpenTry if err := k.VerifyConnectionState( @@ -202,7 +204,7 @@ func (k Keeper) ConnOpenAck( // Update connection state to Open connection.State = types.OPEN - connection.Versions = []string{version} + connection.Versions = []string{encodedVersion} k.SetConnection(ctx, connectionID, connection) return nil } diff --git a/x/ibc/03-connection/keeper/handshake_test.go b/x/ibc/03-connection/keeper/handshake_test.go index a73ea77b24..09b9a5e6dd 100644 --- a/x/ibc/03-connection/keeper/handshake_test.go +++ b/x/ibc/03-connection/keeper/handshake_test.go @@ -102,7 +102,9 @@ func (suite *KeeperTestSuite) TestConnOpenTry() { _, _, err := suite.coordinator.ConnOpenInit(suite.chainA, suite.chainB, clientA, clientB) suite.Require().NoError(err) - versions = []string{"(version won't match,[])"} + version, err := types.NewVersion("0.0", nil).Encode() + suite.Require().NoError(err) + versions = []string{version} }, false}, {"connection state verification failed", func() { clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, clientexported.Tendermint) @@ -140,9 +142,9 @@ func (suite *KeeperTestSuite) TestConnOpenTry() { tc := tc suite.Run(tc.msg, func() { - suite.SetupTest() // reset - consensusHeight = 0 // must be explicitly changed in malleate - versions = types.GetCompatibleVersions() // must be explicitly changed in malleate + suite.SetupTest() // reset + consensusHeight = 0 // must be explicitly changed in malleate + versions = types.GetCompatibleEncodedVersions() // must be explicitly changed in malleate tc.malleate() @@ -266,6 +268,17 @@ func (suite *KeeperTestSuite) TestConnOpenAck() { version = "" }, false}, + {"feature set verification failed - unsupported feature", func() { + clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, clientexported.Tendermint) + connA, connB, err := suite.coordinator.ConnOpenInit(suite.chainA, suite.chainB, clientA, clientB) + suite.Require().NoError(err) + + err = suite.coordinator.ConnOpenTry(suite.chainB, suite.chainA, connB, connA) + suite.Require().NoError(err) + + version, err = types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_ORDERED", "ORDER_UNORDERED", "ORDER_DAG"}).Encode() + suite.Require().NoError(err) + }, false}, {"self consensus state not found", func() { clientA, clientB = suite.coordinator.SetupClients(suite.chainA, suite.chainB, clientexported.Tendermint) connA, connB, err := suite.coordinator.ConnOpenInit(suite.chainA, suite.chainB, clientA, clientB) @@ -305,9 +318,9 @@ func (suite *KeeperTestSuite) TestConnOpenAck() { for _, tc := range testCases { tc := tc suite.Run(tc.msg, func() { - suite.SetupTest() // reset - version = types.GetCompatibleVersions()[0] // must be explicitly changed in malleate - consensusHeight = 0 // must be explicitly changed in malleate + suite.SetupTest() // reset + version = types.GetCompatibleEncodedVersions()[0] // must be explicitly changed in malleate + consensusHeight = 0 // must be explicitly changed in malleate tc.malleate() diff --git a/x/ibc/03-connection/keeper/keeper_test.go b/x/ibc/03-connection/keeper/keeper_test.go index 2a094ff1bf..7d4a4b2a17 100644 --- a/x/ibc/03-connection/keeper/keeper_test.go +++ b/x/ibc/03-connection/keeper/keeper_test.go @@ -47,10 +47,10 @@ func (suite *KeeperTestSuite) TestSetAndGetClientConnectionPaths() { _, existed := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetClientConnectionPaths(suite.chainA.GetContext(), clientA) suite.False(existed) - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetClientConnectionPaths(suite.chainA.GetContext(), clientA, types.GetCompatibleVersions()) + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetClientConnectionPaths(suite.chainA.GetContext(), clientA, types.GetCompatibleEncodedVersions()) paths, existed := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetClientConnectionPaths(suite.chainA.GetContext(), clientA) suite.True(existed) - suite.EqualValues(types.GetCompatibleVersions(), paths) + suite.EqualValues(types.GetCompatibleEncodedVersions(), paths) } // create 2 connections: A0 - B0, A1 - B1 @@ -61,8 +61,8 @@ func (suite KeeperTestSuite) TestGetAllConnections() { counterpartyB0 := types.NewCounterparty(clientB, connB0.ID, suite.chainB.GetPrefix()) // connection B0 counterpartyB1 := types.NewCounterparty(clientB, connB1.ID, suite.chainB.GetPrefix()) // connection B1 - conn1 := types.NewConnectionEnd(types.OPEN, connA0.ID, clientA, counterpartyB0, types.GetCompatibleVersions()) // A0 - B0 - conn2 := types.NewConnectionEnd(types.OPEN, connA1.ID, clientA, counterpartyB1, types.GetCompatibleVersions()) // A1 - B1 + conn1 := types.NewConnectionEnd(types.OPEN, connA0.ID, clientA, counterpartyB0, types.GetCompatibleEncodedVersions()) // A0 - B0 + conn2 := types.NewConnectionEnd(types.OPEN, connA1.ID, clientA, counterpartyB1, types.GetCompatibleEncodedVersions()) // A1 - B1 expConnections := []types.ConnectionEnd{conn1, conn2} connections := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetAllConnections(suite.chainA.GetContext()) diff --git a/x/ibc/03-connection/types/connection.go b/x/ibc/03-connection/types/connection.go index 3a07f0be2c..b8350f8fa8 100644 --- a/x/ibc/03-connection/types/connection.go +++ b/x/ibc/03-connection/types/connection.go @@ -60,7 +60,7 @@ func (c ConnectionEnd) ValidateBasic() error { return sdkerrors.Wrap(sdkerrors.ErrInvalidVersion, "empty connection versions") } for _, version := range c.Versions { - if err := host.ConnectionVersionValidator(version); err != nil { + if err := ValidateVersion(version); err != nil { return err } } diff --git a/x/ibc/03-connection/types/connection.pb.go b/x/ibc/03-connection/types/connection.pb.go index b60081471b..617953fd51 100644 --- a/x/ibc/03-connection/types/connection.pb.go +++ b/x/ibc/03-connection/types/connection.pb.go @@ -34,7 +34,8 @@ const ( UNINITIALIZED State = 0 // A connection end has just started the opening handshake. INIT State = 1 - // A connection end has acknowledged the handshake step on the counterparty chain. + // A connection end has acknowledged the handshake step on the counterparty + // chain. TRYOPEN State = 2 // A connection end has completed the handshake. OPEN State = 3 @@ -132,14 +133,15 @@ func (m *MsgConnectionOpenInit) GetSigner() github_com_cosmos_cosmos_sdk_types.A return nil } -// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a connection -// on Chain B. +// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a +// connection on Chain B. type MsgConnectionOpenTry struct { ClientID string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"client_id"` ConnectionID string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` CounterpartyVersions []string `protobuf:"bytes,4,rep,name=counterparty_versions,json=counterpartyVersions,proto3" json:"counterparty_versions,omitempty" yaml:"counterparty_versions"` - // proof of the initialization the connection on Chain A: `UNITIALIZED -> INIT` + // proof of the initialization the connection on Chain A: `UNITIALIZED -> + // INIT` ProofInit []byte `protobuf:"bytes,5,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty" yaml:"proof_init"` ProofHeight uint64 `protobuf:"varint,6,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height,omitempty"` // proof of client consensus state @@ -244,12 +246,13 @@ func (m *MsgConnectionOpenTry) GetSigner() github_com_cosmos_cosmos_sdk_types.Ac return nil } -// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to acknowledge -// the change of connection state to TRYOPEN on Chain B. +// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to +// acknowledge the change of connection state to TRYOPEN on Chain B. type MsgConnectionOpenAck struct { ConnectionID string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - // proof of the initialization the connection on Chain B: `UNITIALIZED -> TRYOPEN` + // proof of the initialization the connection on Chain B: `UNITIALIZED -> + // TRYOPEN` ProofTry []byte `protobuf:"bytes,3,opt,name=proof_try,json=proofTry,proto3" json:"proof_try,omitempty" yaml:"proof_try"` ProofHeight uint64 `protobuf:"varint,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height,omitempty" yaml:"proof_height"` // proof of client consensus state @@ -340,8 +343,8 @@ func (m *MsgConnectionOpenAck) GetSigner() github_com_cosmos_cosmos_sdk_types.Ac return nil } -// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to acknowledge -// the change of connection state to OPEN on Chain A. +// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of connection state to OPEN on Chain A. type MsgConnectionOpenConfirm struct { ConnectionID string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` // proof for the change of the connection state on Chain A: `INIT -> OPEN` @@ -411,16 +414,15 @@ func (m *MsgConnectionOpenConfirm) GetSigner() github_com_cosmos_cosmos_sdk_type return nil } -// ConnectionEnd defines a stateful object on a chain connected to another separate -// one. -// NOTE: there must only be 2 defined ConnectionEnds to establish a connection -// between two chains. +// ConnectionEnd defines a stateful object on a chain connected to another +// separate one. NOTE: there must only be 2 defined ConnectionEnds to establish +// a connection between two chains. type ConnectionEnd struct { // connection identifier. ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // client associated with this connection. ClientID string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"client_id"` - // opaque string which can be utilised to determine encodings or protocols for + // IBC version which can be utilised to determine encodings or protocols for // channels or packets utilising this connection Versions []string `protobuf:"bytes,3,rep,name=versions,proto3" json:"versions,omitempty"` // current state of the connection end. @@ -464,9 +466,11 @@ var xxx_messageInfo_ConnectionEnd proto.InternalMessageInfo // Counterparty defines the counterparty chain associated with a connection end. type Counterparty struct { - // identifies the client on the counterparty chain associated with a given connection. + // identifies the client on the counterparty chain associated with a given + // connection. ClientID string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"client_id"` - // identifies the connection end on the counterparty chain associated with a given connection. + // identifies the connection end on the counterparty chain associated with a + // given connection. ConnectionID string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty" yaml:"connection_id"` // commitment merkle prefix of the counterparty chain Prefix types.MerklePrefix `protobuf:"bytes,3,opt,name=prefix,proto3" json:"prefix"` @@ -606,6 +610,48 @@ func (m *ConnectionPaths) GetPaths() []string { return nil } +// Version defines the versioning scheme used to negotiate the IBC verison in +// the connection handshake. +type Version struct { + // unique version identifier + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + // list of features compatible with the specified identifier + Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` +} + +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { + return fileDescriptor_3bf62bacf5a27ee9, []int{8} +} +func (m *Version) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Version.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Version) XXX_Merge(src proto.Message) { + xxx_messageInfo_Version.Merge(m, src) +} +func (m *Version) XXX_Size() int { + return m.Size() +} +func (m *Version) XXX_DiscardUnknown() { + xxx_messageInfo_Version.DiscardUnknown(m) +} + +var xxx_messageInfo_Version proto.InternalMessageInfo + func init() { proto.RegisterEnum("ibc.connection.State", State_name, State_value) proto.RegisterType((*MsgConnectionOpenInit)(nil), "ibc.connection.MsgConnectionOpenInit") @@ -616,67 +662,71 @@ func init() { proto.RegisterType((*Counterparty)(nil), "ibc.connection.Counterparty") proto.RegisterType((*ClientPaths)(nil), "ibc.connection.ClientPaths") proto.RegisterType((*ConnectionPaths)(nil), "ibc.connection.ConnectionPaths") + proto.RegisterType((*Version)(nil), "ibc.connection.Version") } func init() { proto.RegisterFile("ibc/connection/connection.proto", fileDescriptor_3bf62bacf5a27ee9) } var fileDescriptor_3bf62bacf5a27ee9 = []byte{ - // 879 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x56, 0x4f, 0x6f, 0xe2, 0x46, - 0x14, 0xc7, 0xc6, 0xfc, 0x1b, 0x20, 0x61, 0xbd, 0xd0, 0xb5, 0xdc, 0x95, 0xed, 0x7a, 0x2f, 0xa8, - 0x55, 0xa0, 0xbb, 0x5b, 0xed, 0x01, 0xa9, 0x07, 0x20, 0x44, 0xb5, 0xd4, 0xb0, 0xc8, 0x21, 0x95, - 0xba, 0x17, 0x04, 0xb6, 0x81, 0x11, 0xc1, 0x46, 0xf6, 0xa4, 0x5a, 0xbe, 0x41, 0xc4, 0xa9, 0xd7, - 0x1e, 0x22, 0x55, 0xca, 0x97, 0xe8, 0x27, 0xa8, 0x72, 0xcc, 0xb1, 0x27, 0xab, 0x22, 0xdf, 0x00, - 0xf5, 0xd4, 0x53, 0xe5, 0x19, 0x63, 0x1b, 0x88, 0x5a, 0xa5, 0xe4, 0x50, 0xf5, 0xc4, 0xfb, 0xf3, - 0x9b, 0x37, 0xef, 0xbd, 0xdf, 0xf3, 0x63, 0x80, 0x08, 0x07, 0x5a, 0x55, 0xb3, 0x4c, 0xd3, 0xd0, - 0x10, 0xb4, 0xcc, 0x88, 0x58, 0x99, 0xd9, 0x16, 0xb2, 0xd8, 0x03, 0x38, 0xd0, 0x2a, 0xa1, 0x95, - 0x2f, 0x8e, 0xac, 0x91, 0x85, 0x5d, 0x55, 0x4f, 0x22, 0x28, 0xde, 0x0f, 0x33, 0x9d, 0x42, 0x34, - 0x35, 0x4c, 0x14, 0x11, 0x09, 0x40, 0xfe, 0x85, 0x06, 0xa5, 0x53, 0x67, 0xd4, 0x0c, 0x02, 0xbd, - 0x9f, 0x19, 0xa6, 0x62, 0x42, 0xc4, 0x7e, 0x0d, 0x32, 0xda, 0x05, 0x34, 0x4c, 0xd4, 0x83, 0x3a, - 0x47, 0x49, 0x54, 0x39, 0xd3, 0x90, 0x96, 0xae, 0x98, 0x6e, 0x62, 0xa3, 0x72, 0xbc, 0x72, 0xc5, - 0xc2, 0xbc, 0x3f, 0xbd, 0xa8, 0xc9, 0x01, 0x4c, 0x56, 0xd3, 0x44, 0x56, 0x74, 0xf6, 0x14, 0xe4, - 0xc3, 0xec, 0xbc, 0x10, 0x34, 0x0e, 0x51, 0x5e, 0xba, 0x62, 0x2e, 0xbc, 0x0d, 0x87, 0x29, 0xfa, - 0x61, 0xa2, 0x70, 0x59, 0xcd, 0x85, 0xba, 0xa2, 0xb3, 0x27, 0x20, 0xa7, 0x59, 0x97, 0x26, 0x32, - 0xec, 0x59, 0xdf, 0x46, 0x73, 0x2e, 0x2e, 0x51, 0xe5, 0xec, 0x9b, 0x97, 0x95, 0xcd, 0x2e, 0x54, - 0x9a, 0x11, 0x4c, 0x83, 0xb9, 0x75, 0xc5, 0x98, 0xba, 0x71, 0x8e, 0x55, 0x40, 0xd2, 0x81, 0x23, - 0xd3, 0xb0, 0x39, 0x46, 0xa2, 0xca, 0xb9, 0xc6, 0xeb, 0x3f, 0x5d, 0xf1, 0x68, 0x04, 0xd1, 0xf8, - 0x72, 0x50, 0xd1, 0xac, 0x69, 0x55, 0xb3, 0x9c, 0xa9, 0xe5, 0xf8, 0x3f, 0x47, 0x8e, 0x3e, 0xa9, - 0xa2, 0xf9, 0xcc, 0x70, 0x2a, 0x75, 0x4d, 0xab, 0xeb, 0xba, 0x6d, 0x38, 0x8e, 0xea, 0x07, 0x90, - 0xff, 0x60, 0x40, 0x71, 0xa7, 0x75, 0x5d, 0x7b, 0xfe, 0x3f, 0xed, 0xdc, 0x39, 0x28, 0x45, 0xf5, - 0xde, 0x0f, 0x86, 0xed, 0x40, 0xcb, 0x74, 0x38, 0x46, 0x8a, 0x7b, 0x15, 0xae, 0x5c, 0xf1, 0xe5, - 0x3a, 0x9d, 0x07, 0x60, 0xb2, 0x5a, 0x8c, 0xda, 0xbf, 0xf3, 0xcd, 0xec, 0x57, 0x00, 0xcc, 0x6c, - 0xcb, 0x1a, 0xf6, 0xa0, 0x09, 0x11, 0x97, 0xc0, 0xa4, 0x94, 0x56, 0xae, 0xf8, 0x8c, 0xc4, 0x0a, - 0x7d, 0xb2, 0x9a, 0xc1, 0x0a, 0x1e, 0xce, 0xcf, 0x40, 0x8e, 0x78, 0xc6, 0x06, 0x1c, 0x8d, 0x11, - 0x97, 0x94, 0xa8, 0x32, 0xa3, 0x66, 0xb1, 0xed, 0x1b, 0x6c, 0x62, 0x9b, 0xe0, 0x90, 0x40, 0x34, - 0xcb, 0x74, 0x0c, 0xd3, 0xb9, 0x74, 0xb8, 0x14, 0x8e, 0xce, 0xaf, 0x5c, 0xf1, 0x93, 0x68, 0xf4, - 0x00, 0x20, 0xab, 0x07, 0xd8, 0xd2, 0x5c, 0x1b, 0xd8, 0x13, 0x50, 0x08, 0xbc, 0xeb, 0xbb, 0xd2, - 0xde, 0x5d, 0x8d, 0x4f, 0x57, 0xae, 0xf8, 0x22, 0x68, 0xff, 0x06, 0x42, 0x56, 0x0f, 0x03, 0x93, - 0x9f, 0x4c, 0x38, 0x76, 0x99, 0x7d, 0xc7, 0xee, 0xd7, 0xf8, 0x03, 0x63, 0x57, 0xd7, 0x26, 0xbb, - 0x73, 0x43, 0xed, 0x35, 0x37, 0x1c, 0x48, 0xf9, 0xdc, 0x91, 0x01, 0x54, 0xd7, 0x2a, 0xfb, 0x1a, - 0x10, 0x26, 0x7a, 0xc8, 0x26, 0xe3, 0x94, 0x6b, 0x14, 0xc3, 0x99, 0x0e, 0x5c, 0xb2, 0x9a, 0xc6, - 0xb2, 0xf7, 0x49, 0xd4, 0xb6, 0xf8, 0x62, 0x70, 0x0f, 0x5f, 0xac, 0x5c, 0xf1, 0x79, 0xf4, 0xd4, - 0xba, 0x7f, 0xff, 0x44, 0x64, 0xe2, 0x49, 0x88, 0x4c, 0xee, 0x45, 0x64, 0x6a, 0x5f, 0x22, 0x6f, - 0x68, 0xc0, 0xed, 0x10, 0xd9, 0xb4, 0xcc, 0x21, 0xb4, 0xa7, 0x4f, 0x4d, 0x66, 0x40, 0x59, 0x5f, - 0x9b, 0x60, 0x3a, 0x1f, 0xa0, 0xac, 0xaf, 0x4d, 0xd6, 0x94, 0x79, 0xe3, 0xb4, 0x4d, 0x59, 0xfc, - 0x11, 0x94, 0x3d, 0xe1, 0x96, 0x5d, 0xd0, 0x20, 0x1f, 0x16, 0xdc, 0x32, 0x75, 0xf6, 0x15, 0xa0, - 0x83, 0x7e, 0x3c, 0x5f, 0xba, 0x22, 0x8d, 0xbb, 0x90, 0x21, 0x49, 0x79, 0xa5, 0xd3, 0x50, 0xdf, - 0xdc, 0xc1, 0xf4, 0xa3, 0x77, 0x30, 0x0f, 0xd2, 0xc1, 0x7e, 0x8b, 0x7b, 0xfb, 0x4d, 0x0d, 0x74, - 0xf6, 0x0b, 0x90, 0x70, 0x50, 0x1f, 0x19, 0xb8, 0xb6, 0x83, 0x37, 0xa5, 0xed, 0x4d, 0x7a, 0xe6, - 0x39, 0x55, 0x82, 0xd9, 0xd9, 0xbe, 0x89, 0x7f, 0xb7, 0x7d, 0x6b, 0xcc, 0xd5, 0xcf, 0x62, 0x4c, - 0x76, 0x29, 0x90, 0x8b, 0x42, 0xff, 0x63, 0x7f, 0x35, 0x35, 0x90, 0x9c, 0xd9, 0xc6, 0x10, 0x7e, - 0xdc, 0xfa, 0x93, 0x09, 0xde, 0x1c, 0xa7, 0x86, 0x3d, 0xb9, 0x30, 0x3a, 0x18, 0xe3, 0x97, 0xe9, - 0x9f, 0xf0, 0x0b, 0x7c, 0x05, 0xb2, 0x24, 0xf5, 0x4e, 0x1f, 0x8d, 0x1d, 0xb6, 0x08, 0x12, 0x33, - 0x4f, 0xe0, 0x28, 0xcc, 0x01, 0x51, 0xe4, 0x21, 0x38, 0x0c, 0x93, 0x23, 0xc0, 0x3d, 0xfb, 0x10, - 0xdc, 0x43, 0x47, 0xee, 0xf9, 0xfc, 0x27, 0x0a, 0x24, 0x30, 0x99, 0xec, 0x3b, 0x20, 0x9e, 0x75, - 0xeb, 0xdd, 0x56, 0xef, 0xbc, 0xad, 0xb4, 0x95, 0xae, 0x52, 0xff, 0x56, 0xf9, 0xd0, 0x3a, 0xee, - 0x9d, 0xb7, 0xcf, 0x3a, 0xad, 0xa6, 0x72, 0xa2, 0xb4, 0x8e, 0x0b, 0x31, 0xfe, 0xd9, 0xe2, 0x5a, - 0xca, 0x6f, 0x00, 0x58, 0x0e, 0x00, 0x72, 0xce, 0x33, 0x16, 0x28, 0x3e, 0xbd, 0xb8, 0x96, 0x18, - 0x4f, 0x66, 0x05, 0x90, 0x27, 0x9e, 0xae, 0xfa, 0xfd, 0xfb, 0x4e, 0xab, 0x5d, 0xa0, 0xf9, 0xec, - 0xe2, 0x5a, 0x4a, 0xf9, 0x6a, 0x78, 0x12, 0x3b, 0xe3, 0xe4, 0xa4, 0x27, 0xf3, 0xcc, 0xd5, 0x8d, - 0x10, 0x6b, 0x74, 0x6e, 0x97, 0x02, 0x75, 0xb7, 0x14, 0xa8, 0xdf, 0x97, 0x02, 0xf5, 0xe3, 0xbd, - 0x10, 0xbb, 0xbb, 0x17, 0x62, 0xbf, 0xdd, 0x0b, 0xb1, 0x0f, 0xef, 0xfe, 0xf6, 0x3b, 0xfb, 0x58, - 0xf5, 0x5e, 0x84, 0x5f, 0xbe, 0x3d, 0x8a, 0xbc, 0x2d, 0xf1, 0xb7, 0x37, 0x48, 0xe2, 0x07, 0xe1, - 0xdb, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x37, 0x42, 0xa5, 0xea, 0x7a, 0x0a, 0x00, 0x00, + // 914 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x56, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0x16, 0x29, 0xea, 0x6f, 0x2c, 0xdb, 0x0a, 0x23, 0x37, 0x04, 0x1b, 0x90, 0x2c, 0x73, 0x11, + 0x5a, 0x58, 0x6a, 0x92, 0x22, 0x07, 0x03, 0x3d, 0x48, 0xb2, 0x8c, 0x12, 0xad, 0x1d, 0x81, 0x96, + 0x0b, 0x34, 0x17, 0x41, 0x26, 0x57, 0xf2, 0x42, 0x16, 0x29, 0x90, 0xeb, 0x22, 0x7e, 0x83, 0xc0, + 0xa7, 0x5e, 0x7b, 0x30, 0x50, 0x20, 0x2f, 0xd1, 0x27, 0x28, 0x72, 0xcc, 0xb1, 0x27, 0xa2, 0x90, + 0xdf, 0x40, 0xe8, 0xa9, 0xa7, 0x82, 0xbb, 0x14, 0x49, 0x59, 0x46, 0x8b, 0x54, 0x3e, 0x14, 0x39, + 0x71, 0x7e, 0xbe, 0x9d, 0xdd, 0x99, 0x6f, 0x38, 0xbb, 0xa0, 0xe2, 0x53, 0xab, 0x61, 0xb9, 0x8e, + 0x83, 0x2c, 0x82, 0x5d, 0x27, 0x25, 0xd6, 0xa7, 0x9e, 0x4b, 0x5c, 0x71, 0x0b, 0x9f, 0x5a, 0xf5, + 0xc4, 0x2a, 0x57, 0x47, 0xee, 0xc8, 0xa5, 0xae, 0x46, 0x28, 0x31, 0x94, 0x1c, 0x85, 0x99, 0x4c, + 0x30, 0x99, 0x20, 0x87, 0xa4, 0x44, 0x06, 0xd0, 0x7f, 0xe5, 0x61, 0xe7, 0xd0, 0x1f, 0xb5, 0xe3, + 0x40, 0x2f, 0xa7, 0xc8, 0x31, 0x1c, 0x4c, 0xc4, 0xaf, 0xa1, 0x64, 0x9d, 0x63, 0xe4, 0x90, 0x3e, + 0xb6, 0x25, 0x4e, 0xe3, 0x6a, 0xa5, 0x96, 0x36, 0x0b, 0xd4, 0x62, 0x9b, 0x1a, 0x8d, 0xfd, 0x79, + 0xa0, 0x56, 0x2e, 0x07, 0x93, 0xf3, 0x3d, 0x3d, 0x86, 0xe9, 0x66, 0x91, 0xc9, 0x86, 0x2d, 0x1e, + 0xc2, 0x66, 0x72, 0xba, 0x30, 0x04, 0x4f, 0x43, 0xd4, 0x66, 0x81, 0x5a, 0x4e, 0x76, 0xa3, 0x61, + 0xaa, 0x51, 0x98, 0x34, 0x5c, 0x37, 0xcb, 0x89, 0x6e, 0xd8, 0xe2, 0x01, 0x94, 0x2d, 0xf7, 0xc2, + 0x21, 0xc8, 0x9b, 0x0e, 0x3c, 0x72, 0x29, 0x65, 0x35, 0xae, 0xb6, 0xf1, 0xec, 0x71, 0x7d, 0xb9, + 0x0a, 0xf5, 0x76, 0x0a, 0xd3, 0x12, 0xde, 0x05, 0x6a, 0xc6, 0x5c, 0x5a, 0x27, 0x1a, 0x90, 0xf7, + 0xf1, 0xc8, 0x41, 0x9e, 0x24, 0x68, 0x5c, 0xad, 0xdc, 0x7a, 0xfa, 0x57, 0xa0, 0xee, 0x8e, 0x30, + 0x39, 0xbb, 0x38, 0xad, 0x5b, 0xee, 0xa4, 0x61, 0xb9, 0xfe, 0xc4, 0xf5, 0xa3, 0xcf, 0xae, 0x6f, + 0x8f, 0x1b, 0xe4, 0x72, 0x8a, 0xfc, 0x7a, 0xd3, 0xb2, 0x9a, 0xb6, 0xed, 0x21, 0xdf, 0x37, 0xa3, + 0x00, 0xfa, 0x9f, 0x02, 0x54, 0x57, 0x4a, 0xd7, 0xf3, 0x2e, 0x3f, 0xd2, 0xca, 0x9d, 0xc0, 0x4e, + 0x5a, 0xef, 0xff, 0x88, 0x3c, 0x1f, 0xbb, 0x8e, 0x2f, 0x09, 0x5a, 0x36, 0xcc, 0x70, 0x1e, 0xa8, + 0x8f, 0x17, 0xc7, 0xb9, 0x03, 0xa6, 0x9b, 0xd5, 0xb4, 0xfd, 0xfb, 0xc8, 0x2c, 0x7e, 0x05, 0x30, + 0xf5, 0x5c, 0x77, 0xd8, 0xc7, 0x0e, 0x26, 0x52, 0x8e, 0x92, 0xb2, 0x33, 0x0f, 0xd4, 0x07, 0x2c, + 0x56, 0xe2, 0xd3, 0xcd, 0x12, 0x55, 0x68, 0x73, 0x7e, 0x06, 0x65, 0xe6, 0x39, 0x43, 0x78, 0x74, + 0x46, 0xa4, 0xbc, 0xc6, 0xd5, 0x04, 0x73, 0x83, 0xda, 0xbe, 0xa1, 0x26, 0xb1, 0x0d, 0xdb, 0x0c, + 0x62, 0xb9, 0x8e, 0x8f, 0x1c, 0xff, 0xc2, 0x97, 0x0a, 0x34, 0xba, 0x3c, 0x0f, 0xd4, 0x4f, 0xd2, + 0xd1, 0x63, 0x80, 0x6e, 0x6e, 0x51, 0x4b, 0x7b, 0x61, 0x10, 0x0f, 0xa0, 0x12, 0x7b, 0x17, 0x7b, + 0x15, 0xc3, 0xbd, 0x5a, 0x9f, 0xce, 0x03, 0xf5, 0x51, 0x5c, 0xfe, 0x25, 0x84, 0x6e, 0x6e, 0xc7, + 0xa6, 0xe8, 0x30, 0x49, 0xdb, 0x95, 0xd6, 0x6d, 0xbb, 0xdf, 0xb2, 0x77, 0xb4, 0x5d, 0xd3, 0x1a, + 0xaf, 0xf6, 0x0d, 0xb7, 0x56, 0xdf, 0x48, 0x50, 0x88, 0xb8, 0x63, 0x0d, 0x68, 0x2e, 0x54, 0xf1, + 0x29, 0x30, 0x26, 0xfa, 0xc4, 0x63, 0xed, 0x54, 0x6e, 0x55, 0x93, 0x9e, 0x8e, 0x5d, 0xba, 0x59, + 0xa4, 0x72, 0xf8, 0x4b, 0xec, 0xdd, 0xe2, 0x4b, 0xa0, 0x35, 0x7c, 0x34, 0x0f, 0xd4, 0x87, 0xe9, + 0x55, 0x8b, 0xfa, 0xfd, 0x1b, 0x91, 0xb9, 0x7b, 0x21, 0x32, 0xbf, 0x16, 0x91, 0x85, 0x75, 0x89, + 0x7c, 0xcb, 0x83, 0xb4, 0x42, 0x64, 0xdb, 0x75, 0x86, 0xd8, 0x9b, 0xdc, 0x37, 0x99, 0x31, 0x65, + 0x03, 0x6b, 0x4c, 0xe9, 0xbc, 0x83, 0xb2, 0x81, 0x35, 0x5e, 0x50, 0x16, 0xb6, 0xd3, 0x6d, 0xca, + 0xb2, 0x1f, 0x40, 0xd9, 0x3d, 0x4e, 0xd9, 0x2b, 0x1e, 0x36, 0x93, 0x84, 0x3b, 0x8e, 0x2d, 0x3e, + 0x01, 0x3e, 0xae, 0xc7, 0xc3, 0x59, 0xa0, 0xf2, 0xb4, 0x0a, 0x25, 0x76, 0xa8, 0x30, 0x75, 0x1e, + 0xdb, 0xcb, 0x33, 0x98, 0xff, 0xe0, 0x19, 0x2c, 0x43, 0x31, 0x9e, 0x6f, 0xd9, 0x70, 0xbe, 0x99, + 0xb1, 0x2e, 0x7e, 0x01, 0x39, 0x9f, 0x0c, 0x08, 0xa2, 0xb9, 0x6d, 0x3d, 0xdb, 0xb9, 0x3d, 0x49, + 0x8f, 0x43, 0xa7, 0xc9, 0x30, 0x2b, 0xd3, 0x37, 0xf7, 0xdf, 0xa6, 0xef, 0x9e, 0xf0, 0xe6, 0x17, + 0x35, 0xa3, 0x07, 0x1c, 0x94, 0xd3, 0xd0, 0xff, 0xd9, 0x55, 0xb3, 0x07, 0xf9, 0xa9, 0x87, 0x86, + 0xf8, 0xf5, 0xad, 0x4b, 0x26, 0x7e, 0x73, 0x1c, 0x22, 0x6f, 0x7c, 0x8e, 0xba, 0x14, 0x13, 0xa5, + 0x19, 0xad, 0x88, 0x12, 0x7c, 0x02, 0x1b, 0xec, 0xe8, 0xdd, 0x01, 0x39, 0xf3, 0xc5, 0x2a, 0xe4, + 0xa6, 0xa1, 0x20, 0x71, 0x94, 0x03, 0xa6, 0xe8, 0x43, 0xd8, 0x4e, 0x0e, 0xc7, 0x80, 0x6b, 0xd6, + 0x21, 0xde, 0x87, 0x4f, 0xef, 0xf3, 0x2d, 0x14, 0xa2, 0x6b, 0x4a, 0x54, 0x00, 0xb0, 0x8d, 0x1c, + 0x82, 0x87, 0x18, 0x79, 0x6c, 0x03, 0x33, 0x65, 0x09, 0xfb, 0x65, 0x88, 0x06, 0xe4, 0xc2, 0x43, + 0x8b, 0x18, 0xb1, 0xce, 0x32, 0xfb, 0xfc, 0x67, 0x0e, 0x72, 0xb4, 0x33, 0xc4, 0x17, 0xa0, 0x1e, + 0xf7, 0x9a, 0xbd, 0x4e, 0xff, 0xe4, 0xc8, 0x38, 0x32, 0x7a, 0x46, 0xf3, 0x3b, 0xe3, 0x55, 0x67, + 0xbf, 0x7f, 0x72, 0x74, 0xdc, 0xed, 0xb4, 0x8d, 0x03, 0xa3, 0xb3, 0x5f, 0xc9, 0xc8, 0x0f, 0xae, + 0xae, 0xb5, 0xcd, 0x25, 0x80, 0x28, 0x01, 0xb0, 0x75, 0xa1, 0xb1, 0xc2, 0xc9, 0xc5, 0xab, 0x6b, + 0x4d, 0x08, 0x65, 0x51, 0x81, 0x4d, 0xe6, 0xe9, 0x99, 0x3f, 0xbc, 0xec, 0x76, 0x8e, 0x2a, 0xbc, + 0xbc, 0x71, 0x75, 0xad, 0x15, 0x22, 0x35, 0x59, 0x49, 0x9d, 0x59, 0xb6, 0x32, 0x94, 0x65, 0xe1, + 0xcd, 0x5b, 0x25, 0xd3, 0xea, 0xbe, 0x9b, 0x29, 0xdc, 0xfb, 0x99, 0xc2, 0xfd, 0x31, 0x53, 0xb8, + 0x9f, 0x6e, 0x94, 0xcc, 0xfb, 0x1b, 0x25, 0xf3, 0xfb, 0x8d, 0x92, 0x79, 0xf5, 0xe2, 0x1f, 0x7f, + 0xda, 0xd7, 0x8d, 0xf0, 0x79, 0xf9, 0xe5, 0xf3, 0xdd, 0xd4, 0x43, 0x95, 0xfe, 0xc8, 0xa7, 0x79, + 0xfa, 0xba, 0x7c, 0xfe, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x4e, 0x8d, 0xdb, 0xc7, 0x0a, + 0x00, 0x00, } func (m *MsgConnectionOpenInit) Marshal() (dAtA []byte, err error) { @@ -1116,6 +1166,45 @@ func (m *ConnectionPaths) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Version) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Version) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Version) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Features) > 0 { + for iNdEx := len(m.Features) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Features[iNdEx]) + copy(dAtA[i:], m.Features[iNdEx]) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Features[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Identifier) > 0 { + i -= len(m.Identifier) + copy(dAtA[i:], m.Identifier) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Identifier))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintConnection(dAtA []byte, offset int, v uint64) int { offset -= sovConnection(v) base := offset @@ -1333,6 +1422,25 @@ func (m *ConnectionPaths) Size() (n int) { return n } +func (m *Version) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + if len(m.Features) > 0 { + for _, s := range m.Features { + l = len(s) + n += 1 + l + sovConnection(uint64(l)) + } + } + return n +} + func sovConnection(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2827,6 +2935,123 @@ func (m *ConnectionPaths) Unmarshal(dAtA []byte) error { } return nil } +func (m *Version) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Version: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Version: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Features", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Features = append(m.Features, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipConnection(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ibc/03-connection/types/connection_test.go b/x/ibc/03-connection/types/connection_test.go index a5d40f8e17..53251c7825 100644 --- a/x/ibc/03-connection/types/connection_test.go +++ b/x/ibc/03-connection/types/connection_test.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) var ( @@ -24,17 +25,17 @@ func TestConnectionValidateBasic(t *testing.T) { }{ { "valid connection", - types.ConnectionEnd{connectionID, clientID, []string{types.DefaultIBCVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, + types.ConnectionEnd{connectionID, clientID, []string{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, true, }, { "invalid connection id", - types.ConnectionEnd{"(connectionIDONE)", clientID, []string{types.DefaultIBCVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, + types.ConnectionEnd{"(connectionIDONE)", clientID, []string{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, false, }, { "invalid client id", - types.ConnectionEnd{connectionID, "(clientID1)", []string{types.DefaultIBCVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, + types.ConnectionEnd{connectionID, "(clientID1)", []string{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}}, false, }, { @@ -49,7 +50,7 @@ func TestConnectionValidateBasic(t *testing.T) { }, { "invalid counterparty", - types.ConnectionEnd{connectionID, clientID, []string{types.DefaultIBCVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, emptyPrefix}}, + types.ConnectionEnd{connectionID, clientID, []string{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, emptyPrefix}}, false, }, } diff --git a/x/ibc/03-connection/types/genesis_test.go b/x/ibc/03-connection/types/genesis_test.go index 6c2c7ff7ab..ee1a02c623 100644 --- a/x/ibc/03-connection/types/genesis_test.go +++ b/x/ibc/03-connection/types/genesis_test.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func TestValidateGenesis(t *testing.T) { @@ -26,7 +27,7 @@ func TestValidateGenesis(t *testing.T) { name: "valid genesis", genState: types.NewGenesisState( []types.ConnectionEnd{ - types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{types.DefaultIBCVersion}), + types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{ibctesting.ConnectionVersion}), }, []types.ConnectionPaths{ {clientID, []string{host.ConnectionPath(connectionID)}}, @@ -38,7 +39,7 @@ func TestValidateGenesis(t *testing.T) { name: "invalid connection", genState: types.NewGenesisState( []types.ConnectionEnd{ - types.NewConnectionEnd(types.INIT, connectionID, "(CLIENTIDONE)", types.Counterparty{clientID, connectionID, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{types.DefaultIBCVersion}), + types.NewConnectionEnd(types.INIT, connectionID, "(CLIENTIDONE)", types.Counterparty{clientID, connectionID, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{ibctesting.ConnectionVersion}), }, []types.ConnectionPaths{ {clientID, []string{host.ConnectionPath(connectionID)}}, @@ -50,7 +51,7 @@ func TestValidateGenesis(t *testing.T) { name: "invalid client id", genState: types.NewGenesisState( []types.ConnectionEnd{ - types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{types.DefaultIBCVersion}), + types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{ibctesting.ConnectionVersion}), }, []types.ConnectionPaths{ {"(CLIENTIDONE)", []string{host.ConnectionPath(connectionID)}}, @@ -62,7 +63,7 @@ func TestValidateGenesis(t *testing.T) { name: "invalid path", genState: types.NewGenesisState( []types.ConnectionEnd{ - types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{types.DefaultIBCVersion}), + types.NewConnectionEnd(types.INIT, connectionID, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{ibctesting.ConnectionVersion}), }, []types.ConnectionPaths{ {clientID, []string{connectionID}}, diff --git a/x/ibc/03-connection/types/msgs.go b/x/ibc/03-connection/types/msgs.go index 9b03709cb9..f40c4165a3 100644 --- a/x/ibc/03-connection/types/msgs.go +++ b/x/ibc/03-connection/types/msgs.go @@ -102,9 +102,9 @@ func (msg MsgConnectionOpenTry) ValidateBasic() error { if len(msg.CounterpartyVersions) == 0 { return sdkerrors.Wrap(sdkerrors.ErrInvalidVersion, "empty counterparty versions") } - for _, version := range msg.CounterpartyVersions { - if err := host.ConnectionVersionValidator(version); err != nil { - return err + for i, version := range msg.CounterpartyVersions { + if err := ValidateVersion(version); err != nil { + return sdkerrors.Wrapf(err, "basic validation failed on version with index %d", i) } } if len(msg.ProofInit) == 0 { @@ -169,7 +169,7 @@ func (msg MsgConnectionOpenAck) ValidateBasic() error { if err := host.ConnectionIdentifierValidator(msg.ConnectionID); err != nil { return sdkerrors.Wrap(err, "invalid connection ID") } - if err := host.ConnectionVersionValidator(msg.Version); err != nil { + if err := ValidateVersion(msg.Version); err != nil { return err } if len(msg.ProofTry) == 0 { diff --git a/x/ibc/03-connection/types/msgs_test.go b/x/ibc/03-connection/types/msgs_test.go index 365d874899..6ea55060b6 100644 --- a/x/ibc/03-connection/types/msgs_test.go +++ b/x/ibc/03-connection/types/msgs_test.go @@ -16,6 +16,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) var ( @@ -103,18 +104,19 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { signer, _ := sdk.AccAddressFromBech32("cosmos1ckgw5d7jfj7wwxjzs9fdrdev9vc8dzcw3n2lht") testMsgs := []*types.MsgConnectionOpenTry{ - types.NewMsgConnectionOpenTry("test/conn1", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "test/iris", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "ibc/test", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "test/conn1", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", emptyPrefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("test/conn1", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "test/iris", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "ibc/test", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "test/conn1", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", emptyPrefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{}, suite.proof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, emptyProof, suite.proof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, emptyProof, 10, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 0, 10, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 0, signer), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, nil), - types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{types.DefaultIBCVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, emptyProof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, emptyProof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 0, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 0, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, nil), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{ibctesting.ConnectionVersion}, suite.proof, suite.proof, 10, 10, signer), + types.NewMsgConnectionOpenTry("ibcconntest", "clienttotesta", "connectiontotest", "clienttotest", prefix, []string{"(invalid version)"}, suite.proof, suite.proof, 10, 10, signer), } var testCases = []struct { @@ -134,6 +136,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { {testMsgs[9], false, "invalid consensusHeight"}, {testMsgs[10], false, "empty singer"}, {testMsgs[11], true, "success"}, + {testMsgs[12], false, "invalid version"}, } for i, tc := range testCases { @@ -150,14 +153,14 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { signer, _ := sdk.AccAddressFromBech32("cosmos1ckgw5d7jfj7wwxjzs9fdrdev9vc8dzcw3n2lht") testMsgs := []*types.MsgConnectionOpenAck{ - types.NewMsgConnectionOpenAck("test/conn1", suite.proof, suite.proof, 10, 10, types.DefaultIBCVersion, signer), - types.NewMsgConnectionOpenAck("ibcconntest", emptyProof, suite.proof, 10, 10, types.DefaultIBCVersion, signer), - types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, emptyProof, 10, 10, types.DefaultIBCVersion, signer), - types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 0, 10, types.DefaultIBCVersion, signer), - types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 0, types.DefaultIBCVersion, signer), + types.NewMsgConnectionOpenAck("test/conn1", suite.proof, suite.proof, 10, 10, ibctesting.ConnectionVersion, signer), + types.NewMsgConnectionOpenAck("ibcconntest", emptyProof, suite.proof, 10, 10, ibctesting.ConnectionVersion, signer), + types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, emptyProof, 10, 10, ibctesting.ConnectionVersion, signer), + types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 0, 10, ibctesting.ConnectionVersion, signer), + types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 0, ibctesting.ConnectionVersion, signer), types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 10, "", signer), - types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 10, types.DefaultIBCVersion, nil), - types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 10, types.DefaultIBCVersion, signer), + types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 10, ibctesting.ConnectionVersion, nil), + types.NewMsgConnectionOpenAck("ibcconntest", suite.proof, suite.proof, 10, 10, ibctesting.ConnectionVersion, signer), } var testCases = []struct { msg *types.MsgConnectionOpenAck diff --git a/x/ibc/03-connection/types/version.go b/x/ibc/03-connection/types/version.go index a6d60d803e..6d6097c832 100644 --- a/x/ibc/03-connection/types/version.go +++ b/x/ibc/03-connection/types/version.go @@ -1,127 +1,184 @@ package types import ( - "fmt" "strings" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) var ( - // DefaultIBCVersion represents the latest supported version of IBC. - // The current version supports only ORDERED and UNORDERED channels. - DefaultIBCVersion = CreateVersionString("1", []string{"ORDER_ORDERED", "ORDER_UNORDERED"}) + // DefaultIBCVersion represents the latest supported version of IBC used + // in connection version negotiation. The current version supports only + // ORDERED and UNORDERED channels and requires at least one channel type + // to be agreed upon. + DefaultIBCVersion = NewVersion(DefaultIBCVersionIdentifier, []string{"ORDER_ORDERED", "ORDER_UNORDERED"}) + + // DefaultIBCVersionIdentifier is the IBC v1.0.0 protocol version identifier + DefaultIBCVersionIdentifier = "1" + + // AllowNilFeatureSet is a helper map to indicate if a specified version + // identifier is allowed to have a nil feature set. Any versions supported, + // but not included in the map default to not supporting nil feature sets. + allowNilFeatureSet = map[string]bool{ + DefaultIBCVersionIdentifier: false, + } ) +// NewVersion returns a new instance of Version. +func NewVersion(identifier string, features []string) Version { + return Version{ + Identifier: identifier, + Features: features, + } +} + +// GetIdentifier implements the VersionI interface +func (version Version) GetIdentifier() string { + return version.Identifier +} + +// GetFeatures implements the VersionI interface +func (version Version) GetFeatures() []string { + return version.Features +} + +// ValidateVersion does basic validation of the version identifier and +// features. It unmarshals the version string into a Version object. +func ValidateVersion(encodedVersion string) error { + var version Version + if err := SubModuleCdc.UnmarshalBinaryBare([]byte(encodedVersion), &version); err != nil { + return sdkerrors.Wrapf(err, "failed to unmarshal version string %s", encodedVersion) + } + + if strings.TrimSpace(version.Identifier) == "" { + return sdkerrors.Wrap(ErrInvalidVersion, "version identifier cannot be blank") + } + for i, feature := range version.Features { + if strings.TrimSpace(feature) == "" { + return sdkerrors.Wrapf(ErrInvalidVersion, "feature cannot be blank, index %d", i) + } + } + + return nil +} + +// Encode proto encodes the version and returns the bytes as a string. +func (version Version) Encode() (string, error) { + encodedVersion, err := SubModuleCdc.MarshalBinaryBare(&version) + if err != nil { + return "", err + } + + return string(encodedVersion), nil +} + +// EncodeVersions iterates over the provided versions and marshals each +// into proto encoded strings. This represents the stored value of the version +// in the connection end as well as the value passed over the wire. +func EncodeVersions(versions []Version) ([]string, error) { + encodedVersions := make([]string, len(versions)) + + for i, version := range versions { + ver, err := version.Encode() + if err != nil { + return nil, err + } + + encodedVersions[i] = ver + } + + return encodedVersions, nil +} + +// DecodeVersion unmarshals a proto encoded version into a Version struct. +func DecodeVersion(encodedVersion string) (Version, error) { + var version Version + if err := SubModuleCdc.UnmarshalBinaryBare([]byte(encodedVersion), &version); err != nil { + return Version{}, sdkerrors.Wrapf(err, "failed to unmarshal version string %s", encodedVersion) + } + + return version, nil +} + +// DecodeVersions returns the supplied list of proto encoded version strings +// as unmarshalled Version structs. +func DecodeVersions(encodedVersions []string) ([]Version, error) { + versions := make([]Version, len(encodedVersions)) + + for i, encodedVersion := range encodedVersions { + version, err := DecodeVersion(encodedVersion) + if err != nil { + return nil, err + } + + versions[i] = version + } + + return versions, nil +} + // GetCompatibleVersions returns a descending ordered set of compatible IBC // versions for the caller chain's connection end. The latest supported // version should be first element and the set should descend to the oldest // supported version. -func GetCompatibleVersions() []string { - return []string{DefaultIBCVersion} +func GetCompatibleVersions() []Version { + return []Version{DefaultIBCVersion} } -// CreateVersionString constructs a valid connection version given a -// version identifier and feature set. The format is written as: -// -// ([version_identifier],[feature_0,feature_1,feature_2...]) -// -// A connection version is considered invalid if it is not in this format -// or violates one of these rules: -// - the version identifier is empty or contains commas -// - a specified feature contains commas -func CreateVersionString(identifier string, featureSet []string) string { - return fmt.Sprintf("(%s,[%s])", identifier, strings.Join(featureSet, ",")) -} - -// UnpackVersion parses a version string and returns the identifier and the -// feature set of a version. An error is returned if the version is not valid. -func UnpackVersion(version string) (string, []string, error) { - // validate version so valid splitting assumptions can be made - if err := host.ConnectionVersionValidator(version); err != nil { - return "", nil, err +// GetCompatibleEncodedVersions returns the return value from GetCompatibleVersions +// as a proto encoded string. +func GetCompatibleEncodedVersions() []string { + versions, err := EncodeVersions(GetCompatibleVersions()) + if err != nil { + panic(err) // should not occur with properly set hardcoded versions } - // peel off prefix and suffix of the tuple - version = strings.TrimPrefix(version, "(") - version = strings.TrimSuffix(version, ")") - - // split into identifier and feature set - splitVersion := strings.SplitN(version, ",", 2) - if splitVersion[0] == version { - return "", nil, sdkerrors.Wrapf(ErrInvalidVersion, "failed to split version '%s' into identifier and features", version) - } - identifier := splitVersion[0] - - // peel off prefix and suffix of features - featureSet := strings.TrimPrefix(splitVersion[1], "[") - featureSet = strings.TrimSuffix(featureSet, "]") - - // check if features are empty - if len(featureSet) == 0 { - return identifier, []string{}, nil - } - - features := strings.Split(featureSet, ",") - - return identifier, features, nil + return versions } // FindSupportedVersion returns the version with a matching version identifier // if it exists. The returned boolean is true if the version is found and // false otherwise. -func FindSupportedVersion(version string, supportedVersions []string) (string, bool) { - identifier, _, err := UnpackVersion(version) - if err != nil { - return "", false - } - +func FindSupportedVersion(version Version, supportedVersions []Version) (Version, bool) { for _, supportedVersion := range supportedVersions { - supportedIdentifier, _, err := UnpackVersion(supportedVersion) - if err != nil { - continue - } - - if identifier == supportedIdentifier { + if version.GetIdentifier() == supportedVersion.GetIdentifier() { return supportedVersion, true } } - return "", false + return Version{}, false } // PickVersion iterates over the descending ordered set of compatible IBC // versions and selects the first version with a version identifier that is // supported by the counterparty. The returned version contains a feature // set with the intersection of the features supported by the source and -// counterparty chains. This function is called in the ConnOpenTry handshake -// procedure. -func PickVersion(counterpartyVersions []string) (string, error) { - versions := GetCompatibleVersions() +// counterparty chains. If the feature set intersection is nil and this is +// not allowed for the chosen version identifier then the search for a +// compatible version continues. This function is called in the ConnOpenTry +// handshake procedure. +func PickVersion(encodedCounterpartyVersions []string) (string, error) { + counterpartyVersions, err := DecodeVersions(encodedCounterpartyVersions) + if err != nil { + return "", sdkerrors.Wrapf(err, "failed to unmarshal counterparty versions (%s) when attempting to pick compatible version", encodedCounterpartyVersions) + } + supportedVersions := GetCompatibleVersions() - for _, ver := range versions { + for _, supportedVersion := range supportedVersions { // check if the source version is supported by the counterparty - if counterpartyVer, found := FindSupportedVersion(ver, counterpartyVersions); found { - sourceIdentifier, sourceFeatures, err := UnpackVersion(ver) - if err != nil { - return "", err + if counterpartyVersion, found := FindSupportedVersion(supportedVersion, counterpartyVersions); found { + + featureSet := GetFeatureSetIntersection(supportedVersion.GetFeatures(), counterpartyVersion.GetFeatures()) + if len(featureSet) == 0 && !allowNilFeatureSet[supportedVersion.GetIdentifier()] { + continue } - _, counterpartyFeatures, err := UnpackVersion(counterpartyVer) - if err != nil { - return "", err - } - - featureSet := GetFeatureSetIntersection(sourceFeatures, counterpartyFeatures) - - version := CreateVersionString(sourceIdentifier, featureSet) - return version, nil + return NewVersion(supportedVersion.GetIdentifier(), featureSet).Encode() } } return "", sdkerrors.Wrapf( ErrVersionNegotiationFailed, - "failed to find a matching counterparty version (%s) from the supported version list (%s)", counterpartyVersions, versions, + "failed to find a matching counterparty version (%s) from the supported version list (%s)", counterpartyVersions, supportedVersions, ) } @@ -139,37 +196,46 @@ func GetFeatureSetIntersection(sourceFeatureSet, counterpartyFeatureSet []string return featureSet } -// VerifyProposedFeatureSet verifies that the entire feature set in the -// proposed version is supported by this chain. -func VerifyProposedFeatureSet(proposedVersion, supportedVersion string) bool { - _, proposedFeatureSet, err := UnpackVersion(proposedVersion) - if err != nil { - return false +// VerifyProposedVersion verifies that the entire feature set in the +// proposed version is supported by this chain. If the feature set is +// empty it verifies that this is allowed for the specified version +// identifier. +func (version Version) VerifyProposedVersion(proposedVersion Version) error { + if proposedVersion.GetIdentifier() != version.GetIdentifier() { + return sdkerrors.Wrapf( + ErrVersionNegotiationFailed, + "proposed version identifier does not equal supported version identifier (%s != %s)", proposedVersion.GetIdentifier(), version.GetIdentifier(), + ) } - _, supportedFeatureSet, err := UnpackVersion(supportedVersion) - if err != nil { - return false + if len(proposedVersion.GetFeatures()) == 0 && !allowNilFeatureSet[proposedVersion.GetIdentifier()] { + return sdkerrors.Wrapf( + ErrVersionNegotiationFailed, + "nil feature sets are not supported for version identifier (%s)", proposedVersion.GetIdentifier(), + ) } - for _, proposedFeature := range proposedFeatureSet { - if !contains(proposedFeature, supportedFeatureSet) { - return false + for _, proposedFeature := range proposedVersion.GetFeatures() { + if !contains(proposedFeature, version.GetFeatures()) { + return sdkerrors.Wrapf( + ErrVersionNegotiationFailed, + "proposed feature (%s) is not a supported feature set (%s)", proposedFeature, version.GetFeatures(), + ) } } - return true + return nil } -// VerifySupportedFeature takes in a version string and feature string and returns +// VerifySupportedFeature takes in a version and feature string and returns // true if the feature is supported by the version and false otherwise. -func VerifySupportedFeature(version, feature string) bool { - _, featureSet, err := UnpackVersion(version) +func VerifySupportedFeature(encodedVersion, feature string) bool { + version, err := DecodeVersion(encodedVersion) if err != nil { return false } - for _, f := range featureSet { + for _, f := range version.GetFeatures() { if f == feature { return true } @@ -181,7 +247,7 @@ func VerifySupportedFeature(version, feature string) bool { // string set. func contains(elem string, set []string) bool { for _, element := range set { - if strings.TrimSpace(elem) == strings.TrimSpace(element) { + if elem == element { return true } } diff --git a/x/ibc/03-connection/types/version_test.go b/x/ibc/03-connection/types/version_test.go index ed7ce8257c..7bbb55e143 100644 --- a/x/ibc/03-connection/types/version_test.go +++ b/x/ibc/03-connection/types/version_test.go @@ -6,61 +6,79 @@ import ( "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) -// testing of invalid version formats exist within 24-host/validate_test.go -func TestUnpackVersion(t *testing.T) { +func TestValidateVersion(t *testing.T) { testCases := []struct { - name string - version string - expIdentifier string - expFeatures []string - expPass bool + name string + version types.Version + expPass bool }{ - {"valid version", "(1,[ORDERED channel,UNORDERED channel])", "1", []string{"ORDERED channel", "UNORDERED channel"}, true}, - {"valid empty features", "(1,[])", "1", []string{}, true}, - {"empty identifier", "(,[features])", "", []string{}, false}, - {"invalid version", "identifier,[features]", "", []string{}, false}, - {"empty string", " ", "", []string{}, false}, + {"valid version", types.DefaultIBCVersion, true}, + {"valid empty feature set", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{}), true}, + {"empty version identifier", types.NewVersion(" ", []string{"ORDER_UNORDERED"}), false}, + {"empty feature", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_UNORDERED", " "}), false}, } - for _, tc := range testCases { - tc := tc + for i, tc := range testCases { + encodedVersion, err := tc.version.Encode() + require.NoError(t, err, "test case %d failed to marshal version string: %s", i, tc.name) - t.Run(tc.name, func(t *testing.T) { - identifier, features, err := types.UnpackVersion(tc.version) + err = types.ValidateVersion(encodedVersion) - if tc.expPass { - require.NoError(t, err) - require.Equal(t, tc.expIdentifier, identifier) - require.Equal(t, tc.expFeatures, features) - } else { - require.Error(t, err) - } - }) + if tc.expPass { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.Error(t, err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func TestDecodeVersion(t *testing.T) { + testCases := []struct { + name string + version string + expVersion types.Version + expPass bool + }{ + {"valid version", ibctesting.ConnectionVersion, types.DefaultIBCVersion, true}, + {"invalid version", "not a proto encoded version", types.Version{}, false}, + {"empty string", " ", types.Version{}, false}, + } + + for i, tc := range testCases { + version, err := types.DecodeVersion(tc.version) + + if tc.expPass { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + require.Equal(t, tc.expVersion, version) + } else { + require.Error(t, err, "invalid test case %d passed: %s", i, tc.name) + } } } func TestFindSupportedVersion(t *testing.T) { testCases := []struct { name string - version string - supportedVersions []string - expVersion string + version types.Version + supportedVersions []types.Version + expVersion types.Version expFound bool }{ {"valid supported version", types.DefaultIBCVersion, types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, - {"empty (invalid) version", "", types.GetCompatibleVersions(), "", false}, - {"empty supported versions", types.DefaultIBCVersion, []string{}, "", false}, - {"desired version is last", types.DefaultIBCVersion, []string{"(validversion,[])", "(2,[feature])", "(3,[])", types.DefaultIBCVersion}, types.DefaultIBCVersion, true}, - {"desired version identifier with different feature set", "(1,[features])", types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, - {"version not supported", "(2,[DAG])", types.GetCompatibleVersions(), "", false}, + {"empty (invalid) version", types.Version{}, types.GetCompatibleVersions(), types.Version{}, false}, + {"empty supported versions", types.DefaultIBCVersion, []types.Version{}, types.Version{}, false}, + {"desired version is last", types.DefaultIBCVersion, []types.Version{types.NewVersion("1.1", nil), types.NewVersion("2", []string{"ORDER_UNORDERED"}), types.NewVersion("3", nil), types.DefaultIBCVersion}, types.DefaultIBCVersion, true}, + {"desired version identifier with different feature set", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_DAG"}), types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, + {"version not supported", types.NewVersion("2", []string{"ORDER_DAG"}), types.GetCompatibleVersions(), types.Version{}, false}, } for i, tc := range testCases { version, found := types.FindSupportedVersion(tc.version, tc.supportedVersions) - require.Equal(t, tc.expVersion, version, "test case %d: %s", i, tc.name) + require.Equal(t, tc.expVersion.GetIdentifier(), version.GetIdentifier(), "test case %d: %s", i, tc.name) require.Equal(t, tc.expFound, found, "test case %d: %s", i, tc.name) } } @@ -68,62 +86,77 @@ func TestFindSupportedVersion(t *testing.T) { func TestPickVersion(t *testing.T) { testCases := []struct { name string - counterpartyVersions []string - expVer string + counterpartyVersions []types.Version + expVer types.Version expPass bool }{ {"valid default ibc version", types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, - {"valid version in counterparty versions", []string{"(version1,[])", "(2.0.0,[DAG,ZK])", types.DefaultIBCVersion}, types.DefaultIBCVersion, true}, - {"valid identifier match but empty feature set", []string{"(1,[DAG,ORDERED-ZK,UNORDERED-zk])"}, "(1,[])", true}, - {"empty counterparty versions", []string{}, "", false}, - {"non-matching counterparty versions", []string{"(2.0.0,[])"}, "", false}, + {"valid version in counterparty versions", []types.Version{types.NewVersion("version1", nil), types.NewVersion("2.0.0", []string{"ORDER_UNORDERED-ZK"}), types.DefaultIBCVersion}, types.DefaultIBCVersion, true}, + {"valid identifier match but empty feature set not allowed", []types.Version{types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"DAG", "ORDERED-ZK", "UNORDERED-zk]"})}, types.NewVersion(types.DefaultIBCVersionIdentifier, nil), false}, + {"empty counterparty versions", []types.Version{}, types.Version{}, false}, + {"non-matching counterparty versions", []types.Version{types.NewVersion("2.0.0", nil)}, types.Version{}, false}, } for i, tc := range testCases { - version, err := types.PickVersion(tc.counterpartyVersions) + encodedCounterpartyVersions, err := types.EncodeVersions(tc.counterpartyVersions) + require.NoError(t, err) + + encodedVersion, err := types.PickVersion(encodedCounterpartyVersions) if tc.expPass { require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + + version, err := types.DecodeVersion(encodedVersion) + require.NoError(t, err) require.Equal(t, tc.expVer, version, "valid test case %d falied: %s", i, tc.name) } else { require.Error(t, err, "invalid test case %d passed: %s", i, tc.name) - require.Equal(t, "", version, "invalid test case %d passed: %s", i, tc.name) + require.Equal(t, "", encodedVersion, "invalid test case %d passed: %s", i, tc.name) } } } -func TestVerifyProposedFeatureSet(t *testing.T) { +func TestVerifyProposedVersion(t *testing.T) { testCases := []struct { name string - proposedVersion string - supportedVersion string + proposedVersion types.Version + supportedVersion types.Version expPass bool }{ - {"entire feature set supported", types.DefaultIBCVersion, types.CreateVersionString("1", []string{"ORDER_ORDERED", "ORDER_UNORDERED", "ORDER_DAG"}), true}, - {"empty feature sets", types.CreateVersionString("1", []string{}), types.DefaultIBCVersion, true}, - {"one feature missing", types.DefaultIBCVersion, types.CreateVersionString("1", []string{"ORDER_UNORDERED", "ORDER_DAG"}), false}, - {"both features missing", types.DefaultIBCVersion, types.CreateVersionString("1", []string{"ORDER_DAG"}), false}, + {"entire feature set supported", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_ORDERED", "ORDER_UNORDERED", "ORDER_DAG"}), true}, + {"empty feature sets not supported", types.NewVersion("1", []string{}), types.DefaultIBCVersion, false}, + {"one feature missing", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_UNORDERED", "ORDER_DAG"}), false}, + {"both features missing", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_DAG"}), false}, + {"identifiers do not match", types.NewVersion("2", []string{"ORDER_UNORDERED", "ORDER_ORDERED"}), types.DefaultIBCVersion, false}, } for i, tc := range testCases { - supported := types.VerifyProposedFeatureSet(tc.proposedVersion, tc.supportedVersion) + err := tc.supportedVersion.VerifyProposedVersion(tc.proposedVersion) - require.Equal(t, tc.expPass, supported, "test case %d: %s", i, tc.name) + if tc.expPass { + require.NoError(t, err, "test case %d: %s", i, tc.name) + } else { + require.Error(t, err, "test case %d: %s", i, tc.name) + } } } func TestVerifySupportedFeature(t *testing.T) { + nilFeatures, err := types.NewVersion(types.DefaultIBCVersionIdentifier, nil).Encode() + require.NoError(t, err) + testCases := []struct { name string version string feature string expPass bool }{ - {"check ORDERED supported", types.DefaultIBCVersion, "ORDER_ORDERED", true}, - {"check UNORDERED supported", types.DefaultIBCVersion, "ORDER_UNORDERED", true}, - {"check DAG unsupported", types.DefaultIBCVersion, "ORDER_DAG", false}, - {"check empty feature set returns false", types.CreateVersionString("1", []string{}), "ORDER_ORDERED", false}, + {"check ORDERED supported", ibctesting.ConnectionVersion, "ORDER_ORDERED", true}, + {"check UNORDERED supported", ibctesting.ConnectionVersion, "ORDER_UNORDERED", true}, + {"check DAG unsupported", ibctesting.ConnectionVersion, "ORDER_DAG", false}, + {"check empty feature set returns false", nilFeatures, "ORDER_ORDERED", false}, + {"failed to unmarshal version", "not an encoded version", "ORDER_ORDERED", false}, } for i, tc := range testCases { diff --git a/x/ibc/04-channel/client/cli/tx.go b/x/ibc/04-channel/client/cli/tx.go index 7674343fe2..3ffaa0a3f3 100644 --- a/x/ibc/04-channel/client/cli/tx.go +++ b/x/ibc/04-channel/client/cli/tx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" + ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" connectionutils "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/client/utils" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" ) @@ -54,7 +55,7 @@ func NewChannelOpenInitCmd() *cobra.Command { } cmd.Flags().Bool(FlagOrdered, true, "Pass flag for opening ordered channels") - cmd.Flags().String(FlagIBCVersion, types.DefaultChannelVersion, "supported IBC version") + cmd.Flags().String(FlagIBCVersion, ibctransfertypes.Version, "IBC application version") flags.AddTxFlagsToCmd(cmd) return cmd @@ -107,7 +108,7 @@ func NewChannelOpenTryCmd() *cobra.Command { } cmd.Flags().Bool(FlagOrdered, true, "Pass flag for opening ordered channels") - cmd.Flags().String(FlagIBCVersion, types.DefaultChannelVersion, "supported IBC version") + cmd.Flags().String(FlagIBCVersion, ibctransfertypes.Version, "IBC application version") flags.AddTxFlagsToCmd(cmd) return cmd @@ -152,8 +153,7 @@ func NewChannelOpenAckCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } - - cmd.Flags().String(FlagIBCVersion, types.DefaultChannelVersion, "supported IBC version") + cmd.Flags().String(FlagIBCVersion, ibctransfertypes.Version, "IBC application version") flags.AddTxFlagsToCmd(cmd) return cmd diff --git a/x/ibc/04-channel/keeper/handshake_test.go b/x/ibc/04-channel/keeper/handshake_test.go index 9f9c5891fd..7bac18edfe 100644 --- a/x/ibc/04-channel/keeper/handshake_test.go +++ b/x/ibc/04-channel/keeper/handshake_test.go @@ -66,7 +66,11 @@ func (suite *KeeperTestSuite) TestChanOpenInit() { // modify connA versions conn := suite.chainA.GetConnection(connA) - conn.Versions = append(conn.Versions, connectiontypes.CreateVersionString("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"})) + + version, err := connectiontypes.NewVersion("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"}).Encode() + suite.Require().NoError(err) + conn.Versions = append(conn.Versions, version) + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection( suite.chainA.GetContext(), connA.ID, conn, @@ -80,7 +84,11 @@ func (suite *KeeperTestSuite) TestChanOpenInit() { // modify connA versions to only support UNORDERED channels conn := suite.chainA.GetConnection(connA) - conn.Versions = []string{connectiontypes.CreateVersionString("1", []string{"ORDER_UNORDERED"})} + + version, err := connectiontypes.NewVersion("1", []string{"ORDER_UNORDERED"}).Encode() + suite.Require().NoError(err) + conn.Versions = []string{version} + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection( suite.chainA.GetContext(), connA.ID, conn, @@ -207,7 +215,11 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { // modify connB versions conn := suite.chainB.GetConnection(connB) - conn.Versions = append(conn.Versions, connectiontypes.CreateVersionString("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"})) + + version, err := connectiontypes.NewVersion("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"}).Encode() + suite.Require().NoError(err) + conn.Versions = append(conn.Versions, version) + suite.chainB.App.IBCKeeper.ConnectionKeeper.SetConnection( suite.chainB.GetContext(), connB.ID, conn, @@ -221,7 +233,11 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { // modify connA versions to only support UNORDERED channels conn := suite.chainA.GetConnection(connA) - conn.Versions = []string{connectiontypes.CreateVersionString("1", []string{"ORDER_UNORDERED"})} + + version, err := connectiontypes.NewVersion("1", []string{"ORDER_UNORDERED"}).Encode() + suite.Require().NoError(err) + conn.Versions = []string{version} + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection( suite.chainA.GetContext(), connA.ID, conn, @@ -364,6 +380,7 @@ func (suite *KeeperTestSuite) TestChanOpenAck() { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.SetupTest() // reset heightDiff = 0 // must be explicitly changed + tc.malleate() channelA := connA.FirstOrNextTestChannel() diff --git a/x/ibc/04-channel/types/channel.go b/x/ibc/04-channel/types/channel.go index dd414b8fb1..b4b7228b0b 100644 --- a/x/ibc/04-channel/types/channel.go +++ b/x/ibc/04-channel/types/channel.go @@ -13,9 +13,6 @@ var ( _ exported.CounterpartyI = (*Counterparty)(nil) ) -// DefaultChannelVersion defines the default channel version used during handshake. -const DefaultChannelVersion = "1.0.0" - // NewChannel creates a new Channel instance func NewChannel( state State, ordering Order, counterparty Counterparty, diff --git a/x/ibc/24-host/validate.go b/x/ibc/24-host/validate.go index f01b06b279..0cf552eb7d 100644 --- a/x/ibc/24-host/validate.go +++ b/x/ibc/24-host/validate.go @@ -14,15 +14,6 @@ import ( // - `[`, `]`, `<`, `>` var IsValidID = regexp.MustCompile(`^[a-zA-Z0-9\.\_\+\-\#\[\]\<\>]+$`).MatchString -// IsValidConnectionVersion defines the regular expression to check if the -// string is in the form of a tuple consisting of a string identifier and -// a set of features. The entire version tuple must be enclosed in parentheses. -// The version identifier must not contain any commas. The set of features -// must be enclosed in brackets and separated by commas. -// -// valid connection version = ([version_identifier], [feature_0, feature_1, etc]) -var IsValidConnectionVersion = regexp.MustCompile(`^\([^,]+\,\[([^,]+(\,[^,]+)*)?\]\)$`).MatchString - // ICS 024 Identifier and Path Validation Implementation // // This file defines ValidateFn to validate identifier and path strings @@ -83,24 +74,6 @@ func PortIdentifierValidator(id string) error { return defaultIdentifierValidator(id, 2, 20) } -// ConnectionVersionValidator is the default validator function for Connection -// versions. A valid version must be in semantic versioning form and contain -// only non-negative integers. -func ConnectionVersionValidator(version string) error { - if strings.TrimSpace(version) == "" { - return sdkerrors.Wrap(ErrInvalidVersion, "version cannot be blank") - } - - if !IsValidConnectionVersion(version) { - return sdkerrors.Wrapf( - ErrInvalidVersion, - "version '%s' must be in '(version_identifier,[feature_0, feature_1])' with no extra spacing", version, - ) - } - - return nil -} - // NewPathValidator takes in a Identifier Validator function and returns // a Path Validator function which requires path only has valid identifiers // alphanumeric character strings, and "/" separators diff --git a/x/ibc/24-host/validate_test.go b/x/ibc/24-host/validate_test.go index 7215d3a522..417447fe03 100644 --- a/x/ibc/24-host/validate_test.go +++ b/x/ibc/24-host/validate_test.go @@ -111,31 +111,3 @@ func TestCustomPathValidator(t *testing.T) { } } } - -func TestConnectionVersionValidator(t *testing.T) { - testCases := []testCase{ - {"valid connection version", "(my-test-version 1.0,[feature0, feature1])", true}, - {"valid random character version, no commas", "(a!@!#$%^&34,[)(*&^),....,feature_2])", true}, - {"valid: empty features", "(identifier,[])", true}, - {"invalid: empty features with spacing", "(identifier, [ ])", false}, - {"missing identifier", "( , [feature_0])", false}, - {"no features bracket", "(identifier, feature_0, feature_1)", false}, - {"no tuple parentheses", "identifier, [feature$%#]", false}, - {"string with only spaces", " ", false}, - {"empty string", "", false}, - {"no comma", "(idenitifer [features])", false}, - {"invalid comma usage in features", "(identifier, [feature_0,,feature_1])", false}, - {"empty features with comma", "(identifier, [ , ])", false}, - } - - for _, tc := range testCases { - - err := ConnectionVersionValidator(tc.id) - - if tc.expPass { - require.NoError(t, err, tc.msg) - } else { - require.Error(t, err, tc.msg) - } - } -} diff --git a/x/ibc/genesis_test.go b/x/ibc/genesis_test.go index 40ce6ba206..5a3f321429 100644 --- a/x/ibc/genesis_test.go +++ b/x/ibc/genesis_test.go @@ -11,6 +11,7 @@ import ( localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" "github.com/cosmos/cosmos-sdk/x/ibc/types" ) @@ -47,7 +48,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { ), ConnectionGenesis: connectiontypes.NewGenesisState( []connectiontypes.ConnectionEnd{ - connectiontypes.NewConnectionEnd(connectiontypes.INIT, connectionID, clientID, connectiontypes.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{connectiontypes.DefaultIBCVersion}), + connectiontypes.NewConnectionEnd(connectiontypes.INIT, connectionID, clientID, connectiontypes.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{ibctesting.ConnectionVersion}), }, []connectiontypes.ConnectionPaths{ connectiontypes.NewConnectionPaths(clientID, []string{host.ConnectionPath(connectionID)}), diff --git a/x/ibc/spec/01_concepts.md b/x/ibc/spec/01_concepts.md index 486075a9b7..1645a6f1f6 100644 --- a/x/ibc/spec/01_concepts.md +++ b/x/ibc/spec/01_concepts.md @@ -45,3 +45,35 @@ identifier, but differing feature sets. This will result in undefined behavior with regards to version selection in `ConnOpenTry`. Each version in a set of versions should have a unique version identifier. ::: + +### Channel Version Negotation + +During the channel handshake procedure a version must be agreed upon between +the two parties. The selection process is largely left to the callers and +the verification of valid versioning must be handled by application developers +in the channel handshake callbacks. + +During `ChanOpenInit`, a version string is passed in and set in party A's +channel state. + +During `ChanOpenTry`, a version string for party A and for party B are passed +in. The party A version string must match the version string used in +`ChanOpenInit` otherwise channel state verification will fail. The party B +version string could be anything (even different than the proposed one by +party A). However, the proposed version by party B is expected to be fully +supported by party A. + +During the `ChanOpenAck` callback, the application module is expected to verify +the version proposed by party B using the `MsgChanOpenAck` `CounterpartyVersion` +field. The application module should throw an error if the version string is +not valid. + +Application modules may implement their own versioning system, such as semantic +versioning, or they may lean upon the versioning system used for in connection +version negotiation. To use the connection version semantics the application +would simply pass the proto encoded version into each of the handshake calls +and decode the version string into a `Version` instance to do version verification +in the handshake callbacks. + +Implementations which do not feel they would benefit from versioning can do +basic string matching using a single compatible version. diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index 63b1bdf06d..27725d59e8 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -21,6 +21,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" @@ -38,7 +39,7 @@ const ( UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 MaxClockDrift time.Duration = time.Second * 10 - ChannelVersion = "ics20-1" + ChannelVersion = ibctransfertypes.Version InvalidID = "IDisInvalid" ConnectionIDPrefix = "connectionid" @@ -50,7 +51,7 @@ var ( DefaultTrustLevel tmmath.Fraction = lite.DefaultTrustLevel TestHash = []byte("TESTING HASH") - ConnectionVersion = connectiontypes.DefaultIBCVersion + ConnectionVersion = connectiontypes.GetCompatibleEncodedVersions()[0] ) // TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI