Skip to content

Commit

Permalink
chore: add channel client state rpc (#7616)
Browse files Browse the repository at this point in the history
* chore: add channel client state rpc

* chore: drop func to provide proof
  • Loading branch information
DimitrisJim authored Dec 6, 2024
1 parent b9000c3 commit 82348c6
Show file tree
Hide file tree
Showing 9 changed files with 942 additions and 132 deletions.
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

0 comments on commit 82348c6

Please sign in to comment.