Skip to content

Commit 78fc6c8

Browse files
committed
chore: add channel consensus state rpc
1 parent 2ca2824 commit 78fc6c8

File tree

7 files changed

+1088
-145
lines changed

7 files changed

+1088
-145
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,38 @@ func (q *queryServer) ChannelClientState(ctx context.Context, req *types.QueryCh
7878
return res, nil
7979
}
8080

81+
// ChannelConsensusState implements the Query/ChannelConsensusState gRPC method
82+
func (q *queryServer) ChannelConsensusState(ctx context.Context, req *types.QueryChannelConsensusStateRequest) (*types.QueryChannelConsensusStateResponse, error) {
83+
if req == nil {
84+
return nil, status.Error(codes.InvalidArgument, "empty request")
85+
}
86+
87+
if err := host.ChannelIdentifierValidator(req.ChannelId); err != nil {
88+
return nil, status.Error(codes.InvalidArgument, err.Error())
89+
}
90+
91+
channel, found := q.GetChannel(ctx, req.ChannelId)
92+
if !found {
93+
return nil, status.Error(codes.NotFound, errorsmod.Wrapf(types.ErrChannelNotFound, "channel-id: %s", req.ChannelId).Error())
94+
}
95+
96+
consHeight := clienttypes.NewHeight(req.RevisionNumber, req.RevisionHeight)
97+
consensusState, found := q.ClientKeeper.GetClientConsensusState(ctx, channel.ClientId, consHeight)
98+
if !found {
99+
return nil, status.Error(
100+
codes.NotFound,
101+
errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "client-id: %s", channel.ClientId).Error(),
102+
)
103+
}
104+
105+
anyConsensusState, err := clienttypes.PackConsensusState(consensusState)
106+
if err != nil {
107+
return nil, status.Error(codes.Internal, err.Error())
108+
}
109+
110+
return types.NewQueryChannelConsensusStateResponse(channel.ClientId, anyConsensusState, nil, clienttypes.GetSelfHeight(ctx)), nil
111+
}
112+
81113
// NextSequenceSend implements the Query/NextSequenceSend gRPC method
82114
func (q *queryServer) NextSequenceSend(ctx context.Context, req *types.QueryNextSequenceSendRequest) (*types.QueryNextSequenceSendResponse, error) {
83115
if req == nil {

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/keeper"
1313
"github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
1414
commitmenttypes "github.com/cosmos/ibc-go/v9/modules/core/23-commitment/types"
15+
"github.com/cosmos/ibc-go/v9/modules/core/exported"
1516
ibctesting "github.com/cosmos/ibc-go/v9/testing"
1617
)
1718

@@ -190,6 +191,113 @@ func (suite *KeeperTestSuite) TestQueryChannelClientState() {
190191
}
191192
}
192193

