diff --git a/x/ibc/03-connection/client/utils/utils.go b/x/ibc/03-connection/client/utils/utils.go index f2cb94beac96..0334ad0b5393 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.ConnectionEnd, int64, error) { +func QueryAllConnections(cliCtx context.CLIContext, page, limit int) ([]types.IdentifiedConnectionEnd, 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.Co return nil, 0, err } - var connections []types.ConnectionEnd + var connections []types.IdentifiedConnectionEnd 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/keeper/keeper.go b/x/ibc/03-connection/keeper/keeper.go index 165e46e93823..3ae51c8b44cb 100644 --- a/x/ibc/03-connection/keeper/keeper.go +++ b/x/ibc/03-connection/keeper/keeper.go @@ -87,7 +87,7 @@ func (k Keeper) SetClientConnectionPaths(ctx sdk.Context, clientID string, paths // 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.ConnectionEnd) bool) { +func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.IdentifiedConnectionEnd) bool) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ibctypes.KeyConnectionPrefix) @@ -95,16 +95,17 @@ func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.ConnectionEnd) for ; iterator.Valid(); iterator.Next() { var connection types.ConnectionEnd k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &connection) + identifier := string(iterator.Key()[len(ibctypes.KeyConnectionPrefix)+1:]) - if cb(connection) { + if cb(types.IdentifiedConnectionEnd{connection, identifier}) { break } } } // GetAllConnections returns all stored ConnectionEnd objects. -func (k Keeper) GetAllConnections(ctx sdk.Context) (connections []types.ConnectionEnd) { - k.IterateConnections(ctx, func(connection types.ConnectionEnd) bool { +func (k Keeper) GetAllConnections(ctx sdk.Context) (connections []types.IdentifiedConnectionEnd) { + k.IterateConnections(ctx, func(connection types.IdentifiedConnectionEnd) 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 b0143c0f5d6e..f70b405eed7e 100644 --- a/x/ibc/03-connection/keeper/keeper_test.go +++ b/x/ibc/03-connection/keeper/keeper_test.go @@ -111,11 +111,15 @@ func (suite KeeperTestSuite) TestGetAllConnections() { conn2 := types.NewConnectionEnd(exported.INIT, testClientIDB, counterparty1, types.GetCompatibleVersions()) conn3 := types.NewConnectionEnd(exported.UNINITIALIZED, testClientID3, counterparty2, types.GetCompatibleVersions()) - expConnections := []types.ConnectionEnd{conn1, conn2, conn3} + expConnections := []types.IdentifiedConnectionEnd{ + {Connection: conn1, Identifier: testConnectionIDA}, + {Connection: conn2, Identifier: testConnectionIDB}, + {Connection: conn3, Identifier: testConnectionID3}, + } - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionIDA, expConnections[0]) - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionIDB, expConnections[1]) - suite.chainA.App.IBCKeeper.ConnectionKeeper.SetConnection(suite.chainA.GetContext(), testConnectionID3, expConnections[2]) + 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)) diff --git a/x/ibc/03-connection/keeper/querier.go b/x/ibc/03-connection/keeper/querier.go index 8225eccaa858..1eebee214bd5 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.ConnectionEnd{} + connections = []types.IdentifiedConnectionEnd{} } else { connections = connections[start:end] } diff --git a/x/ibc/03-connection/types/querier.go b/x/ibc/03-connection/types/querier.go index 48db30da8f95..a283f37641f9 100644 --- a/x/ibc/03-connection/types/querier.go +++ b/x/ibc/03-connection/types/querier.go @@ -15,10 +15,16 @@ 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 ConnectionEnd `json:"connection" yaml:"connection"` + Connection IdentifiedConnectionEnd `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"` @@ -29,7 +35,7 @@ func NewConnectionResponse( connectionID string, connection ConnectionEnd, proof *merkle.Proof, height int64, ) ConnectionResponse { return ConnectionResponse{ - Connection: connection, + Connection: IdentifiedConnectionEnd{connection, connectionID}, Proof: commitmenttypes.MerkleProof{Proof: proof}, ProofPath: commitmenttypes.NewMerklePath(strings.Split(ibctypes.ConnectionPath(connectionID), "/")), ProofHeight: uint64(height), diff --git a/x/ibc/04-channel/alias.go b/x/ibc/04-channel/alias.go index dc4073212266..55b3ac78d0b2 100644 --- a/x/ibc/04-channel/alias.go +++ b/x/ibc/04-channel/alias.go @@ -12,18 +12,20 @@ import ( ) const ( - SubModuleName = types.SubModuleName - StoreKey = types.StoreKey - RouterKey = types.RouterKey - QuerierRoute = types.QuerierRoute - QueryAllChannels = types.QueryAllChannels - QueryChannel = types.QueryChannel + SubModuleName = types.SubModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + QueryAllChannels = types.QueryAllChannels + QueryConnectionChannels = types.QueryConnectionChannels + QueryChannel = types.QueryChannel ) var ( // functions aliases NewKeeper = keeper.NewKeeper QuerierChannels = keeper.QuerierChannels + QuerierConnectionChannels = keeper.QuerierConnectionChannels NewChannel = types.NewChannel NewCounterparty = types.NewCounterparty RegisterCodec = types.RegisterCodec diff --git a/x/ibc/04-channel/client/utils/utils.go b/x/ibc/04-channel/client/utils/utils.go index b9fbd9175ba1..729fad9d1d2e 100644 --- a/x/ibc/04-channel/client/utils/utils.go +++ b/x/ibc/04-channel/client/utils/utils.go @@ -29,8 +29,8 @@ func QueryPacket( return types.PacketResponse{}, err } - destPortID := channelRes.Channel.Counterparty.PortID - destChannelID := channelRes.Channel.Counterparty.ChannelID + destPortID := channelRes.Channel.Channel.Counterparty.PortID + destChannelID := channelRes.Channel.Channel.Counterparty.ChannelID packet := types.NewPacket( res.Value, diff --git a/x/ibc/04-channel/keeper/keeper.go b/x/ibc/04-channel/keeper/keeper.go index ab756c044971..bccdeb775765 100644 --- a/x/ibc/04-channel/keeper/keeper.go +++ b/x/ibc/04-channel/keeper/keeper.go @@ -151,7 +151,7 @@ func (k Keeper) GetPacketAcknowledgement(ctx sdk.Context, portID, channelID stri // IterateChannels provides an iterator over all Channel objects. For each // Channel, cb will be called. If the cb returns true, the iterator will close // and stop. -func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.Channel) bool) { +func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.IdentifiedChannel) bool) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ibctypes.GetChannelPortsKeysPrefix(ibctypes.KeyChannelPrefix)) @@ -159,16 +159,17 @@ func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.Channel) bool) { for ; iterator.Valid(); iterator.Next() { var channel types.Channel k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &channel) + portID, channelID := ibctypes.MustParseChannelPath(string(iterator.Key())) - if cb(channel) { + if cb(types.IdentifiedChannel{Channel: channel, PortIdentifier: portID, ChannelIdentifier: channelID}) { break } } } // GetAllChannels returns all stored Channel objects. -func (k Keeper) GetAllChannels(ctx sdk.Context) (channels []types.Channel) { - k.IterateChannels(ctx, func(channel types.Channel) bool { +func (k Keeper) GetAllChannels(ctx sdk.Context) (channels []types.IdentifiedChannel) { + k.IterateChannels(ctx, func(channel types.IdentifiedChannel) bool { channels = append(channels, channel) return false }) diff --git a/x/ibc/04-channel/keeper/keeper_test.go b/x/ibc/04-channel/keeper/keeper_test.go index 2bf08ae2669e..8780b1feba9d 100644 --- a/x/ibc/04-channel/keeper/keeper_test.go +++ b/x/ibc/04-channel/keeper/keeper_test.go @@ -121,12 +121,16 @@ func (suite KeeperTestSuite) TestGetAllChannels() { Version: testChannelVersion, } - expChannels := []types.Channel{channel1, channel2, channel3} + expChannels := []types.IdentifiedChannel{ + {Channel: channel1, PortIdentifier: testPort1, ChannelIdentifier: testChannel1}, + {Channel: channel2, PortIdentifier: testPort2, ChannelIdentifier: testChannel2}, + {Channel: channel3, PortIdentifier: testPort3, ChannelIdentifier: testChannel3}, + } ctx := suite.chainB.GetContext() - suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort1, testChannel1, expChannels[0]) - suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort2, testChannel2, expChannels[1]) - suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort3, testChannel3, expChannels[2]) + suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort1, testChannel1, channel1) + suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort2, testChannel2, channel2) + suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort3, testChannel3, channel3) channels := suite.chainB.App.IBCKeeper.ChannelKeeper.GetAllChannels(ctx) suite.Require().Len(channels, len(expChannels)) diff --git a/x/ibc/04-channel/keeper/querier.go b/x/ibc/04-channel/keeper/querier.go index 0d60cab08c8b..91dfce5ec5ed 100644 --- a/x/ibc/04-channel/keeper/querier.go +++ b/x/ibc/04-channel/keeper/querier.go @@ -22,7 +22,7 @@ func QuerierChannels(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, start, end := client.Paginate(len(channels), params.Page, params.Limit, 100) if start < 0 || end < 0 { - channels = []types.Channel{} + channels = []types.IdentifiedChannel{} } else { channels = channels[start:end] } @@ -34,3 +34,35 @@ func QuerierChannels(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, return res, nil } + +// QuerierConnectionChannels defines the sdk.Querier to query all the channels for a connection. +func QuerierConnectionChannels(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + var params types.QueryConnectionChannelsParams + + if err := k.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + + channels := k.GetAllChannels(ctx) + + connectionChannels := []types.IdentifiedChannel{} + for _, channel := range channels { + if channel.Channel.ConnectionHops[0] == params.Connection { + connectionChannels = append(connectionChannels, channel) + } + } + + start, end := client.Paginate(len(connectionChannels), params.Page, params.Limit, 100) + if start < 0 || end < 0 { + connectionChannels = []types.IdentifiedChannel{} + } else { + connectionChannels = channels[start:end] + } + + res, err := codec.MarshalJSONIndent(k.cdc, connectionChannels) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + + return res, nil +} diff --git a/x/ibc/04-channel/types/querier.go b/x/ibc/04-channel/types/querier.go index fdedeca408b6..289a5bfc3cc6 100644 --- a/x/ibc/04-channel/types/querier.go +++ b/x/ibc/04-channel/types/querier.go @@ -11,14 +11,21 @@ import ( // query routes supported by the IBC channel Querier const ( - QueryAllChannels = "channels" - QueryChannel = "channel" + QueryAllChannels = "channels" + QueryChannel = "channel" + QueryConnectionChannels = "connection-channels" ) +type IdentifiedChannel struct { + Channel Channel `json:"channel_end" yaml:"channel_end"` + PortIdentifier string `json:"port_identifier" yaml:"port_identifier"` + ChannelIdentifier string `json:"channel_identifier" yaml:"channel_identifier"` +} + // ChannelResponse defines the client query response for a channel which also // includes a proof,its path and the height from which the proof was retrieved. type ChannelResponse struct { - Channel Channel `json:"channel" yaml:"channel"` + Channel IdentifiedChannel `json:"channel" yaml:"channel"` 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"` @@ -29,7 +36,7 @@ func NewChannelResponse( portID, channelID string, channel Channel, proof *merkle.Proof, height int64, ) ChannelResponse { return ChannelResponse{ - Channel: channel, + Channel: IdentifiedChannel{Channel: channel, PortIdentifier: portID, ChannelIdentifier: channelID}, Proof: commitmenttypes.MerkleProof{Proof: proof}, ProofPath: commitmenttypes.NewMerklePath(strings.Split(ibctypes.ChannelPath(portID, channelID), "/")), ProofHeight: uint64(height), @@ -51,6 +58,23 @@ func NewQueryAllChannelsParams(page, limit int) QueryAllChannelsParams { } } +// QueryConnectionChannelsParams defines the parameters necessary for querying +// for all channels associated with a given connection. +type QueryConnectionChannelsParams struct { + Connection string `json:"connection" yaml:"connection"` + Page int `json:"page" yaml:"page"` + Limit int `json:"limit" yaml:"limit"` +} + +// NewQueryConnectionChannelsParams creates a new QueryConnectionChannelsParams instance. +func NewQueryConnectionChannelsParams(connection string, page, limit int) QueryConnectionChannelsParams { + return QueryConnectionChannelsParams{ + Connection: connection, + Page: page, + Limit: limit, + } +} + // PacketResponse defines the client query response for a packet which also // includes a proof, its path and the height form which the proof was retrieved type PacketResponse struct { diff --git a/x/ibc/keeper/querier.go b/x/ibc/keeper/querier.go index 20bd0e746e82..0f9b2f0f5be5 100644 --- a/x/ibc/keeper/querier.go +++ b/x/ibc/keeper/querier.go @@ -39,6 +39,8 @@ func NewQuerier(k Keeper) sdk.Querier { switch path[1] { case channel.QueryAllChannels: res, err = channel.QuerierChannels(ctx, req, k.ChannelKeeper) + case channel.QueryConnectionChannels: + res, err = channel.QuerierConnectionChannels(ctx, req, k.ChannelKeeper) default: err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown IBC %s query endpoint", channel.SubModuleName) } diff --git a/x/ibc/keeper/querier_test.go b/x/ibc/keeper/querier_test.go index 3c8231c6be06..83987a517cc1 100644 --- a/x/ibc/keeper/querier_test.go +++ b/x/ibc/keeper/querier_test.go @@ -79,6 +79,12 @@ func (suite *KeeperTestSuite) TestNewQuerier() { false, "", }, + { + "channel - QuerierConnectionChannels", + []string{channel.SubModuleName, channel.QueryConnectionChannels}, + false, + "", + }, { "channel - invalid query", []string{channel.SubModuleName, "foo"}, diff --git a/x/ibc/types/keys.go b/x/ibc/types/keys.go index 8789ec33cc82..edb2ed243eb3 100644 --- a/x/ibc/types/keys.go +++ b/x/ibc/types/keys.go @@ -1,6 +1,9 @@ package types -import "fmt" +import ( + "fmt" + "strings" +) const ( // ModuleName is the name of the IBC module @@ -179,6 +182,17 @@ func channelPath(portID, channelID string) string { return fmt.Sprintf("ports/%s/channels/%s", portID, channelID) } +func MustParseChannelPath(path string) (string, string) { + split := strings.Split(path, "/") + if len(split) != 5 { + panic("cannot parse channel path") + } + if split[1] != "ports" || split[3] != "channels" { + panic("cannot parse channel path") + } + return split[2], split[4] +} + // ICS05 // The following paths are the keys to the store as defined in https://github.com/cosmos/ics/tree/master/spec/ics-005-port-allocation#store-paths