Skip to content

Commit 8f28494

Browse files
committed
chore: add channel client state rpc
1 parent 3864793 commit 8f28494

File tree

10 files changed

+985
-132
lines changed

10 files changed

+985
-132
lines changed

modules/core/04-channel/v2/client/cli/abci.go

+29
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
11
package cli
22

33
import (
4+
"context"
45
"encoding/binary"
56

67
errorsmod "cosmossdk.io/errors"
78

89
"github.com/cosmos/cosmos-sdk/client"
910

11+
clientutils "github.com/cosmos/ibc-go/v9/modules/core/02-client/client/utils"
12+
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
1013
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
1114
hostv2 "github.com/cosmos/ibc-go/v9/modules/core/24-host/v2"
1215
ibcclient "github.com/cosmos/ibc-go/v9/modules/core/client"
1316
)
1417

18+
func queryChannelClientStateABCI(clientCtx client.Context, channelID string) (*types.QueryChannelClientStateResponse, error) {
19+
queryClient := types.NewQueryClient(clientCtx)
20+
req := &types.QueryChannelClientStateRequest{
21+
ChannelId: channelID,
22+
}
23+
24+
res, err := queryClient.ChannelClientState(context.Background(), req)
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
clientStateRes, err := clientutils.QueryClientStateABCI(clientCtx, res.IdentifiedClientState.ClientId)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
// use client state returned from ABCI query in case query height differs
35+
identifiedClientState := clienttypes.IdentifiedClientState{
36+
ClientId: res.IdentifiedClientState.ClientId,
37+
ClientState: clientStateRes.ClientState,
38+
}
39+
res = types.NewQueryChannelClientStateResponse(identifiedClientState, clientStateRes.Proof, clientStateRes.ProofHeight)
40+
41+
return res, nil
42+
}
43+
1544
func queryNextSequenceSendABCI(clientCtx client.Context, channelID string) (*types.QueryNextSequenceSendResponse, error) {
1645
key := hostv2.NextSequenceSendKey(channelID)
1746
value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key)

