package keeper import ( "context" "strconv" "strings" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/query" clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) var _ types.QueryServer = (*Keeper)(nil) // Channel implements the Query/Channel gRPC method func (q Keeper) Channel(c context.Context, req *types.QueryChannelRequest) (*types.QueryChannelResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), ) } selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryChannelResponse(req.PortId, req.ChannelId, channel, nil, selfHeight), nil } // Channels implements the Query/Channels gRPC method func (q Keeper) Channels(c context.Context, req *types.QueryChannelsRequest) (*types.QueryChannelsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } ctx := sdk.UnwrapSDKContext(c) channels := []*types.IdentifiedChannel{} store := prefix.NewStore(ctx.KVStore(q.storeKey), []byte(host.KeyChannelPrefix)) pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { var result types.Channel if err := q.cdc.UnmarshalBinaryBare(value, &result); err != nil { return err } portID, channelID, err := host.ParseChannelPath(string(key)) if err != nil { return err } identifiedChannel := types.NewIdentifiedChannel(portID, channelID, result) channels = append(channels, &identifiedChannel) return nil }) if err != nil { return nil, err } selfHeight := clienttypes.GetSelfHeight(ctx) return &types.QueryChannelsResponse{ Channels: channels, Pagination: pageRes, Height: selfHeight, }, nil } // ConnectionChannels implements the Query/ConnectionChannels gRPC method func (q Keeper) ConnectionChannels(c context.Context, req *types.QueryConnectionChannelsRequest) (*types.QueryConnectionChannelsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := host.ConnectionIdentifierValidator(req.Connection); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } ctx := sdk.UnwrapSDKContext(c) channels := []*types.IdentifiedChannel{} store := prefix.NewStore(ctx.KVStore(q.storeKey), []byte(host.KeyChannelPrefix)) pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { var result types.Channel if err := q.cdc.UnmarshalBinaryBare(value, &result); err != nil { return err } // ignore channel and continue to the next item if the connection is // different than the requested one if result.ConnectionHops[0] != req.Connection { return nil } portID, channelID, err := host.ParseChannelPath(string(key)) if err != nil { return err } identifiedChannel := types.NewIdentifiedChannel(portID, channelID, result) channels = append(channels, &identifiedChannel) return nil }) if err != nil { return nil, err } selfHeight := clienttypes.GetSelfHeight(ctx) return &types.QueryConnectionChannelsResponse{ Channels: channels, Pagination: pageRes, Height: selfHeight, }, nil } // ChannelClientState implements the Query/ChannelClientState gRPC method func (q Keeper) ChannelClientState(c context.Context, req *types.QueryChannelClientStateRequest) (*types.QueryChannelClientStateResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), ) } connection, found := q.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", channel.ConnectionHops[0]).Error(), ) } clientState, found := q.clientKeeper.GetClientState(ctx, connection.ClientId) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(clienttypes.ErrClientNotFound, "client-id: %s", connection.ClientId).Error(), ) } identifiedClientState := clienttypes.NewIdentifiedClientState(connection.ClientId, clientState) selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryChannelClientStateResponse(identifiedClientState, nil, selfHeight), nil } // ChannelConsensusState implements the Query/ChannelConsensusState gRPC method func (q Keeper) ChannelConsensusState(c context.Context, req *types.QueryChannelConsensusStateRequest) (*types.QueryChannelConsensusStateResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), ) } connection, found := q.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", channel.ConnectionHops[0]).Error(), ) } consHeight := clienttypes.NewHeight(req.EpochNumber, req.EpochHeight) consensusState, found := q.clientKeeper.GetClientConsensusState(ctx, connection.ClientId, consHeight) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(clienttypes.ErrConsensusStateNotFound, "client-id: %s", connection.ClientId).Error(), ) } anyConsensusState, err := clienttypes.PackConsensusState(consensusState) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryChannelConsensusStateResponse(connection.ClientId, anyConsensusState, consHeight, nil, selfHeight), nil } // PacketCommitment implements the Query/PacketCommitment gRPC method func (q Keeper) PacketCommitment(c context.Context, req *types.QueryPacketCommitmentRequest) (*types.QueryPacketCommitmentResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } if req.Sequence == 0 { return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") } ctx := sdk.UnwrapSDKContext(c) commitmentBz := q.GetPacketCommitment(ctx, req.PortId, req.ChannelId, req.Sequence) if len(commitmentBz) == 0 { return nil, status.Error(codes.NotFound, "packet commitment hash not found") } selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryPacketCommitmentResponse(req.PortId, req.ChannelId, req.Sequence, commitmentBz, nil, selfHeight), nil } // PacketCommitments implements the Query/PacketCommitments gRPC method func (q Keeper) PacketCommitments(c context.Context, req *types.QueryPacketCommitmentsRequest) (*types.QueryPacketCommitmentsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) commitments := []*types.PacketAckCommitment{} store := prefix.NewStore(ctx.KVStore(q.storeKey), []byte(host.PacketCommitmentPrefixPath(req.PortId, req.ChannelId))) pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { keySplit := strings.Split(string(key), "/") sequence, err := strconv.ParseUint(keySplit[len(keySplit)-1], 10, 64) if err != nil { return err } commitment := types.NewPacketAckCommitment(req.PortId, req.ChannelId, sequence, value) commitments = append(commitments, &commitment) return nil }) if err != nil { return nil, err } selfHeight := clienttypes.GetSelfHeight(ctx) return &types.QueryPacketCommitmentsResponse{ Commitments: commitments, Pagination: pageRes, Height: selfHeight, }, nil } // PacketAcknowledgement implements the Query/PacketAcknowledgement gRPC method func (q Keeper) PacketAcknowledgement(c context.Context, req *types.QueryPacketAcknowledgementRequest) (*types.QueryPacketAcknowledgementResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } if req.Sequence == 0 { return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") } ctx := sdk.UnwrapSDKContext(c) acknowledgementBz, found := q.GetPacketAcknowledgement(ctx, req.PortId, req.ChannelId, req.Sequence) if !found || len(acknowledgementBz) == 0 { return nil, status.Error(codes.NotFound, "packet acknowledgement hash not found") } selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryPacketAcknowledgementResponse(req.PortId, req.ChannelId, req.Sequence, acknowledgementBz, nil, selfHeight), nil } // UnreceivedPackets implements the Query/UnreceivedPackets gRPC method. Given // a list of counterparty packet commitments, the querier checks if the packet // has already been received by checking if an acknowledgement exists on this // chain for the packet sequence. All packets that haven't been received yet // are returned in the response // Usage: To use this method correctly, first query all packet commitments on // the sending chain using the Query/PacketCommitments gRPC method. // Then input the returned sequences into the QueryUnreceivedPacketsRequest // and send the request to this Query/UnreceivedPackets on the **receiving** // chain. This gRPC method will then return the list of packet sequences that // are yet to be received on the receiving chain. // // NOTE: The querier makes the assumption that the provided list of packet // commitments is correct and will not function properly if the list // is not up to date. Ideally the query height should equal the latest height // on the counterparty's client which represents this chain. // TODO: Replace GetPacketAcknowledgement with GetPacketReceipt once async // acknowledgements issue is implemented. // Issue #7254: https://github.com/cosmos/cosmos-sdk/issues/7254 func (q Keeper) UnreceivedPackets(c context.Context, req *types.QueryUnreceivedPacketsRequest) (*types.QueryUnreceivedPacketsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) var unreceivedSequences = []uint64{} for i, seq := range req.PacketCommitmentSequences { if seq == 0 { return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) } // if acknowledgement exists on the receiving chain, then packet has already been received if _, found := q.GetPacketAcknowledgement(ctx, req.PortId, req.ChannelId, seq); !found { unreceivedSequences = append(unreceivedSequences, seq) } } selfHeight := clienttypes.GetSelfHeight(ctx) return &types.QueryUnreceivedPacketsResponse{ Sequences: unreceivedSequences, Height: selfHeight, }, nil } // UnrelayedAcks implements the Query/UnrelayedAcks gRPC method. Given // a list of counterparty packet acknowledgements, the querier checks if the packet // has already been received by checking if the packet commitment still exists on this // chain (original sender) for the packet sequence. // All acknowledgmeents that haven't been received yet are returned in the response. // Usage: To use this method correctly, first query all packet commitments on // the sending chain using the Query/PacketCommitments gRPC method. // Then input the returned sequences into the QueryUnrelayedPacketsRequest // and send the request to this Query/UnrelayedPackets on the **receiving** // chain. This gRPC method will then return the list of packet sequences whose // acknowledgements are already written on the receiving chain but haven't yet // been relayed back to the sending chain. // // NOTE: The querier makes the assumption that the provided list of packet // acknowledgements is correct and will not function properly if the list // is not up to date. Ideally the query height should equal the latest height // on the counterparty's client which represents this chain. func (q Keeper) UnrelayedAcks(c context.Context, req *types.QueryUnrelayedAcksRequest) (*types.QueryUnrelayedAcksResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) var unrelayedSequences = []uint64{} for i, seq := range req.PacketCommitmentSequences { if seq == 0 { return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) } // if packet commitment still exists on the original sending chain, then packet ack has not been received // since processing the ack will delete the packet commitment if _, found := q.GetPacketAcknowledgement(ctx, req.PortId, req.ChannelId, seq); found { unrelayedSequences = append(unrelayedSequences, seq) } } selfHeight := clienttypes.GetSelfHeight(ctx) return &types.QueryUnrelayedAcksResponse{ Sequences: unrelayedSequences, Height: selfHeight, }, nil } // NextSequenceReceive implements the Query/NextSequenceReceive gRPC method func (q Keeper) NextSequenceReceive(c context.Context, req *types.QueryNextSequenceReceiveRequest) (*types.QueryNextSequenceReceiveResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if err := validategRPCRequest(req.PortId, req.ChannelId); err != nil { return nil, err } ctx := sdk.UnwrapSDKContext(c) sequence, found := q.GetNextSequenceRecv(ctx, req.PortId, req.ChannelId) if !found { return nil, status.Error( codes.NotFound, sdkerrors.Wrapf(types.ErrSequenceReceiveNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), ) } selfHeight := clienttypes.GetSelfHeight(ctx) return types.NewQueryNextSequenceReceiveResponse(req.PortId, req.ChannelId, sequence, nil, selfHeight), nil } func validategRPCRequest(portID, channelID string) error { if err := host.PortIdentifierValidator(portID); err != nil { return status.Error(codes.InvalidArgument, err.Error()) } if err := host.ChannelIdentifierValidator(channelID); err != nil { return status.Error(codes.InvalidArgument, err.Error()) } return nil }