Skip to content

Commit f08fc12

Browse files
chore: add unreceived packets rpc (#7561)
* add UnreceivedPackets query * lint * minor * use HasChannel and correct flag * add unit test * lint * fix expErr * Update modules/core/04-channel/v2/keeper/grpc_query.go Co-authored-by: Damian Nolan <[email protected]> * using HasPacketReceipt --------- Co-authored-by: Damian Nolan <[email protected]>
1 parent 2acc7f6 commit f08fc12

File tree

8 files changed

+1073
-73
lines changed

8 files changed

+1073
-73
lines changed

Diff for: modules/core/04-channel/v2/client/cli/cli.go

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func GetQueryCmd() *cobra.Command {
2525
getCmdQueryPacketCommitments(),
2626
getCmdQueryPacketAcknowledgement(),
2727
getCmdQueryPacketReceipt(),
28+
getCmdQueryUnreceivedPackets(),
2829
getCmdQueryUnreceivedAcks(),
2930
)
3031

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

+44
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,50 @@ func getCmdQueryPacketReceipt() *cobra.Command {
287287
return cmd
288288
}
289289

290+
// getCmdQueryUnreceivedPackets defines the command to query all the unreceived
291+
// packets on the receiving chain
292+
func getCmdQueryUnreceivedPackets() *cobra.Command {
293+
cmd := &cobra.Command{
294+
Use: "unreceived-packets [channel-id]",
295+
Short: "Query a channel/v2 unreceived-packets",
296+
Long: "Query a channel/v2 unreceived-packets by channel-id and sequences",
297+
Example: fmt.Sprintf(
298+
"%s query %s %s unreceived-packet [channel-id] --sequences=1,2,3", version.AppName, exported.ModuleName, types.SubModuleName,
299+
),
300+
Args: cobra.ExactArgs(1),
301+
RunE: func(cmd *cobra.Command, args []string) error {
302+
clientCtx, err := client.GetClientQueryContext(cmd)
303+
if err != nil {
304+
return err
305+
}
306+
307+
channelID := args[0]
308+
seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences)
309+
if err != nil {
310+
return err
311+
}
312+
313+
seqs := make([]uint64, len(seqSlice))
314+
for i := range seqSlice {
315+
seqs[i] = uint64(seqSlice[i])
316+
}
317+
318+
queryClient := types.NewQueryClient(clientCtx)
319+
res, err := queryClient.UnreceivedPackets(cmd.Context(), types.NewQueryUnreceivedPacketsRequest(channelID, seqs))
320+
if err != nil {
321+
return err
322+
}
323+
324+
return clientCtx.PrintProto(res)
325+
},
326+
}
327+
328+
cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers")
329+
flags.AddQueryFlagsToCmd(cmd)
330+
331+
return cmd
332+
}
333+
290334
// getCmdQueryUnreceivedAcks defines the command to query all the unreceived acks on the original sending chain
291335
func getCmdQueryUnreceivedAcks() *cobra.Command {
292336
cmd := &cobra.Command{

Diff for: modules/core/04-channel/v2/keeper/grpc_query.go

+49
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,55 @@ func (q *queryServer) PacketReceipt(ctx context.Context, req *types.QueryPacketR
188188
return types.NewQueryPacketReceiptResponse(hasReceipt, nil, clienttypes.GetSelfHeight(ctx)), nil
189189
}
190190

191+
// UnreceivedPackets implements the Query/UnreceivedPackets gRPC method. Given
192+
// a list of counterparty packet commitments, the querier checks if the packet
193+
// has already been received by checking if a receipt exists on this
194+
// chain for the packet sequence. All packets that haven't been received yet
195+
// are returned in the response
196+
// Usage: To use this method correctly, first query all packet commitments on
197+
// the sending chain using the Query/PacketCommitments gRPC method.
198+
// Then input the returned sequences into the QueryUnreceivedPacketsRequest
199+
// and send the request to this Query/UnreceivedPackets on the **receiving**
200+
// chain. This gRPC method will then return the list of packet sequences that
201+
// are yet to be received on the receiving chain.
202+
//
203+
// NOTE: The querier makes the assumption that the provided list of packet
204+
// commitments is correct and will not function properly if the list
205+
// is not up to date. Ideally the query height should equal the latest height
206+
// on the counterparty's client which represents this chain.
207+
func (q *queryServer) UnreceivedPackets(ctx context.Context, req *types.QueryUnreceivedPacketsRequest) (*types.QueryUnreceivedPacketsResponse, error) {
208+
if req == nil {
209+
return nil, status.Error(codes.InvalidArgument, "empty request")
210+
}
211+
212+
if err := host.ChannelIdentifierValidator(req.ChannelId); err != nil {
213+
return nil, status.Error(codes.InvalidArgument, err.Error())
214+
}
215+
216+
if !q.HasChannel(ctx, req.ChannelId) {
217+
return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrChannelNotFound, req.ChannelId).Error())
218+
}
219+
220+
var unreceivedSequences []uint64
221+
for i, seq := range req.Sequences {
222+
// filter for invalid sequences to ensure they are not included in the response value.
223+
if seq == 0 {
224+
return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i)
225+
}
226+
227+
// if the packet receipt does not exist, then it is unreceived
228+
if !q.HasPacketReceipt(ctx, req.ChannelId, seq) {
229+
unreceivedSequences = append(unreceivedSequences, seq)
230+
}
231+
}
232+
233+
selfHeight := clienttypes.GetSelfHeight(ctx)
234+
return &types.QueryUnreceivedPacketsResponse{
235+
Sequences: unreceivedSequences,
236+
Height: selfHeight,
237+
}, nil
238+
}
239+
191240
// UnreceivedAcks implements the Query/UnreceivedAcks gRPC method. Given
192241
// a list of counterparty packet acknowledgements, the querier checks if the packet
193242
// has already been received by checking if the packet commitment still exists on this

