Skip to content

Commit

Permalink
chore: add unreceived packets rpc (#7561)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
lacsomot and damiannolan authored Nov 19, 2024
1 parent 2acc7f6 commit f08fc12
Show file tree
Hide file tree
Showing 8 changed files with 1,073 additions and 73 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 @@ -25,6 +25,7 @@ func GetQueryCmd() *cobra.Command {
getCmdQueryPacketCommitments(),
getCmdQueryPacketAcknowledgement(),
getCmdQueryPacketReceipt(),
getCmdQueryUnreceivedPackets(),
getCmdQueryUnreceivedAcks(),
)

Expand Down
44 changes: 44 additions & 0 deletions modules/core/04-channel/v2/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,50 @@ func getCmdQueryPacketReceipt() *cobra.Command {
return cmd
}

// getCmdQueryUnreceivedPackets defines the command to query all the unreceived
// packets on the receiving chain
func getCmdQueryUnreceivedPackets() *cobra.Command {
cmd := &cobra.Command{
Use: "unreceived-packets [channel-id]",
Short: "Query a channel/v2 unreceived-packets",
Long: "Query a channel/v2 unreceived-packets by channel-id and sequences",
Example: fmt.Sprintf(
"%s query %s %s unreceived-packet [channel-id] --sequences=1,2,3", 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]
seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences)
if err != nil {
return err
}

seqs := make([]uint64, len(seqSlice))
for i := range seqSlice {
seqs[i] = uint64(seqSlice[i])
}

queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.UnreceivedPackets(cmd.Context(), types.NewQueryUnreceivedPacketsRequest(channelID, seqs))
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers")
flags.AddQueryFlagsToCmd(cmd)

return cmd
}

// getCmdQueryUnreceivedAcks defines the command to query all the unreceived acks on the original sending chain
func getCmdQueryUnreceivedAcks() *cobra.Command {
cmd := &cobra.Command{
Expand Down
49 changes: 49 additions & 0 deletions modules/core/04-channel/v2/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,55 @@ func (q *queryServer) PacketReceipt(ctx context.Context, req *types.QueryPacketR
return types.NewQueryPacketReceiptResponse(hasReceipt, nil, clienttypes.GetSelfHeight(ctx)), nil
}

// UnreceivedPackets implements the Query/UnreceivedPackets gRPC method. Given
// a list of counterparty packet commitments, the querier checks if the packet
// has already been received by checking if a receipt exists on this
// chain for the packet sequence. All packets that haven't been received yet
// are returned in the response
// Usage: To use this method correctly, first query all packet commitments on
// the sending chain using the Query/PacketCommitments gRPC method.
// Then input the returned sequences into the QueryUnreceivedPacketsRequest
// and send the request to this Query/UnreceivedPackets on the **receiving**
// chain. This gRPC method will then return the list of packet sequences that
// are yet to be received on the receiving chain.
//
// NOTE: The querier makes the assumption that the provided list of packet
// commitments is correct and will not function properly if the list
// is not up to date. Ideally the query height should equal the latest height
// on the counterparty's client which represents this chain.
func (q *queryServer) UnreceivedPackets(ctx context.Context, req *types.QueryUnreceivedPacketsRequest) (*types.QueryUnreceivedPacketsResponse, 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())
}

if !q.HasChannel(ctx, req.ChannelId) {
return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrChannelNotFound, req.ChannelId).Error())
}

var unreceivedSequences []uint64
for i, seq := range req.Sequences {
// filter for invalid sequences to ensure they are not included in the response value.
if seq == 0 {
return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i)
}

// if the packet receipt does not exist, then it is unreceived
if !q.HasPacketReceipt(ctx, req.ChannelId, seq) {
unreceivedSequences = append(unreceivedSequences, seq)
}
}

selfHeight := clienttypes.GetSelfHeight(ctx)
return &types.QueryUnreceivedPacketsResponse{
Sequences: unreceivedSequences,
Height: selfHeight,
}, nil
}

// UnreceivedAcks implements the Query/UnreceivedAcks gRPC method. Given
// a list of counterparty packet acknowledgements, the querier checks if the packet
// has already been received by checking if the packet commitment still exists on this
Expand Down
149 changes: 149 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 @@ -587,6 +587,155 @@ func (suite *KeeperTestSuite) TestQueryNextSequenceSend() {
}
}

func (suite *KeeperTestSuite) TestQueryUnreceivedPackets() {
var (
expSeq []uint64
path *ibctesting.Path
req *types.QueryUnreceivedPacketsRequest
)

testCases := []struct {
msg string
malleate func()
expError error
}{
{
"empty request",
func() {
req = nil
},
status.Error(codes.InvalidArgument, "empty request"),
},
{
"invalid channel ID",
func() {
req = &types.QueryUnreceivedPacketsRequest{
ChannelId: "",
}
},
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
},
{
"invalid seq",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

req = &types.QueryUnreceivedPacketsRequest{
ChannelId: path.EndpointA.ChannelID,
Sequences: []uint64{0},
}
},
status.Error(codes.InvalidArgument, "packet sequence 0 cannot be 0"),
},
{
"channel not found",
func() {
req = &types.QueryUnreceivedPacketsRequest{
ChannelId: "invalid-channel-id",
}
},
status.Error(codes.NotFound, fmt.Sprintf("%s: channel not found", "invalid-channel-id")),
},
{
"basic success empty packet commitments",
func() {
path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

expSeq = []uint64(nil)
req = &types.QueryUnreceivedPacketsRequest{
ChannelId: path.EndpointA.ChannelID,
Sequences: []uint64{},
}
},
nil,
},
{
"basic success unreceived packet commitments",
func() {
path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

// no ack exists

expSeq = []uint64{1}
req = &types.QueryUnreceivedPacketsRequest{
ChannelId: path.EndpointA.ChannelID,
Sequences: []uint64{1},
}
},
nil,
},
{
"basic success unreceived packet commitments, nothing to relay",
func() {
path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelID, 1)

expSeq = []uint64(nil)
req = &types.QueryUnreceivedPacketsRequest{
ChannelId: path.EndpointA.ChannelID,
Sequences: []uint64{1},
}
},
nil,
},
{
"success multiple unreceived packet commitments",
func() {
path = ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()
expSeq = []uint64(nil) // reset
packetCommitments := []uint64{}

// set packet receipt for every other sequence
for seq := uint64(1); seq < 10; seq++ {
packetCommitments = append(packetCommitments, seq)

if seq%2 == 0 {
suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelID, seq)
} else {
expSeq = append(expSeq, seq)
}
}

req = &types.QueryUnreceivedPacketsRequest{
ChannelId: path.EndpointA.ChannelID,
Sequences: packetCommitments,
}
},
nil,
},
}

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.UnreceivedPackets(ctx, req)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expSeq, res.Sequences)
} else {
suite.Require().ErrorIs(err, tc.expError)
suite.Require().Error(err)
}
})
}
}

func (suite *KeeperTestSuite) TestQueryUnreceivedAcks() {
var (
path *ibctesting.Path
Expand Down
8 changes: 8 additions & 0 deletions modules/core/04-channel/v2/types/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,11 @@ func NewQueryPacketReceiptResponse(exists bool, proof []byte, height clienttypes
ProofHeight: height,
}
}

// NewQueryPacketReceiptRequest creates and returns a new packet receipt query request.
func NewQueryUnreceivedPacketsRequest(channelID string, sequences []uint64) *QueryUnreceivedPacketsRequest {
return &QueryUnreceivedPacketsRequest{
ChannelId: channelID,
Sequences: sequences,
}
}
Loading

0 comments on commit f08fc12

Please sign in to comment.