Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add channel client state rpc #7616

Merged
merged 2 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/core/04-channel/v2/client/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func GetQueryCmd() *cobra.Command {

queryCmd.AddCommand(
getCmdQueryChannel(),
getCmdQueryChannelClientState(),
getCmdQueryNextSequenceSend(),
getCmdQueryPacketCommitment(),
getCmdQueryPacketCommitments(),
Expand Down
31 changes: 31 additions & 0 deletions modules/core/04-channel/v2/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ func getCmdQueryChannel() *cobra.Command {
return cmd
}

// getCmdQueryChannelClientState defines the command to query the channel client state for the given channel ID.
func getCmdQueryChannelClientState() *cobra.Command {
cmd := &cobra.Command{
Use: "client-state [channel-id]",
Short: "Query the client state associated with a channel.",
Long: "Query the client state associated with a channel for the provided channel ID.",
Example: fmt.Sprintf("%s query %s %s client-state [channel-id]", version.AppName, exported.ModuleName, types.SubModuleName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

channelID := args[0]
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.ChannelClientState(cmd.Context(), types.NewQueryChannelClientStateRequest(channelID))
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

// getCmdQueryNextSequenceSend defines the command to query a next send sequence for a given channel
func getCmdQueryNextSequenceSend() *cobra.Command {
cmd := &cobra.Command{
Expand Down
26 changes: 26 additions & 0 deletions modules/core/04-channel/v2/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ func (q *queryServer) Channel(ctx context.Context, req *types.QueryChannelReques
return types.NewQueryChannelResponse(channel), nil
}

// ChannelClientState implements the Query/ChannelClientState gRPC method
func (q *queryServer) ChannelClientState(ctx context.Context, req *types.QueryChannelClientStateRequest) (*types.QueryChannelClientStateResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

if err := host.ChannelIdentifierValidator(req.ChannelId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

channel, found := q.GetChannel(ctx, req.ChannelId)
if !found {
return nil, status.Error(codes.NotFound, errorsmod.Wrapf(types.ErrChannelNotFound, "channel-id: %s", req.ChannelId).Error())
}

clientState, found := q.ClientKeeper.GetClientState(ctx, channel.ClientId)
if !found {
return nil, status.Error(codes.NotFound, errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client-id: %s", channel.ClientId).Error())
}

identifiedClientState := clienttypes.NewIdentifiedClientState(channel.ClientId, clientState)
res := types.NewQueryChannelClientStateResponse(identifiedClientState, nil, clienttypes.GetSelfHeight(ctx))

return res, nil
}

// NextSequenceSend implements the Query/NextSequenceSend gRPC method
func (q *queryServer) NextSequenceSend(ctx context.Context, req *types.QueryNextSequenceSendRequest) (*types.QueryNextSequenceSendResponse, error) {
if req == nil {
Expand Down
101 changes: 101 additions & 0 deletions modules/core/04-channel/v2/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/cosmos/cosmos-sdk/types/query"

clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/keeper"
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
commitmenttypes "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types"
Expand Down Expand Up @@ -89,6 +90,106 @@ func (suite *KeeperTestSuite) TestQueryChannel() {
}
}

func (suite *KeeperTestSuite) TestQueryChannelClientState() {
var (
req *types.QueryChannelClientStateRequest
expIdentifiedClientState clienttypes.IdentifiedClientState
)

testCases := []struct {
msg string
malleate func()
expError error
}{
{
"success",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

expClientState := suite.chainA.GetClientState(path.EndpointA.ClientID)
expIdentifiedClientState = clienttypes.NewIdentifiedClientState(path.EndpointA.ClientID, expClientState)

req = &types.QueryChannelClientStateRequest{
ChannelId: path.EndpointA.ChannelID,
}
},
nil,
},
{
"empty request",
func() {
req = nil
},
status.Error(codes.InvalidArgument, "empty request"),
},
{
"invalid channel ID",
func() {
req = &types.QueryChannelClientStateRequest{
ChannelId: "",
}
},
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
},
{
"channel not found",
func() {
req = &types.QueryChannelClientStateRequest{
ChannelId: "test-channel-id",
}
},
status.Error(codes.NotFound, fmt.Sprintf("channel-id: %s: channel not found", "test-channel-id")),
},
{
"client state not found",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

channel, found := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelID)
suite.Require().True(found)
channel.ClientId = ibctesting.SecondClientID

path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelID, channel)

req = &types.QueryChannelClientStateRequest{
ChannelId: path.EndpointA.ChannelID,
}
},
status.Error(codes.NotFound, fmt.Sprintf("client-id: %s: light client not found", ibctesting.SecondClientID)),
},
}

for _, tc := range testCases {
tc := tc

suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset

tc.malleate()
ctx := suite.chainA.GetContext()

queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2)
res, err := queryServer.ChannelClientState(ctx, req)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(&expIdentifiedClientState, res.IdentifiedClientState)

// ensure UnpackInterfaces is defined
cachedValue := res.IdentifiedClientState.ClientState.GetCachedValue()
suite.Require().NotNil(cachedValue)
} else {
suite.Require().ErrorIs(err, tc.expError)
suite.Require().Nil(res)
}
})
}
}

func (suite *KeeperTestSuite) TestQueryPacketCommitment() {
var (
expCommitment []byte
Expand Down
2 changes: 2 additions & 0 deletions modules/core/04-channel/v2/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ type ClientKeeper interface {
// GetClientTimestampAtHeight returns the timestamp for a given height on the client
// given its client ID and height
GetClientTimestampAtHeight(ctx context.Context, clientID string, height exported.Height) (uint64, error)
// GetClientState gets a particular client from the store
GetClientState(ctx context.Context, clientID string) (exported.ClientState, bool)
}
16 changes: 16 additions & 0 deletions modules/core/04-channel/v2/types/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ func NewQueryChannelResponse(channel Channel) *QueryChannelResponse {
}
}

// NewQueryChannelClientStateRequest creates and returns a new ChannelClientState query request.
func NewQueryChannelClientStateRequest(channelID string) *QueryChannelClientStateRequest {
return &QueryChannelClientStateRequest{
ChannelId: channelID,
}
}

// NewQueryChannelClientStateResponse creates and returns a new ChannelClientState query response.
func NewQueryChannelClientStateResponse(identifiedClientState clienttypes.IdentifiedClientState, proof []byte, height clienttypes.Height) *QueryChannelClientStateResponse {
return &QueryChannelClientStateResponse{
IdentifiedClientState: &identifiedClientState,
Proof: proof,
ProofHeight: height,
}
}

// NewQueryNextSequenceSendRequest creates a new next sequence send query.
func NewQueryNextSequenceSendRequest(channelID string) *QueryNextSequenceSendRequest {
return &QueryNextSequenceSendRequest{
Expand Down
Loading
Loading