Skip to content

Commit

Permalink
chore: add unreceived acks rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
DimitrisJim committed Nov 14, 2024
1 parent 831a351 commit b6634c6
Show file tree
Hide file tree
Showing 7 changed files with 1,052 additions and 57 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(),
getCmdQueryUnreceivedAcks(),
)

return queryCmd
Expand Down
53 changes: 53 additions & 0 deletions modules/core/04-channel/v2/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import (
"github.com/cosmos/ibc-go/v9/modules/core/exported"
)

const (
flagSequences = "sequences"
)

// getCmdQueryChannel defines the command to query the channel information (creator and channel) for the given channel ID.
func getCmdQueryChannel() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -282,3 +286,52 @@ func getCmdQueryPacketReceipt() *cobra.Command {

return cmd
}

// getCmdQueryUnreceivedAcks defines the command to query all the unreceived acks on the original sending chain
func getCmdQueryUnreceivedAcks() *cobra.Command {
cmd := &cobra.Command{
Use: "unreceived-acks [channel-id]",
Short: "Query all the unreceived acks associated with a channel",
Long: `Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain.
The return value represents:
- Unreceived packet acknowledgement: packet commitment exists on original sending (executing) chain and ack exists on receiving chain.
`,
Example: fmt.Sprintf("%s query %s %s unreceived-acks [port-id] [channel-id] --sequences=1,2,3", version.AppName, exported.ModuleName, types.SubModuleName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

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])
}

req := &types.QueryUnreceivedAcksRequest{
ChannelId: args[0],
PacketAckSequences: seqs,
}

res, err := queryClient.UnreceivedAcks(cmd.Context(), req)
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
}
52 changes: 52 additions & 0 deletions modules/core/04-channel/v2/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,55 @@ func (q *queryServer) PacketReceipt(ctx context.Context, req *types.QueryPacketR

return types.NewQueryPacketReceiptResponse(hasReceipt, nil, clienttypes.GetSelfHeight(ctx)), 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
// chain (original sender) for the packet sequence.
// All acknowledgmeents that haven't been received yet are returned in the response.
// Usage: To use this method correctly, first query all packet acknowledgements on
// the original receiving chain (ie the chain that wrote the acks) using the Query/PacketAcknowledgements gRPC method.
// Then input the returned sequences into the QueryUnreceivedAcksRequest
// and send the request to this Query/UnreceivedAcks on the **original sending**
// chain. This gRPC method will then return the list of packet sequences whose
// acknowledgements are already written on the receiving chain but haven't yet
// been received back to the sending chain.
//
// NOTE: The querier makes the assumption that the provided list of packet
// acknowledgements 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) UnreceivedAcks(ctx context.Context, req *types.QueryUnreceivedAcksRequest) (*types.QueryUnreceivedAcksResponse, 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 _, seq := range req.PacketAckSequences {
if seq == 0 {
return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0")
}

// if packet commitment still exists on the original sending chain, then packet ack has not been received
// since processing the ack will delete the packet commitment
if commitment := q.GetPacketCommitment(ctx, req.ChannelId, seq); len(commitment) != 0 {
unreceivedSequences = append(unreceivedSequences, seq)
}

}

selfHeight := clienttypes.GetSelfHeight(ctx)
return &types.QueryUnreceivedAcksResponse{
Sequences: unreceivedSequences,
Height: selfHeight,
}, nil
}
131 changes: 131 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 @@ -586,3 +586,134 @@ func (suite *KeeperTestSuite) TestQueryNextSequenceSend() {
})
}
}

func (suite *KeeperTestSuite) TestQueryUnreceivedAcks() {
var (
req *types.QueryUnreceivedAcksRequest
expSeq = []uint64{}
)

testCases := []struct {
msg string
malleate func()
expError error
}{
{
"success",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

expSeq = []uint64(nil)
req = &types.QueryUnreceivedAcksRequest{
ChannelId: path.EndpointA.ChannelID,
PacketAckSequences: []uint64{1},
}
},
nil,
},
{
"success: single unreceived packet ack",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelID, 1, []byte("commitment"))

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

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

if seq%2 == 0 {
suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelID, seq, []byte("commitement"))
expSeq = append(expSeq, seq)
}
}

req = &types.QueryUnreceivedAcksRequest{
ChannelId: path.EndpointA.ChannelID,
PacketAckSequences: packetAcks,
}
},
nil,
},
{
"empty request",
func() {
req = nil
},
status.Error(codes.InvalidArgument, "empty request"),
},
{
"invalid channel ID",
func() {
req = &types.QueryUnreceivedAcksRequest{
ChannelId: "",
}
},
status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"),
},
{
"channel not found",
func() {
req = &types.QueryUnreceivedAcksRequest{
ChannelId: "test-channel-id",
}
},
status.Error(codes.NotFound, fmt.Sprintf("%s: channel not found", "test-channel-id")),
},
{
"invalid seq",
func() {
path := ibctesting.NewPath(suite.chainA, suite.chainB)
path.SetupV2()

req = &types.QueryUnreceivedAcksRequest{
ChannelId: path.EndpointA.ChannelID,
PacketAckSequences: []uint64{0},
}
},
status.Error(codes.InvalidArgument, "packet sequence cannot be 0"),
},
}

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.UnreceivedAcks(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().Nil(res)
}
})
}
}
Loading

0 comments on commit b6634c6

Please sign in to comment.