From e9b5fc97169099cebe0fd22ecdf1129343d95e36 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Fri, 24 Apr 2020 11:52:07 -0400 Subject: [PATCH] ibc/03-connection: import export GenesisState (#6067) * ibc/03-connection: import export GenesisState * add connection paths * add genesis to ibc module root package * genesis tests * iterator and fixes * changelog * lint fixes * godoc * revert validation --- CHANGELOG.md | 1 + x/ibc/03-connection/alias.go | 5 ++ x/ibc/03-connection/client/utils/utils.go | 4 +- x/ibc/03-connection/exported/exported.go | 1 + x/ibc/03-connection/genesis.go | 24 ++++++ x/ibc/03-connection/keeper/handshake.go | 10 +-- x/ibc/03-connection/keeper/handshake_test.go | 2 +- x/ibc/03-connection/keeper/keeper.go | 34 +++++--- x/ibc/03-connection/keeper/keeper_test.go | 49 ++++++++--- x/ibc/03-connection/keeper/querier.go | 2 +- x/ibc/03-connection/keeper/verify_test.go | 8 +- x/ibc/03-connection/types/connection.go | 16 +++- x/ibc/03-connection/types/connection_test.go | 26 ++++-- x/ibc/03-connection/types/expected_keepers.go | 1 + x/ibc/03-connection/types/genesis.go | 68 +++++++++++++++ x/ibc/03-connection/types/genesis_test.go | 84 +++++++++++++++++++ x/ibc/03-connection/types/querier.go | 10 +-- .../07-tendermint/types/client_state_test.go | 2 +- x/ibc/09-localhost/types/client_state_test.go | 2 +- x/ibc/genesis.go | 37 ++++++++ x/ibc/genesis_test.go | 70 ++++++++++++++++ x/ibc/module.go | 22 +++-- 22 files changed, 418 insertions(+), 60 deletions(-) create mode 100644 x/ibc/03-connection/genesis.go create mode 100644 x/ibc/03-connection/types/genesis.go create mode 100644 x/ibc/03-connection/types/genesis_test.go create mode 100644 x/ibc/genesis.go create mode 100644 x/ibc/genesis_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b65d8f217..a8651cb65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -223,6 +223,7 @@ functionality that requires an online connection. * (x/auth/ante) [\#6040](https://github.com/cosmos/cosmos-sdk/pull/6040) `AccountKeeper` interface used for `NewAnteHandler` and handler's decorators to add support of using custom `AccountKeeper` implementations. * (simulation) [\#6002](https://github.com/cosmos/cosmos-sdk/pull/6002) Add randomized consensus params into simulation. * (x/staking) [\#6059](https://github.com/cosmos/cosmos-sdk/pull/6059) Updated `HistoricalEntries` parameter default to 100. +* (x/ibc) [\#5948](https://github.com/cosmos/cosmos-sdk/issues/5948) Add `InitGenesis` and `ExportGenesis` functions for `ibc` module. ## [v0.38.3] - 2020-04-09 diff --git a/x/ibc/03-connection/alias.go b/x/ibc/03-connection/alias.go index 7ffb090107..3479242d36 100644 --- a/x/ibc/03-connection/alias.go +++ b/x/ibc/03-connection/alias.go @@ -46,6 +46,9 @@ var ( GetCompatibleVersions = types.GetCompatibleVersions LatestVersion = types.LatestVersion PickVersion = types.PickVersion + NewConnectionPaths = types.NewConnectionPaths + DefaultGenesisState = types.DefaultGenesisState + NewGenesisState = types.NewGenesisState // variable aliases SubModuleCdc = types.SubModuleCdc @@ -69,4 +72,6 @@ type ( ConnectionResponse = types.ConnectionResponse ClientConnectionsResponse = types.ClientConnectionsResponse QueryClientConnectionsParams = types.QueryClientConnectionsParams + GenesisState = types.GenesisState + ConnectionPaths = types.ConnectionPaths ) diff --git a/x/ibc/03-connection/client/utils/utils.go b/x/ibc/03-connection/client/utils/utils.go index 4ede59e5dd..492fbc5259 100644 --- a/x/ibc/03-connection/client/utils/utils.go +++ b/x/ibc/03-connection/client/utils/utils.go @@ -17,7 +17,7 @@ import ( // QueryAllConnections returns all the connections. It _does not_ return // any merkle proof. -func QueryAllConnections(cliCtx context.CLIContext, page, limit int) ([]types.IdentifiedConnectionEnd, int64, error) { +func QueryAllConnections(cliCtx context.CLIContext, page, limit int) ([]types.ConnectionEnd, int64, error) { params := types.NewQueryAllConnectionsParams(page, limit) bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { @@ -30,7 +30,7 @@ func QueryAllConnections(cliCtx context.CLIContext, page, limit int) ([]types.Id return nil, 0, err } - var connections []types.IdentifiedConnectionEnd + var connections []types.ConnectionEnd err = cliCtx.Codec.UnmarshalJSON(res, &connections) if err != nil { return nil, 0, fmt.Errorf("failed to unmarshal connections: %w", err) diff --git a/x/ibc/03-connection/exported/exported.go b/x/ibc/03-connection/exported/exported.go index 8f1591f2f6..afd7f8f0c0 100644 --- a/x/ibc/03-connection/exported/exported.go +++ b/x/ibc/03-connection/exported/exported.go @@ -9,6 +9,7 @@ import ( // ConnectionI describes the required methods for a connection. type ConnectionI interface { GetState() State + GetID() string GetClientID() string GetCounterparty() CounterpartyI GetVersions() []string diff --git a/x/ibc/03-connection/genesis.go b/x/ibc/03-connection/genesis.go new file mode 100644 index 0000000000..4ce96924c7 --- /dev/null +++ b/x/ibc/03-connection/genesis.go @@ -0,0 +1,24 @@ +package connection + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the ibc connection submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) { + for _, connection := range gs.Connections { + k.SetConnection(ctx, connection.ID, connection) + } + for _, connPaths := range gs.ClientConnectionPaths { + k.SetClientConnectionPaths(ctx, connPaths.ClientID, connPaths.Paths) + } +} + +// ExportGenesis returns the ibc connection submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + return GenesisState{ + Connections: k.GetAllConnections(ctx), + ClientConnectionPaths: k.GetAllClientConnectionPaths(ctx), + } +} diff --git a/x/ibc/03-connection/keeper/handshake.go b/x/ibc/03-connection/keeper/handshake.go index e7cc6b796d..4cf0ff2a28 100644 --- a/x/ibc/03-connection/keeper/handshake.go +++ b/x/ibc/03-connection/keeper/handshake.go @@ -28,7 +28,7 @@ func (k Keeper) ConnOpenInit( } // connection defines chain A's ConnectionEnd - connection := types.NewConnectionEnd(exported.INIT, clientID, counterparty, types.GetCompatibleVersions()) + connection := types.NewConnectionEnd(exported.INIT, connectionID, clientID, counterparty, types.GetCompatibleVersions()) k.SetConnection(ctx, connectionID, connection) if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil { @@ -69,14 +69,14 @@ func (k Keeper) ConnOpenTry( // NOTE: chain A's counterparty is chain B (i.e where this code is executed) prefix := k.GetCommitmentPrefix() expectedCounterparty := types.NewCounterparty(clientID, connectionID, prefix) - expectedConnection := types.NewConnectionEnd(exported.INIT, counterparty.ClientID, expectedCounterparty, counterpartyVersions) + expectedConnection := types.NewConnectionEnd(exported.INIT, counterparty.ConnectionID, counterparty.ClientID, expectedCounterparty, counterpartyVersions) // chain B picks a version from Chain A's available versions that is compatible // with the supported IBC versions version := types.PickVersion(counterpartyVersions, types.GetCompatibleVersions()) // connection defines chain B's ConnectionEnd - connection := types.NewConnectionEnd(exported.UNINITIALIZED, clientID, counterparty, []string{version}) + connection := types.NewConnectionEnd(exported.UNINITIALIZED, connectionID, clientID, counterparty, []string{version}) // Check that ChainA committed expectedConnectionEnd to its state if err := k.VerifyConnectionState( @@ -165,7 +165,7 @@ func (k Keeper) ConnOpenAck( prefix := k.GetCommitmentPrefix() expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, prefix) - expectedConnection := types.NewConnectionEnd(exported.TRYOPEN, connection.Counterparty.ClientID, expectedCounterparty, []string{version}) + expectedConnection := types.NewConnectionEnd(exported.TRYOPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, []string{version}) // Ensure that ChainB stored expected connectionEnd in its state during ConnOpenTry if err := k.VerifyConnectionState( @@ -216,7 +216,7 @@ func (k Keeper) ConnOpenConfirm( prefix := k.GetCommitmentPrefix() expectedCounterparty := types.NewCounterparty(connection.ClientID, connectionID, prefix) - expectedConnection := types.NewConnectionEnd(exported.OPEN, connection.Counterparty.ClientID, expectedCounterparty, connection.Versions) + expectedConnection := types.NewConnectionEnd(exported.OPEN, connection.Counterparty.ConnectionID, connection.Counterparty.ClientID, expectedCounterparty, connection.Versions) // Check that connection on ChainA is open if err := k.VerifyConnectionState( diff --git a/x/ibc/03-connection/keeper/handshake_test.go b/x/ibc/03-connection/keeper/handshake_test.go index 30bef2e58d..6c78915e84 100644 --- a/x/ibc/03-connection/keeper/handshake_test.go +++ b/x/ibc/03-connection/keeper/handshake_test.go @@ -25,7 +25,7 @@ func (suite *KeeperTestSuite) TestConnOpenInit() { {"couldn't add connection to client", func() {}, false}, } - counterparty := connection.NewCounterparty(testClientIDA, testConnectionIDB, suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) + counterparty := connection.NewCounterparty(testClientIDB, testConnectionIDB, suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) for i, tc := range testCases { tc := tc diff --git a/x/ibc/03-connection/keeper/keeper.go b/x/ibc/03-connection/keeper/keeper.go index 205019c9f4..446a74a914 100644 --- a/x/ibc/03-connection/keeper/keeper.go +++ b/x/ibc/03-connection/keeper/keeper.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported" @@ -101,10 +102,29 @@ func (k Keeper) SetClientConnectionPaths(ctx sdk.Context, clientID string, paths store.Set(ibctypes.KeyClientConnections(clientID), bz) } +// GetAllClientConnectionPaths returns all stored clients connection id paths. It +// will ignore the clients that haven't initialized a connection handshake since +// no paths are stored. +func (k Keeper) GetAllClientConnectionPaths(ctx sdk.Context) []types.ConnectionPaths { + var allConnectionPaths []types.ConnectionPaths + k.clientKeeper.IterateClients(ctx, func(cs clientexported.ClientState) bool { + paths, found := k.GetClientConnectionPaths(ctx, cs.GetID()) + if !found { + // continue when connection handshake is not initialized + return false + } + connPaths := types.NewConnectionPaths(cs.GetID(), paths) + allConnectionPaths = append(allConnectionPaths, connPaths) + return false + }) + + return allConnectionPaths +} + // IterateConnections provides an iterator over all ConnectionEnd objects. // For each ConnectionEnd, cb will be called. If the cb returns true, the // iterator will close and stop. -func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.IdentifiedConnectionEnd) bool) { +func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.ConnectionEnd) bool) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ibctypes.KeyConnectionPrefix) @@ -112,22 +132,16 @@ func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.IdentifiedConn for ; iterator.Valid(); iterator.Next() { var connection types.ConnectionEnd k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &connection) - identifier := string(iterator.Key()[len(ibctypes.KeyConnectionPrefix)+1:]) - conn := types.IdentifiedConnectionEnd{ - Connection: connection, - Identifier: identifier, - } - - if cb(conn) { + if cb(connection) { break } } } // GetAllConnections returns all stored ConnectionEnd objects. -func (k Keeper) GetAllConnections(ctx sdk.Context) (connections []types.IdentifiedConnectionEnd) { - k.IterateConnections(ctx, func(connection types.IdentifiedConnectionEnd) bool { +func (k Keeper) GetAllConnections(ctx sdk.Context) (connections []types.ConnectionEnd) { + k.IterateConnections(ctx, func(connection types.ConnectionEnd) bool { connections = append(connections, connection) return false }) diff --git a/x/ibc/03-connection/keeper/keeper_test.go b/x/ibc/03-connection/keeper/keeper_test.go index a2d1be518e..39bd2724fc 100644 --- a/x/ibc/03-connection/keeper/keeper_test.go +++ b/x/ibc/03-connection/keeper/keeper_test.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" @@ -36,7 +37,7 @@ const ( trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 - maxClockDrift time.Duration = time.Second * 10 + maxClockDrift time.Duration = time.Second * 10 nextTimestamp = 10 // increment used for the next header's timestamp ) @@ -88,7 +89,7 @@ func (suite *KeeperTestSuite) TestSetAndGetConnection() { suite.Require().False(existed) counterparty := types.NewCounterparty(testClientIDA, testConnectionIDA, suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) - expConn := types.NewConnectionEnd(exported.INIT, testClientIDB, counterparty, types.GetCompatibleVersions()) + expConn := types.NewConnectionEnd(exported.INIT, testConnectionIDB, testClientIDB, counterparty, types.GetCompatibleVersions()) suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionIDA, expConn) conn, existed := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetConnection(suite.chainA.GetContext(), testConnectionIDA) suite.Require().True(existed) @@ -111,25 +112,46 @@ func (suite KeeperTestSuite) TestGetAllConnections() { counterparty2 := types.NewCounterparty(testClientIDB, testConnectionIDB, suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) counterparty3 := types.NewCounterparty(testClientID3, testConnectionID3, suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) - conn1 := types.NewConnectionEnd(exported.INIT, testClientIDA, counterparty3, types.GetCompatibleVersions()) - conn2 := types.NewConnectionEnd(exported.INIT, testClientIDB, counterparty1, types.GetCompatibleVersions()) - conn3 := types.NewConnectionEnd(exported.UNINITIALIZED, testClientID3, counterparty2, types.GetCompatibleVersions()) + conn1 := types.NewConnectionEnd(exported.INIT, testConnectionIDA, testClientIDA, counterparty3, types.GetCompatibleVersions()) + conn2 := types.NewConnectionEnd(exported.INIT, testConnectionIDB, testClientIDB, counterparty1, types.GetCompatibleVersions()) + conn3 := types.NewConnectionEnd(exported.UNINITIALIZED, testConnectionID3, testClientID3, counterparty2, types.GetCompatibleVersions()) - expConnections := []types.IdentifiedConnectionEnd{ - {Connection: conn1, Identifier: testConnectionIDA}, - {Connection: conn2, Identifier: testConnectionIDB}, - {Connection: conn3, Identifier: testConnectionID3}, + expConnections := []types.ConnectionEnd{conn1, conn2, conn3} + + for i := range expConnections { + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), expConnections[i].ID, expConnections[i]) } - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionIDA, conn1) - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionIDB, conn2) - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionID3, conn3) - connections := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetAllConnections(suite.chainA.GetContext()) suite.Require().Len(connections, len(expConnections)) suite.Require().ElementsMatch(expConnections, connections) } +func (suite KeeperTestSuite) TestGetAllClientConnectionPaths() { + clients := []clientexported.ClientState{ + ibctmtypes.NewClientState(testClientIDA, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}), + ibctmtypes.NewClientState(testClientIDB, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}), + ibctmtypes.NewClientState(testClientID3, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}), + } + + for i := range clients { + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientState(suite.chainA.GetContext(), clients[i]) + } + + expPaths := []types.ConnectionPaths{ + types.NewConnectionPaths(testClientIDA, []string{ibctypes.ConnectionPath(testConnectionIDA)}), + types.NewConnectionPaths(testClientIDB, []string{ibctypes.ConnectionPath(testConnectionIDB), ibctypes.ConnectionPath(testConnectionID3)}), + } + + for i := range expPaths { + suite.chainA.App.IBCKeeper.ConnectionKeeper.SetClientConnectionPaths(suite.chainA.GetContext(), expPaths[i].ClientID, expPaths[i].Paths) + } + + connPaths := suite.chainA.App.IBCKeeper.ConnectionKeeper.GetAllClientConnectionPaths(suite.chainA.GetContext()) + suite.Require().Len(connPaths, 2) + suite.Require().Equal(connPaths, expPaths) +} + // TestGetTimestampAtHeight verifies if the clients on each chain return the correct timestamp // for the other chain. func (suite *KeeperTestSuite) TestGetTimestampAtHeight() { @@ -338,6 +360,7 @@ func (chain *TestChain) createConnection( counterparty := types.NewCounterparty(counterpartyClientID, counterpartyConnID, chain.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix()) connection := types.ConnectionEnd{ State: state, + ID: connID, ClientID: clientID, Counterparty: counterparty, Versions: types.GetCompatibleVersions(), diff --git a/x/ibc/03-connection/keeper/querier.go b/x/ibc/03-connection/keeper/querier.go index 1eebee214b..8225eccaa8 100644 --- a/x/ibc/03-connection/keeper/querier.go +++ b/x/ibc/03-connection/keeper/querier.go @@ -22,7 +22,7 @@ func QuerierConnections(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byt start, end := client.Paginate(len(connections), params.Page, params.Limit, 100) if start < 0 || end < 0 { - connections = []types.IdentifiedConnectionEnd{} + connections = []types.ConnectionEnd{} } else { connections = connections[start:end] } diff --git a/x/ibc/03-connection/keeper/verify_test.go b/x/ibc/03-connection/keeper/verify_test.go index 74ee81065d..78f8e1e77f 100644 --- a/x/ibc/03-connection/keeper/verify_test.go +++ b/x/ibc/03-connection/keeper/verify_test.go @@ -27,7 +27,7 @@ func (suite *KeeperTestSuite) TestVerifyClientConsensusState() { suite.chainA.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix(), ) connection1 := types.NewConnectionEnd( - exported.UNINITIALIZED, testClientIDB, counterparty, + exported.UNINITIALIZED, testConnectionIDB, testClientIDB, counterparty, types.GetCompatibleVersions(), ) @@ -135,8 +135,8 @@ func (suite *KeeperTestSuite) TestVerifyConnectionState() { } // Create B's connection to A - counterparty := types.NewCounterparty(testClientIDB, testConnectionIDA, commitmenttypes.NewMerklePrefix([]byte("ibc"))) - connection := types.NewConnectionEnd(exported.UNINITIALIZED, testClientIDA, counterparty, []string{"1.0.0"}) + counterparty := types.NewCounterparty(testClientIDB, testConnectionIDB, commitmenttypes.NewMerklePrefix([]byte("ibc"))) + connection := types.NewConnectionEnd(exported.UNINITIALIZED, testConnectionIDA, testClientIDA, counterparty, []string{"1.0.0"}) // Ensure chain B can verify connection exists in chain A err := suite.chainB.App.IBCKeeper.ConnectionKeeper.VerifyConnectionState( suite.chainB.GetContext(), connection, proofHeight+1, proof, testConnectionIDA, expectedConnection, @@ -161,7 +161,7 @@ func (suite *KeeperTestSuite) TestVerifyChannelState() { ) connection := types.NewConnectionEnd( - exported.UNINITIALIZED, testClientIDA, counterparty, + exported.UNINITIALIZED, testConnectionIDA, testClientIDA, counterparty, types.GetCompatibleVersions(), ) diff --git a/x/ibc/03-connection/types/connection.go b/x/ibc/03-connection/types/connection.go index 27b5d0d902..0c9adbaa13 100644 --- a/x/ibc/03-connection/types/connection.go +++ b/x/ibc/03-connection/types/connection.go @@ -20,6 +20,7 @@ var _ exported.ConnectionI = ConnectionEnd{} // between two chains. type ConnectionEnd struct { State exported.State `json:"state" yaml:"state"` + ID string `json:"id" yaml:"id"` ClientID string `json:"client_id" yaml:"client_id"` // Counterparty chain associated with this connection. @@ -30,9 +31,10 @@ type ConnectionEnd struct { } // NewConnectionEnd creates a new ConnectionEnd instance. -func NewConnectionEnd(state exported.State, clientID string, counterparty Counterparty, versions []string) ConnectionEnd { +func NewConnectionEnd(state exported.State, connectionID, clientID string, counterparty Counterparty, versions []string) ConnectionEnd { return ConnectionEnd{ State: state, + ID: connectionID, ClientID: clientID, Counterparty: counterparty, Versions: versions, @@ -44,6 +46,11 @@ func (c ConnectionEnd) GetState() exported.State { return c.State } +// GetID implements the Connection interface +func (c ConnectionEnd) GetID() string { + return c.ID +} + // GetClientID implements the Connection interface func (c ConnectionEnd) GetClientID() string { return c.ClientID @@ -59,8 +66,13 @@ func (c ConnectionEnd) GetVersions() []string { return c.Versions } -// ValidateBasic implements the Connection interface +// ValidateBasic implements the Connection interface. +// NOTE: the protocol supports that the connection and client IDs match the +// counterparty's. func (c ConnectionEnd) ValidateBasic() error { + if err := host.DefaultConnectionIdentifierValidator(c.ID); err != nil { + return sdkerrors.Wrapf(err, "invalid connection ID: %s", c.ID) + } if err := host.DefaultClientIdentifierValidator(c.ClientID); err != nil { return sdkerrors.Wrapf(err, "invalid client ID: %s", c.ClientID) } diff --git a/x/ibc/03-connection/types/connection_test.go b/x/ibc/03-connection/types/connection_test.go index 92605a7ea6..aae3536ae2 100644 --- a/x/ibc/03-connection/types/connection_test.go +++ b/x/ibc/03-connection/types/connection_test.go @@ -9,6 +9,13 @@ import ( commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" ) +var ( + connectionID = "connectionidone" + clientID = "clientidone" + connectionID2 = "connectionidtwo" + clientID2 = "clientidtwo" +) + func TestConnectionValidateBasic(t *testing.T) { testCases := []struct { name string @@ -17,27 +24,32 @@ func TestConnectionValidateBasic(t *testing.T) { }{ { "valid connection", - ConnectionEnd{exported.INIT, "clientidone", Counterparty{"clientidtwo", "connectionidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + ConnectionEnd{exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, true, }, + { + "invalid connection id", + ConnectionEnd{exported.INIT, "connectionIDONE", clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + false, + }, { "invalid client id", - ConnectionEnd{exported.INIT, "ClientIDTwo", Counterparty{"clientidtwo", "connectionidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + ConnectionEnd{exported.INIT, connectionID, "ClientIDTwo", Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, false, }, { "empty versions", - ConnectionEnd{exported.INIT, "clientidone", Counterparty{"clientidtwo", "connectionidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, nil}, + ConnectionEnd{exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, nil}, false, }, { "invalid version", - ConnectionEnd{exported.INIT, "clientidone", Counterparty{"clientidtwo", "connectionidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{""}}, + ConnectionEnd{exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{""}}, false, }, { "invalid counterparty", - ConnectionEnd{exported.INIT, "clientidone", Counterparty{"clientidtwo", "connectionidone", nil}, []string{"1.0.0"}}, + ConnectionEnd{exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, nil}, []string{"1.0.0"}}, false, }, } @@ -60,10 +72,10 @@ func TestCounterpartyValidateBasic(t *testing.T) { counterparty Counterparty expPass bool }{ - {"valid counterparty", Counterparty{"clientidone", "connectionidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, true}, + {"valid counterparty", Counterparty{"clientidone", connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, true}, {"invalid client id", Counterparty{"InvalidClient", "channelidone", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, false}, {"invalid connection id", Counterparty{"clientidone", "InvalidConnection", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, false}, - {"invalid prefix", Counterparty{"clientidone", "connectionidone", nil}, false}, + {"invalid prefix", Counterparty{"clientidone", connectionID2, nil}, false}, } for i, tc := range testCases { diff --git a/x/ibc/03-connection/types/expected_keepers.go b/x/ibc/03-connection/types/expected_keepers.go index c91519e0df..bd08356c9f 100644 --- a/x/ibc/03-connection/types/expected_keepers.go +++ b/x/ibc/03-connection/types/expected_keepers.go @@ -10,4 +10,5 @@ type ClientKeeper interface { GetClientState(ctx sdk.Context, clientID string) (clientexported.ClientState, bool) GetClientConsensusState(ctx sdk.Context, clientID string, height uint64) (clientexported.ConsensusState, bool) GetSelfConsensusState(ctx sdk.Context, height uint64) (clientexported.ConsensusState, bool) + IterateClients(ctx sdk.Context, cb func(clientexported.ClientState) bool) } diff --git a/x/ibc/03-connection/types/genesis.go b/x/ibc/03-connection/types/genesis.go new file mode 100644 index 0000000000..8bc9a615d3 --- /dev/null +++ b/x/ibc/03-connection/types/genesis.go @@ -0,0 +1,68 @@ +package types + +import ( + "fmt" + + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" +) + +// ConnectionPaths define all the connection paths for a given client state. +type ConnectionPaths struct { + ClientID string `json:"client_id" yaml:"client_id"` + Paths []string `json:"paths" yaml:"paths"` +} + +// NewConnectionPaths creates a ConnectionPaths instance. +func NewConnectionPaths(id string, paths []string) ConnectionPaths { + return ConnectionPaths{ + ClientID: id, + Paths: paths, + } +} + +// GenesisState defines the ibc connection submodule's genesis state. +type GenesisState struct { + Connections []ConnectionEnd `json:"connections" yaml:"connections"` + ClientConnectionPaths []ConnectionPaths `json:"client_connection_paths" yaml:"client_connection_paths"` +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + connections []ConnectionEnd, connPaths []ConnectionPaths, +) GenesisState { + return GenesisState{ + Connections: connections, + ClientConnectionPaths: connPaths, + } +} + +// DefaultGenesisState returns the ibc connection submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Connections: []ConnectionEnd{}, + ClientConnectionPaths: []ConnectionPaths{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + for i, conn := range gs.Connections { + if err := conn.ValidateBasic(); err != nil { + return fmt.Errorf("invalid connection %d: %w", i, err) + } + } + + for i, conPaths := range gs.ClientConnectionPaths { + if err := host.DefaultClientIdentifierValidator(conPaths.ClientID); err != nil { + return fmt.Errorf("invalid client connection path %d: %w", i, err) + } + for _, path := range conPaths.Paths { + if err := host.DefaultPathValidator(path); err != nil { + return fmt.Errorf("invalid client connection path %d: %w", i, err) + } + } + } + + return nil +} diff --git a/x/ibc/03-connection/types/genesis_test.go b/x/ibc/03-connection/types/genesis_test.go new file mode 100644 index 0000000000..d1afb7dbbc --- /dev/null +++ b/x/ibc/03-connection/types/genesis_test.go @@ -0,0 +1,84 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +func TestValidateGenesis(t *testing.T) { + + testCases := []struct { + name string + genState GenesisState + expPass bool + }{ + { + name: "default", + genState: DefaultGenesisState(), + expPass: true, + }, + { + name: "valid genesis", + genState: NewGenesisState( + []ConnectionEnd{ + {exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + }, + []ConnectionPaths{ + {clientID, []string{ibctypes.ConnectionPath(connectionID)}}, + }, + ), + expPass: true, + }, + { + name: "invalid connection", + genState: NewGenesisState( + []ConnectionEnd{ + NewConnectionEnd(exported.INIT, connectionID, "CLIENTIDONE", Counterparty{clientID, connectionID, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}), + }, + []ConnectionPaths{ + {clientID, []string{ibctypes.ConnectionPath(connectionID)}}, + }, + ), + expPass: false, + }, + { + name: "invalid client id", + genState: NewGenesisState( + []ConnectionEnd{ + {exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + }, + []ConnectionPaths{ + {"CLIENTIDONE", []string{ibctypes.ConnectionPath(connectionID)}}, + }, + ), + expPass: false, + }, + { + name: "invalid path", + genState: NewGenesisState( + []ConnectionEnd{ + {exported.INIT, connectionID, clientID, Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []string{"1.0.0"}}, + }, + []ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + ), + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expPass { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} diff --git a/x/ibc/03-connection/types/querier.go b/x/ibc/03-connection/types/querier.go index a283f37641..48db30da8f 100644 --- a/x/ibc/03-connection/types/querier.go +++ b/x/ibc/03-connection/types/querier.go @@ -15,16 +15,10 @@ const ( QueryClientConnections = "client_connections" ) -// IdentifiedConnectionEnd defines the union of a connection end & an identifier. -type IdentifiedConnectionEnd struct { - Connection ConnectionEnd `json:"connection_end" yaml:"connection_end"` - Identifier string `json:"identifier" yaml:"identifier"` -} - // ConnectionResponse defines the client query response for a connection which // also includes a proof and the height from which the proof was retrieved. type ConnectionResponse struct { - Connection IdentifiedConnectionEnd `json:"connection" yaml:"connection"` + Connection ConnectionEnd `json:"connection" yaml:"connection"` Proof commitmenttypes.MerkleProof `json:"proof,omitempty" yaml:"proof,omitempty"` ProofPath commitmenttypes.MerklePath `json:"proof_path,omitempty" yaml:"proof_path,omitempty"` ProofHeight uint64 `json:"proof_height,omitempty" yaml:"proof_height,omitempty"` @@ -35,7 +29,7 @@ func NewConnectionResponse( connectionID string, connection ConnectionEnd, proof *merkle.Proof, height int64, ) ConnectionResponse { return ConnectionResponse{ - Connection: IdentifiedConnectionEnd{connection, connectionID}, + Connection: connection, Proof: commitmenttypes.MerkleProof{Proof: proof}, ProofPath: commitmenttypes.NewMerklePath(strings.Split(ibctypes.ConnectionPath(connectionID), "/")), ProofHeight: uint64(height), diff --git a/x/ibc/07-tendermint/types/client_state_test.go b/x/ibc/07-tendermint/types/client_state_test.go index 21247cc674..d9ae9393b2 100644 --- a/x/ibc/07-tendermint/types/client_state_test.go +++ b/x/ibc/07-tendermint/types/client_state_test.go @@ -92,7 +92,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { func (suite *TendermintTestSuite) TestVerifyConnectionState() { counterparty := connection.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc"))) - conn := connection.NewConnectionEnd(connectionexported.OPEN, "clientA", counterparty, []string{"1.0.0"}) + conn := connection.NewConnectionEnd(connectionexported.OPEN, testConnectionID, "clientA", counterparty, []string{"1.0.0"}) testCases := []struct { name string diff --git a/x/ibc/09-localhost/types/client_state_test.go b/x/ibc/09-localhost/types/client_state_test.go index dddc244ee7..a36d0087d9 100644 --- a/x/ibc/09-localhost/types/client_state_test.go +++ b/x/ibc/09-localhost/types/client_state_test.go @@ -58,7 +58,7 @@ func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() { func (suite *LocalhostTestSuite) TestVerifyConnectionState() { counterparty := connection.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc"))) - conn := connection.NewConnectionEnd(connectionexported.OPEN, "clientA", counterparty, []string{"1.0.0"}) + conn := connection.NewConnectionEnd(connectionexported.OPEN, testConnectionID, "clientA", counterparty, []string{"1.0.0"}) testCases := []struct { name string diff --git a/x/ibc/genesis.go b/x/ibc/genesis.go new file mode 100644 index 0000000000..9be12b9aa6 --- /dev/null +++ b/x/ibc/genesis.go @@ -0,0 +1,37 @@ +package ibc + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" +) + +// GenesisState defines the ibc module's genesis state. +type GenesisState struct { + ConnectionGenesis connection.GenesisState `json:"connection_genesis" yaml:"connection_genesis"` +} + +// DefaultGenesisState returns the ibc module's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + ConnectionGenesis: connection.DefaultGenesisState(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + return gs.ConnectionGenesis.Validate() +} + +// InitGenesis initializes the ibc connection submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) { + connection.InitGenesis(ctx, k.ConnectionKeeper, gs.ConnectionGenesis) +} + +// ExportGenesis returns the ibc connection submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + return GenesisState{ + ConnectionGenesis: connection.ExportGenesis(ctx, k.ConnectionKeeper), + } +} diff --git a/x/ibc/genesis_test.go b/x/ibc/genesis_test.go new file mode 100644 index 0000000000..1957857ec2 --- /dev/null +++ b/x/ibc/genesis_test.go @@ -0,0 +1,70 @@ +package ibc + +import ( + "testing" + + "github.com/stretchr/testify/require" + + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +var ( + connectionID = "connectionidone" + clientID = "clientidone" + connectionID2 = "connectionidtwo" + clientID2 = "clientidtwo" +) + +func TestValidateGenesis(t *testing.T) { + + testCases := []struct { + name string + genState GenesisState + expPass bool + }{ + { + name: "default", + genState: DefaultGenesisState(), + expPass: true, + }, + { + name: "valid genesis", + genState: GenesisState{ + ConnectionGenesis: connection.NewGenesisState( + []connection.ConnectionEnd{ + connection.NewConnectionEnd(connectionexported.INIT, connectionID, clientID, connection.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{"1.0.0"}), + }, + []connection.ConnectionPaths{ + connection.NewConnectionPaths(clientID, []string{ibctypes.ConnectionPath(connectionID)}), + }, + ), + }, + expPass: true, + }, + { + name: "invalid connection genesis", + genState: GenesisState{ + ConnectionGenesis: connection.NewGenesisState( + []connection.ConnectionEnd{ + connection.NewConnectionEnd(connectionexported.INIT, connectionID, "CLIENTIDONE", connection.NewCounterparty(clientID, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{"1.0.0"}), + }, + nil, + ), + }, + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expPass { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} diff --git a/x/ibc/module.go b/x/ibc/module.go index f1db03875e..8f62ba1250 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -2,6 +2,7 @@ package ibc import ( "encoding/json" + "fmt" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -56,8 +57,13 @@ func (AppModuleBasic) DefaultGenesis(_ codec.JSONMarshaler) json.RawMessage { } // ValidateGenesis performs genesis state validation for the ibc module. -func (AppModuleBasic) ValidateGenesis(_ codec.JSONMarshaler, _ json.RawMessage) error { - return nil +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) error { + var gs GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err) + } + + return gs.Validate() } // RegisterRESTRoutes registers the REST routes for the ibc module. @@ -120,14 +126,20 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { // InitGenesis performs genesis initialization for the ibc module. It returns // no validator updates. -func (am AppModule) InitGenesis(_ sdk.Context, _ codec.JSONMarshaler, _ json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, bz json.RawMessage) []abci.ValidatorUpdate { + var gs GenesisState + err := cdc.UnmarshalJSON(bz, &gs) + if err != nil { + panic(fmt.Sprintf("failed to unmarshal %s genesis state: %s", ModuleName, err)) + } + InitGenesis(ctx, *am.keeper, gs) return []abci.ValidatorUpdate{} } // ExportGenesis returns the exported genesis state as raw bytes for the ibc // module. -func (am AppModule) ExportGenesis(_ sdk.Context, _ codec.JSONMarshaler) json.RawMessage { - return nil +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { + return cdc.MustMarshalJSON(ExportGenesis(ctx, *am.keeper)) } // BeginBlock returns the begin blocker for the ibc module.