Skip to content

Commit

Permalink
feat: Query host chain user unbondings (#721)
Browse files Browse the repository at this point in the history
* query host chain user unbondings

* changelog

* paginate query

* Update CHANGELOG.md

---------

Co-authored-by: Puneet <59960662+puneet2019@users.noreply.github.com>
kruspy and puneet2019 authored Jan 12, 2024
1 parent d55fe77 commit 5e9a36a
Showing 7 changed files with 937 additions and 91 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Features

- [721](https://github.com/persistenceOne/pstake-native/pull/721) Add Query host chain user unbondings.

### Bug Fixes

- [720](https://github.com/persistenceOne/pstake-native/pull/720) Fix unbondings loop.
17 changes: 17 additions & 0 deletions proto/pstake/liquidstakeibc/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -63,6 +63,13 @@ service Query {
"/pstake/liquidstakeibc/v1beta1/user_unbondings/{address}";
}

// Queries all unbondings for a host chain.
rpc HostChainUserUnbondings(QueryHostChainUserUnbondingsRequest)
returns (QueryHostChainUserUnbondingsResponse) {
option (google.api.http).get =
"/pstake/liquidstakeibc/v1beta1/user_unbondings/{chain_id}";
}

// Queries all validator unbondings for a host chain.
rpc ValidatorUnbondings(QueryValidatorUnbondingRequest)
returns (QueryValidatorUnbondingResponse) {
@@ -142,6 +149,16 @@ message QueryUserUnbondingsResponse {
repeated UserUnbonding user_unbondings = 1;
}

message QueryHostChainUserUnbondingsRequest {
string chain_id = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

message QueryHostChainUserUnbondingsResponse {
repeated UserUnbonding user_unbondings = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message QueryValidatorUnbondingRequest { string chain_id = 1; }

message QueryValidatorUnbondingResponse {
47 changes: 47 additions & 0 deletions x/liquidstakeibc/client/query.go
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ func NewQueryCmd() *cobra.Command {
QueryLSMDepositsCmd(),
QueryUnbondingsCmd(),
QueryUserUnbondingsCmd(),
QueryHostChainUserUnbondingsCmd(),
QueryValidatorUnbondingsCmd(),
QueryDepositAccountBalanceCmd(),
QueryExchangeRateCmd(),
@@ -298,6 +299,52 @@ func QueryUserUnbondingsCmd() *cobra.Command {
return cmd
}

// QueryHostChainUserUnbondingsCmd returns all user unbondings for a host chain.
func QueryHostChainUserUnbondingsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "host-chain-user-unbondings [chain-id]",
Short: "Query all user unbonding records for a host chain",
Args: cobra.ExactArgs(1),
Long: strings.TrimSpace(
fmt.Sprintf(
`Query all user unbonding records for a host chain: $ %s query liquidstakeibc host-chain-user-unbondings [chain-id]`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.HostChainUserUnbondings(
context.Background(),
&types.QueryHostChainUserUnbondingsRequest{
ChainId: args[0],
Pagination: pageReq,
},
)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddPaginationFlagsToCmd(cmd, cmd.Use)
flags.AddQueryFlagsToCmd(cmd)

return cmd
}

// QueryValidatorUnbondingsCmd returns all validator unbondings for a host chain.
func QueryValidatorUnbondingsCmd() *cobra.Command {
cmd := &cobra.Command{
47 changes: 47 additions & 0 deletions x/liquidstakeibc/keeper/grpc_querier.go
Original file line number Diff line number Diff line change
@@ -3,8 +3,10 @@ package keeper
import (
"context"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -177,6 +179,51 @@ func (k *Keeper) UserUnbondings(
return &types.QueryUserUnbondingsResponse{UserUnbondings: userUnbondings}, nil
}

func (k *Keeper) HostChainUserUnbondings(
goCtx context.Context,
request *types.QueryHostChainUserUnbondingsRequest,
) (*types.QueryHostChainUserUnbondingsResponse, error) {
if request == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if request.ChainId == "" {
return nil, status.Error(codes.InvalidArgument, "chain id cannot be empty")
}

ctx := sdk.UnwrapSDKContext(goCtx)

store := ctx.KVStore(k.storeKey)
userUnbondingStore := prefix.NewStore(store, types.UserUnbondingKey)

var userUnbondings []*types.UserUnbonding
pageRes, err := query.FilteredPaginate(
userUnbondingStore,
request.Pagination,
func(key []byte, value []byte, accumulate bool) (bool, error) {

Check failure on line 202 in x/liquidstakeibc/keeper/grpc_querier.go

GitHub Actions / lint

File is not `gofumpt`-ed with `-extra` (gofumpt)
if accumulate {
var uu types.UserUnbonding
if err := k.cdc.Unmarshal(value, &uu); err != nil {
return false, err
}

if uu.ChainId == request.ChainId {
userUnbondings = append(userUnbondings, &uu)
return true, nil
}

return false, nil
}

return true, nil
})

Check failure on line 219 in x/liquidstakeibc/keeper/grpc_querier.go

GitHub Actions / lint

File is not `gofumpt`-ed with `-extra` (gofumpt)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

return &types.QueryHostChainUserUnbondingsResponse{UserUnbondings: userUnbondings, Pagination: pageRes}, nil
}

func (k *Keeper) ValidatorUnbondings(
goCtx context.Context,
request *types.QueryValidatorUnbondingRequest,
100 changes: 99 additions & 1 deletion x/liquidstakeibc/keeper/grpc_querier_test.go
Original file line number Diff line number Diff line change
@@ -2,9 +2,11 @@ package keeper_test

import (
"strconv"
"testing"

sdktypes "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/testutil"
"google.golang.org/grpc/codes"
@@ -14,7 +16,9 @@ import (
)

const (
MultipleTestSize int = 10
MultipleTestSize int = 10
PaginatedTestSize int = 98
PaginatedStep int = 5

TestAddress string = "persistence1xruvjju28j0a5ud5325rfdak8f5a04h0s30mld"
)
@@ -307,6 +311,100 @@ func (suite *IntegrationTestSuite) TestQueryUserUnbondings() {
}
}

func (suite *IntegrationTestSuite) TestQueryHostChainUserUnbondings() {
chainAUserUnbondings := make([]*types.UserUnbonding, 0)
for i := 0; i < PaginatedTestSize; i += 1 {
userUnbonding := &types.UserUnbonding{
ChainId: suite.chainA.ChainID,
Address: TestAddress,
EpochNumber: int64(i),
}
suite.app.LiquidStakeIBCKeeper.SetUserUnbonding(suite.ctx, userUnbonding)
chainAUserUnbondings = append(chainAUserUnbondings, userUnbonding)
}

chainBUserUnbondings := make([]*types.UserUnbonding, 0)
for i := 0; i < PaginatedTestSize; i += 1 {
userUnbonding := &types.UserUnbonding{
ChainId: suite.chainB.ChainID,
Address: TestAddress,
EpochNumber: int64(i),
}
suite.app.LiquidStakeIBCKeeper.SetUserUnbonding(suite.ctx, userUnbonding)
chainBUserUnbondings = append(chainBUserUnbondings, userUnbonding)
}

request := func(
chainID string,
next []byte,
offset, limit uint64,
total bool,
) *types.QueryHostChainUserUnbondingsRequest {
return &types.QueryHostChainUserUnbondingsRequest{
ChainId: chainID,
Pagination: &query.PageRequest{Key: next, Offset: offset, Limit: limit, CountTotal: total},
}
}

suite.T().Run("ByOffset", func(t *testing.T) {
for i := 0; i < PaginatedTestSize; i += PaginatedStep {
resp, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(
suite.ctx,
request(suite.chainB.ChainID, nil, uint64(i), uint64(PaginatedStep), false),
)
suite.Require().NoError(err)
suite.Require().LessOrEqual(len(resp.UserUnbondings), PaginatedStep)
suite.Require().Subset(chainBUserUnbondings, resp.UserUnbondings)
}
})

suite.T().Run("ByKey", func(t *testing.T) {
var next []byte
for i := 0; i < PaginatedTestSize; i += PaginatedStep {
resp, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(
suite.ctx,
request(suite.chainA.ChainID, next, 0, uint64(PaginatedStep), false),
)
suite.Require().NoError(err)
suite.Require().LessOrEqual(len(resp.UserUnbondings), PaginatedStep)
suite.Require().Subset(chainAUserUnbondings, resp.UserUnbondings)
next = resp.Pagination.NextKey
}
})

suite.T().Run("Total", func(t *testing.T) {
resp, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(
suite.ctx,
request(suite.chainB.ChainID, nil, 0, 0, true),
)
suite.Require().NoError(err)
suite.Require().Equal(len(chainBUserUnbondings), int(resp.Pagination.Total))
suite.Require().ElementsMatch(chainBUserUnbondings, resp.UserUnbondings)
})

suite.T().Run("Total Empty", func(t *testing.T) {
resp, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(
suite.ctx,
request("non-existing-chain", nil, 0, 0, true),
)
suite.Require().NoError(err)
suite.Require().Equal(0, int(resp.Pagination.Total))
})

suite.T().Run("Invalid Request", func(t *testing.T) {
_, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(
suite.ctx,
request("", nil, 0, 0, true),
)
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "chain id cannot be empty"))
})

suite.T().Run("Invalid Request", func(t *testing.T) {
_, err := suite.app.LiquidStakeIBCKeeper.HostChainUserUnbondings(suite.ctx, nil)
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "empty request"))
})
}

func (suite *IntegrationTestSuite) TestQueryValidatorUnbondings() {
validatorUnbondings := make([]*types.ValidatorUnbonding, 0)
for i := 0; i < MultipleTestSize; i += 1 {
694 changes: 604 additions & 90 deletions x/liquidstakeibc/types/query.pb.go

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions x/liquidstakeibc/types/query.pb.gw.go

0 comments on commit 5e9a36a

Please sign in to comment.