194+
func (suite *KeeperTestSuite) TestQueryChannelConsensusState() {
195+
var (
196+
req *types.QueryChannelConsensusStateRequest
197+
expConsensusState exported.ConsensusState
198+
expClientID string
199+
)
200+
201+
testCases := []struct {
202+
msg string
203+
malleate func()
204+
expError error
205+
}{
206+
{
207+
"success",
208+
func() {
209+
path := ibctesting.NewPath(suite.chainA, suite.chainB)
210+
path.SetupV2()
211+
212+
expConsensusState, _ = suite.chainA.GetConsensusState(path.EndpointA.ClientID, path.EndpointA.GetClientLatestHeight())
213+
suite.Require().NotNil(expConsensusState)
214+
expClientID = path.EndpointA.ClientID
215+
216+
req = &types.QueryChannelConsensusStateRequest{
217+
ChannelId: path.EndpointA.ChannelID,
218+
RevisionNumber: path.EndpointA.GetClientLatestHeight().GetRevisionNumber(),
219+
RevisionHeight: path.EndpointA.GetClientLatestHeight().GetRevisionHeight(),
220+
}
221+
},
222+
nil,
223+
},
224+
{
225+
"empty request",
226+
func() {
227+
req = nil
228+
},
229+
status.Error(codes.InvalidArgument, "empty request"),
230+
},
231+
{
232+
"invalid channel ID",
233+
func() {
234+
req = &types.QueryChannelConsensusStateRequest{
235+
ChannelId: "",
236+
RevisionNumber: 0,
237+
RevisionHeight: 1,
238+
}
239+
},
240+
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
241+
},
242+
{
243+
"channel not found",
244+
func() {
245+
req = &types.QueryChannelConsensusStateRequest{
246+
ChannelId: "test-channel-id",
247+
RevisionNumber: 0,
248+
RevisionHeight: 1,
249+
}
250+
},
251+
status.Error(codes.NotFound, fmt.Sprintf("channel-id: %s: channel not found", "test-channel-id")),
252+
},
253+
{
254+
"consensus state for channel's connection not found",
255+
func() {
256+
path := ibctesting.NewPath(suite.chainA, suite.chainB)
257+
path.SetupV2()
258+
259+
req = &types.QueryChannelConsensusStateRequest{
260+
ChannelId: path.EndpointA.ChannelID,
261+
RevisionNumber: 0,
262+
RevisionHeight: uint64(suite.chainA.GetContext().BlockHeight()), // use current height
263+
}
264+
},
265+
status.Error(codes.NotFound, fmt.Sprintf("client-id: %s: consensus state not found", "07-tendermint-0")),
266+
},
267+
}
268+
269+
for _, tc := range testCases {
270+
tc := tc
271+
272+
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
273+
suite.SetupTest() // reset
274+
275+
tc.malleate()
276+
ctx := suite.chainA.GetContext()
277+
278+
queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2)
279+
res, err := queryServer.ChannelConsensusState(ctx, req)
280+
281+
expPass := tc.expError == nil
282+
if expPass {
283+
suite.Require().NoError(err)
284+
suite.Require().NotNil(res)
285+
consensusState, err := clienttypes.UnpackConsensusState(res.ConsensusState)
286+
suite.Require().NoError(err)
287+
suite.Require().Equal(expConsensusState, consensusState)
288+
suite.Require().Equal(expClientID, res.ClientId)
289+
290+
// ensure UnpackInterfaces is defined
291+
cachedValue := res.ConsensusState.GetCachedValue()
292+
suite.Require().NotNil(cachedValue)
293+
} else {
294+
suite.Require().ErrorIs(err, tc.expError)
295+
suite.Require().Nil(res)
296+
}
297+
})
298+
}
299+
}
300+
193301
func (suite *KeeperTestSuite) TestQueryPacketCommitment() {
194302
var (
195303
expCommitment []byte

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ type ClientKeeper interface {
2121
GetClientTimestampAtHeight(ctx context.Context, clientID string, height exported.Height) (uint64, error)
2222
// GetClientState gets a particular client from the store
2323
GetClientState(ctx context.Context, clientID string) (exported.ClientState, bool)
24+
// GetClientConsensusState gets the stored consensus state from a client at a given height.
25+
GetClientConsensusState(ctx context.Context, clientID string, height exported.Height) (exported.ConsensusState, bool)
2426
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package types
22

3-
import clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
3+
import (
4+
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
5+
6+
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
7+
)
48

59
// NewQueryChannelRequest creates and returns a new channel query request.
610
func NewQueryChannelRequest(channelID string) *QueryChannelRequest {
@@ -32,6 +36,16 @@ func NewQueryChannelClientStateResponse(identifiedClientState clienttypes.Identi
3236
}
3337
}
3438

39+
// NewQueryChannelConsensusStateResponse creates and returns a new ChannelConsensusState query response.
40+
func NewQueryChannelConsensusStateResponse(clientID string, anyConsensusState *codectypes.Any, proof []byte, height clienttypes.Height) *QueryChannelConsensusStateResponse {
41+
return &QueryChannelConsensusStateResponse{
42+
ConsensusState: anyConsensusState,
43+
ClientId: clientID,
44+
Proof: proof,
45+
ProofHeight: height,
46+
}
47+
}
48+
3549
// NewQueryNextSequenceSendRequest creates a new next sequence send query.
3650
func NewQueryNextSequenceSendRequest(channelID string) *QueryNextSequenceSendRequest {
3751
return &QueryNextSequenceSendRequest{

0 commit comments

Comments
 (0)