modules/core/04-channel/v2/client/cli/cli.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func GetQueryCmd() *cobra.Command {
2020

2121
queryCmd.AddCommand(
2222
getCmdQueryChannel(),
23+
getCmdQueryChannelClientState(),
2324
getCmdQueryNextSequenceSend(),
2425
getCmdQueryPacketCommitment(),
2526
getCmdQueryPacketCommitments(),

modules/core/04-channel/v2/client/cli/query.go

+45
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,51 @@ func getCmdQueryChannel() *cobra.Command {
4949
return cmd
5050
}
5151

52+
// getCmdQueryChannelClientState defines the command to query the channel client state for the given channel ID.
53+
func getCmdQueryChannelClientState() *cobra.Command {
54+
cmd := &cobra.Command{
55+
Use: "client-state [channel-id]",
56+
Short: "Query the client state associated with a channel.",
57+
Long: "Query the client state associated with a channel for the provided channel ID.",
58+
Example: fmt.Sprintf("%s query %s %s client-state [channel-id]", version.AppName, exported.ModuleName, types.SubModuleName),
59+
Args: cobra.ExactArgs(1),
60+
RunE: func(cmd *cobra.Command, args []string) error {
61+
clientCtx, err := client.GetClientQueryContext(cmd)
62+
if err != nil {
63+
return err
64+
}
65+
66+
channelID := args[0]
67+
prove, err := cmd.Flags().GetBool(flags.FlagProve)
68+
if err != nil {
69+
return err
70+
}
71+
72+
if prove {
73+
res, err := queryChannelClientStateABCI(clientCtx, channelID)
74+
if err != nil {
75+
return err
76+
}
77+
78+
return clientCtx.PrintProto(res)
79+
}
80+
81+
queryClient := types.NewQueryClient(clientCtx)
82+
res, err := queryClient.ChannelClientState(cmd.Context(), types.NewQueryChannelClientStateRequest(channelID))
83+
if err != nil {
84+
return err
85+
}
86+
87+
return clientCtx.PrintProto(res)
88+
},
89+
}
90+
91+
cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results")
92+
flags.AddQueryFlagsToCmd(cmd)
93+
94+
return cmd
95+
}
96+
5297
// getCmdQueryNextSequenceSend defines the command to query a next send sequence for a given channel
5398
func getCmdQueryNextSequenceSend() *cobra.Command {
5499
cmd := &cobra.Command{

modules/core/04-channel/v2/keeper/grpc_query.go

+26
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,32 @@ func (q *queryServer) Channel(ctx context.Context, req *types.QueryChannelReques
5151
return types.NewQueryChannelResponse(channel), nil
5252
}
5353

54+
// ChannelClientState implements the Query/ChannelClientState gRPC method
55+
func (q *queryServer) ChannelClientState(ctx context.Context, req *types.QueryChannelClientStateRequest) (*types.QueryChannelClientStateResponse, error) {
56+
if req == nil {
57+
return nil, status.Error(codes.InvalidArgument, "empty request")
58+
}
59+
60+
if err := host.ChannelIdentifierValidator(req.ChannelId); err != nil {
61+
return nil, status.Error(codes.InvalidArgument, err.Error())
62+
}
63+
64+
channel, found := q.GetChannel(ctx, req.ChannelId)
65+
if !found {
66+
return nil, status.Error(codes.NotFound, errorsmod.Wrapf(types.ErrChannelNotFound, "channel-id: %s", req.ChannelId).Error())
67+
}
68+
69+
clientState, found := q.ClientKeeper.GetClientState(ctx, channel.ClientId)
70+
if !found {
71+
return nil, status.Error(codes.NotFound, errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client-id: %s", channel.ClientId).Error())
72+
}
73+
74+
identifiedClientState := clienttypes.NewIdentifiedClientState(channel.ClientId, clientState)
75+
res := types.NewQueryChannelClientStateResponse(identifiedClientState, nil, clienttypes.GetSelfHeight(ctx))
76+
77+
return res, nil
78+
}
79+
5480
// NextSequenceSend implements the Query/NextSequenceSend gRPC method
5581
func (q *queryServer) NextSequenceSend(ctx context.Context, req *types.QueryNextSequenceSendRequest) (*types.QueryNextSequenceSendResponse, error) {
5682
if req == nil {

modules/core/04-channel/v2/keeper/grpc_query_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

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

11+
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
1112
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/keeper"
1213
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
1314
commitmenttypes "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types"
@@ -89,6 +90,106 @@ func (suite *KeeperTestSuite) TestQueryChannel() {
8990
}
9091
}
9192

93+
func (suite *KeeperTestSuite) TestQueryChannelClientState() {
94+
var (
95+
req *types.QueryChannelClientStateRequest
96+
expIdentifiedClientState clienttypes.IdentifiedClientState
97+
)
98+
99+
testCases := []struct {
100+
msg string
101+
malleate func()
102+
expError error
103+
}{
104+
{
105+
"success",
106+
func() {
107+
path := ibctesting.NewPath(suite.chainA, suite.chainB)
108+
path.SetupV2()
109+
110+
expClientState := suite.chainA.GetClientState(path.EndpointA.ClientID)
111+
expIdentifiedClientState = clienttypes.NewIdentifiedClientState(path.EndpointA.ClientID, expClientState)
112+
113+
req = &types.QueryChannelClientStateRequest{
114+
ChannelId: path.EndpointA.ChannelID,
115+
}
116+
},
117+
nil,
118+
},
119+
{
120+
"empty request",
121+
func() {
122+
req = nil
123+
},
124+
status.Error(codes.InvalidArgument, "empty request"),
125+
},
126+
{
127+
"invalid channel ID",
128+
func() {
129+
req = &types.QueryChannelClientStateRequest{
130+
ChannelId: "",
131+
}
132+
},
133+
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
134+
},
135+
{
136+
"channel not found",
137+
func() {
138+
req = &types.QueryChannelClientStateRequest{
139+
ChannelId: "test-channel-id",
140+
}
141+
},
142+
status.Error(codes.NotFound, fmt.Sprintf("channel-id: %s: channel not found", "test-channel-id")),
143+
},
144+
{
145+
"client state not found",
146+
func() {
147+
path := ibctesting.NewPath(suite.chainA, suite.chainB)
148+
path.SetupV2()
149+
150+
channel, found := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.GetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelID)
151+
suite.Require().True(found)
152+
channel.ClientId = ibctesting.SecondClientID
153+
154+
path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeperV2.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelID, channel)
155+
156+
req = &types.QueryChannelClientStateRequest{
157+
ChannelId: path.EndpointA.ChannelID,
158+
}
159+
},
160+
status.Error(codes.NotFound, fmt.Sprintf("client-id: %s: light client not found", ibctesting.SecondClientID)),
161+
},
162+
}
163+
164+
for _, tc := range testCases {
165+
tc := tc
166+
167+
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
168+
suite.SetupTest() // reset
169+
170+
tc.malleate()
171+
ctx := suite.chainA.GetContext()
172+
173+
queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2)
174+
res, err := queryServer.ChannelClientState(ctx, req)
175+
176+
expPass := tc.expError == nil
177+
if expPass {
178+
suite.Require().NoError(err)
179+
suite.Require().NotNil(res)
180+
suite.Require().Equal(&expIdentifiedClientState, res.IdentifiedClientState)
181+
182+
// ensure UnpackInterfaces is defined
183+
cachedValue := res.IdentifiedClientState.ClientState.GetCachedValue()
184+
suite.Require().NotNil(cachedValue)
185+
} else {
186+
suite.Require().ErrorIs(err, tc.expError)
187+
suite.Require().Nil(res)
188+
}
189+
})
190+
}
191+
}
192+
92193
func (suite *KeeperTestSuite) TestQueryPacketCommitment() {
93194
var (
94195
expCommitment []byte

modules/core/04-channel/v2/types/expected_keepers.go

+2
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ type ClientKeeper interface {
1919
// GetClientTimestampAtHeight returns the timestamp for a given height on the client
2020
// given its client ID and height
2121
GetClientTimestampAtHeight(ctx context.Context, clientID string, height exported.Height) (uint64, error)
22+
// GetClientState gets a particular client from the store
23+
GetClientState(ctx context.Context, clientID string) (exported.ClientState, bool)
2224
}

modules/core/04-channel/v2/types/query.go

+16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ func NewQueryChannelResponse(channel Channel) *QueryChannelResponse {
1616
}
1717
}
1818

19+
// NewQueryChannelClientStateRequest creates and returns a new ChannelClientState query request.
20+
func NewQueryChannelClientStateRequest(channelID string) *QueryChannelClientStateRequest {
21+
return &QueryChannelClientStateRequest{
22+
ChannelId: channelID,
23+
}
24+
}
25+
26+
// NewQueryChannelClientStateResponse creates and returns a new ChannelClientState query response.
27+
func NewQueryChannelClientStateResponse(identifiedClientState clienttypes.IdentifiedClientState, proof []byte, height clienttypes.Height) *QueryChannelClientStateResponse {
28+
return &QueryChannelClientStateResponse{
29+
IdentifiedClientState: &identifiedClientState,
30+
Proof: proof,
31+
ProofHeight: height,
32+
}
33+
}
34+
1935
// NewQueryNextSequenceSendRequest creates a new next sequence send query.
2036
func NewQueryNextSequenceSendRequest(channelID string) *QueryNextSequenceSendRequest {
2137
return &QueryNextSequenceSendRequest{

0 commit comments

Comments
 (0)