Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pagination for stakerinfos query, format #257

Merged
merged 9 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -634,4 +634,4 @@ check-licenses:
@python3 scripts/check_licenses.py .

swagger-ui:
docker run -p 8080:8080 -e SWAGGER_JSON=/app/swagger.json -v $(pwd)/client/docs/swagger-ui:/app swaggerapi/swagger-ui
docker run -p 8080:8080 -e SWAGGER_JSON=/app/swagger.json -v $(pwd)/client/docs/swagger-ui:/app swaggerapi/swagger-ui
70 changes: 70 additions & 0 deletions client/docs/swagger-ui/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -19974,6 +19974,22 @@
"title": "StakerInfo represents all related information for a staker of native-restaking"
},
"title": "all staker infos under the specified asset"
},
"pagination": {
"description": "pagination defines the pagination in the response.",
"type": "object",
"properties": {
"next_key": {
"type": "string",
"format": "byte",
"description": "next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results."
},
"total": {
"type": "string",
"format": "uint64",
"title": "total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise"
}
}
}
},
"title": "QueryStakerInfosResponse is response type for Query/StakerInfo RCP method"
Expand Down Expand Up @@ -20020,6 +20036,44 @@
"in": "path",
"required": true,
"type": "string"
},
{
"name": "pagination.key",
"description": "key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
},
{
"name": "pagination.offset",
"description": "offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.",
"in": "query",
"required": false,
"type": "string",
"format": "uint64"
},
{
"name": "pagination.limit",
"description": "limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.",
"in": "query",
"required": false,
"type": "string",
"format": "uint64"
},
{
"name": "pagination.count_total",
"description": "count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "pagination.reverse",
"description": "reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43",
"in": "query",
"required": false,
"type": "boolean"
}
],
"tags": [
Expand Down Expand Up @@ -42596,6 +42650,22 @@
"title": "StakerInfo represents all related information for a staker of native-restaking"
},
"title": "all staker infos under the specified asset"
},
"pagination": {
"description": "pagination defines the pagination in the response.",
"type": "object",
"properties": {
"next_key": {
"type": "string",
"format": "byte",
"description": "next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results."
},
"total": {
"type": "string",
"format": "uint64",
"title": "total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise"
}
}
}
},
"title": "QueryStakerInfosResponse is response type for Query/StakerInfo RCP method"
Expand Down
4 changes: 4 additions & 0 deletions proto/exocore/oracle/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,16 @@ message QueryStakerInfoResponse {
message QueryStakerInfosRequest {
// asset id for the staker info request for
string asset_id = 1;
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryStakerInfosResponse is response type for Query/StakerInfo RCP method
message QueryStakerInfosResponse {
// all staker infos under the specified asset
repeated StakerInfo staker_infos = 1;
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryParamsRequest is request type for the Query/Params RPC method.
Expand Down
10 changes: 9 additions & 1 deletion x/oracle/client/cli/query_native_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ func CmdQueryStakerInfos() *cobra.Command {
if _, _, err := assetstypes.ValidateID(assetID, true, false); err != nil {
return err
}
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
if pageReq.Limit > types.MaxPageLimit {
return types.ErrInvalidPagination.Wrapf("QueryStgakerInfos max page limitation is %d, got %d", types.MaxPageLimit, pageReq.Limit)
}

request := &types.QueryStakerInfosRequest{
AssetId: assetID,
AssetId: assetID,
Pagination: pageReq,
}

res, err := queryClient.StakerInfos(cmd.Context(), request)
Expand Down
33 changes: 21 additions & 12 deletions x/oracle/keeper/native_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
"github.com/ExocoreNetwork/exocore/x/oracle/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/ethereum/go-ethereum/common/hexutil"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// deposit: update staker's totalDeposit
Expand Down Expand Up @@ -70,24 +73,30 @@ func (k Keeper) GetStakerInfo(ctx sdk.Context, assetID, stakerAddr string) types
return stakerInfo
}

// TODO: pagination
// GetStakerInfos returns all stakers information
func (k Keeper) GetStakerInfos(ctx sdk.Context, assetID string) (ret []*types.StakerInfo) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.NativeTokenStakerKeyPrefix(assetID))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
func (k Keeper) GetStakerInfos(ctx sdk.Context, req *types.QueryStakerInfosRequest) (*types.QueryStakerInfosResponse, error) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.NativeTokenStakerKeyPrefix(req.AssetId))
retStakerInfos := make([]*types.StakerInfo, 0)
if req.Pagination != nil && req.Pagination.Limit > types.MaxPageLimit {
return nil, status.Errorf(codes.InvalidArgument, "pagination limit %d exceeds maximum allowed %d", req.Pagination.Limit, types.MaxPageLimit)
}
resPage, err := query.Paginate(store, req.Pagination, func(_ []byte, value []byte) error {
sInfo := types.StakerInfo{}
k.cdc.MustUnmarshal(iterator.Value(), &sInfo)
k.cdc.MustUnmarshal(value, &sInfo)
// keep only the latest effective-balance
if len(sInfo.BalanceList) > 0 {
sInfo.BalanceList = sInfo.BalanceList[len(sInfo.BalanceList)-1:]
}
// this is mainly used by price feeder, so we remove the stakerAddr to reduce the size of return value
sInfo.StakerAddr = ""
ret = append(ret, &sInfo)
retStakerInfos = append(retStakerInfos, &sInfo)
return nil
})
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err)
}
return ret
return &types.QueryStakerInfosResponse{
StakerInfos: retStakerInfos,
Pagination: resPage,
}, nil
}