Diff for: modules/core/04-channel/v2/keeper/grpc_query_test.go

+149
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,155 @@ func (suite *KeeperTestSuite) TestQueryNextSequenceSend() {
587587
}
588588
}
589589

590+
func (suite *KeeperTestSuite) TestQueryUnreceivedPackets() {
591+
var (
592+
expSeq []uint64
593+
path *ibctesting.Path
594+
req *types.QueryUnreceivedPacketsRequest
595+
)
596+
597+
testCases := []struct {
598+
msg string
599+
malleate func()
600+
expError error
601+
}{
602+
{
603+
"empty request",
604+
func() {
605+
req = nil
606+
},
607+
status.Error(codes.InvalidArgument, "empty request"),
608+
},
609+
{
610+
"invalid channel ID",
611+
func() {
612+
req = &types.QueryUnreceivedPacketsRequest{
613+
ChannelId: "",
614+
}
615+
},
616+
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
617+
},
618+
{
619+
"invalid seq",
620+
func() {
621+
path := ibctesting.NewPath(suite.chainA, suite.chainB)
622+
path.SetupV2()
623+
624+
req = &types.QueryUnreceivedPacketsRequest{
625+
ChannelId: path.EndpointA.ChannelID,
626+
Sequences: []uint64{0},
627+
}
628+
},
629+
status.Error(codes.InvalidArgument, "packet sequence 0 cannot be 0"),
630+
},
631+
{
632+
"channel not found",
633+
func() {
634+
req = &types.QueryUnreceivedPacketsRequest{
635+
ChannelId: "invalid-channel-id",
636+
}
637+
},
638+
status.Error(codes.NotFound, fmt.Sprintf("%s: channel not found", "invalid-channel-id")),
639+
},
640+
{
641+
"basic success empty packet commitments",
642+
func() {
643+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
644+
path.SetupV2()
645+
646+
expSeq = []uint64(nil)
647+
req = &types.QueryUnreceivedPacketsRequest{
648+
ChannelId: path.EndpointA.ChannelID,
649+
Sequences: []uint64{},
650+
}
651+
},
652+
nil,
653+
},
654+
{
655+
"basic success unreceived packet commitments",
656+
func() {
657+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
658+
path.SetupV2()
659+
660+
// no ack exists
661+
662+
expSeq = []uint64{1}
663+
req = &types.QueryUnreceivedPacketsRequest{
664+
ChannelId: path.EndpointA.ChannelID,
665+
Sequences: []uint64{1},
666+
}
667+
},
668+
nil,
669+
},
670+
{
671+
"basic success unreceived packet commitments, nothing to relay",
672+
func() {
673+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
674+
path.SetupV2()
675+
676+
suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelID, 1)
677+
678+
expSeq = []uint64(nil)
679+
req = &types.QueryUnreceivedPacketsRequest{
680+
ChannelId: path.EndpointA.ChannelID,
681+
Sequences: []uint64{1},
682+
}
683+
},
684+
nil,
685+
},
686+
{
687+
"success multiple unreceived packet commitments",
688+
func() {
689+
path = ibctesting.NewPath(suite.chainA, suite.chainB)
690+
path.SetupV2()
691+
expSeq = []uint64(nil) // reset
692+
packetCommitments := []uint64{}
693+
694+
// set packet receipt for every other sequence
695+
for seq := uint64(1); seq < 10; seq++ {
696+
packetCommitments = append(packetCommitments, seq)
697+
698+
if seq%2 == 0 {
699+
suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelID, seq)
700+
} else {
701+
expSeq = append(expSeq, seq)
702+
}
703+
}
704+
705+
req = &types.QueryUnreceivedPacketsRequest{
706+
ChannelId: path.EndpointA.ChannelID,
707+
Sequences: packetCommitments,
708+
}
709+
},
710+
nil,
711+
},
712+
}
713+
714+
for _, tc := range testCases {
715+
tc := tc
716+
717+
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
718+
suite.SetupTest() // reset
719+
720+
tc.malleate()
721+
ctx := suite.chainA.GetContext()
722+
723+
queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2)
724+
res, err := queryServer.UnreceivedPackets(ctx, req)
725+
726+
expPass := tc.expError == nil
727+
if expPass {
728+
suite.Require().NoError(err)
729+
suite.Require().NotNil(res)
730+
suite.Require().Equal(expSeq, res.Sequences)
731+
} else {
732+
suite.Require().ErrorIs(err, tc.expError)
733+
suite.Require().Error(err)
734+
}
735+
})
736+
}
737+
}
738+
590739
func (suite *KeeperTestSuite) TestQueryUnreceivedAcks() {
591740
var (
592741
path *ibctesting.Path

Diff for: modules/core/04-channel/v2/types/query.go

+8
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ func NewQueryPacketReceiptResponse(exists bool, proof []byte, height clienttypes
8484
ProofHeight: height,
8585
}
8686
}
87+
88+
// NewQueryPacketReceiptRequest creates and returns a new packet receipt query request.
89+
func NewQueryUnreceivedPacketsRequest(channelID string, sequences []uint64) *QueryUnreceivedPacketsRequest {
90+
return &QueryUnreceivedPacketsRequest{
91+
ChannelId: channelID,
92+
Sequences: sequences,
93+
}
94+
}

0 commit comments

Comments
 (0)