// GetAllStakerInfosAssets returns all stakerInfos combined with assetIDs they belong to, used for genesisstate exporting
Expand Down Expand Up @@ -242,7 +251,7 @@ func (k Keeper) UpdateNSTValidatorListForStaker(ctx sdk.Context, assetID, staker
if newBalance.Balance <= 0 {
store.Delete(key)
} else {
stakerInfo.BalanceList = append(stakerInfo.BalanceList, &newBalance)
stakerInfo.Append(&newBalance)
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
}
Expand Down
3 changes: 1 addition & 2 deletions x/oracle/keeper/query_native_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ func (k Keeper) StakerInfos(goCtx context.Context, req *types.QueryStakerInfosRe
return nil, ErrUnsupportedAsset
}
ctx := sdk.UnwrapSDKContext(goCtx)
stakerInfos := k.GetStakerInfos(ctx, req.AssetId)
return &types.QueryStakerInfosResponse{StakerInfos: stakerInfos}, nil
return k.GetStakerInfos(ctx, req)
}

func (k Keeper) StakerInfo(goCtx context.Context, req *types.QueryStakerInfoRequest) (*types.QueryStakerInfoResponse, error) {
Expand Down
96 changes: 96 additions & 0 deletions x/oracle/keeper/query_native_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package keeper_test

import (
"fmt"
"strconv"
"testing"

keepertest "github.com/ExocoreNetwork/exocore/testutil/keeper"
"github.com/ExocoreNetwork/exocore/x/oracle/keeper"
"github.com/ExocoreNetwork/exocore/x/oracle/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func TestQueryStakerInfosPaginated(t *testing.T) {
assetID := string(keeper.NSTETHAssetIDMainnet)
keeper, ctx := keepertest.OracleKeeper(t)
wctx := sdk.WrapSDKContext(ctx)
msgs := createNStakerInfos(keeper, ctx, assetID, 5)

request := func(assetID string, next []byte, offset, limit uint64, total bool) *types.QueryStakerInfosRequest {
return &types.QueryStakerInfosRequest{
AssetId: assetID,
Pagination: &query.PageRequest{
Key: next,
Offset: offset,
Limit: limit,
CountTotal: total,
},
}
}
t.Run("ByOffset", func(t *testing.T) {
step := 2
for i := 0; i < len(msgs); i += step {
resp, err := keeper.StakerInfos(wctx, request(assetID, nil, uint64(i), uint64(step), false))
require.NoError(t, err)
require.LessOrEqual(t, len(resp.StakerInfos), step)
require.Subset(t,
msgs,
resp.StakerInfos,
)
}
})
t.Run("ByKey", func(t *testing.T) {
step := 2
var next []byte
for i := 0; i < len(msgs); i += step {
resp, err := keeper.StakerInfos(wctx, request(assetID, next, 0, uint64(step), false))
require.NoError(t, err)
require.LessOrEqual(t, len(resp.StakerInfos), step)
require.Subset(t,
msgs,
resp.StakerInfos,
)
next = resp.Pagination.NextKey
}
})
t.Run("Total", func(t *testing.T) {
resp, err := keeper.StakerInfos(wctx, request(assetID, nil, 0, 0, true))
require.NoError(t, err)
require.Equal(t, len(msgs), int(resp.Pagination.Total))
require.ElementsMatch(t,
msgs,
resp.StakerInfos,
)
})
t.Run("InvalidRequest", func(t *testing.T) {
_, err := keeper.StakerInfos(wctx, nil)
require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "invalid request"))
})
}

func createNStakerInfos(keeper *keeper.Keeper, ctx sdk.Context, assetID string, n int) []*types.StakerInfo {
ret := make([]*types.StakerInfo, 0, n)
for i := 0; i < n; i++ {
ret = append(ret, &types.StakerInfo{
StakerAddr: fmt.Sprintf("Staker_%d", i),
StakerIndex: int64(i),
ValidatorPubkeyList: []string{strconv.Itoa(i + 1)},
BalanceList: []*types.BalanceInfo{
{
RoundID: 0,
Block: 0,
Index: 0,
Balance: 32,
Change: types.Action_ACTION_DEPOSIT,
},
},
})
}
keeper.SetStakerInfos(ctx, assetID, ret)
return ret
}
2 changes: 2 additions & 0 deletions x/oracle/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
getPriceFailedRoundNotFound
updateNativeTokenVirtualPriceFail
nstAssetNotSurpported
invalidPageLimit
)

// x/oracle module sentinel errors
Expand All @@ -27,4 +28,5 @@ var (
ErrGetPriceRoundNotFound = sdkerrors.Register(ModuleName, getPriceFailedRoundNotFound, "get price failed for round not found")
ErrUpdateNativeTokenVirtualPriceFail = sdkerrors.Register(ModuleName, updateNativeTokenVirtualPriceFail, "update native token balance change failed")
ErrNSTAssetNotSupported = sdkerrors.Register(ModuleName, nstAssetNotSurpported, "nstAsset not supported")
ErrInvalidPagination = sdkerrors.Register(ModuleName, invalidPageLimit, "params for pagination is invalid")
)
Loading
Loading