8000 feat: delegations status filter by beer-1 · Pull Request #386 · initia-labs/initia · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: delegations status filter #386

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

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

Filter by extension

Filter by extension

Conversations
8000 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 client/docs/statik/statik.go

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43489,6 +43489,16 @@ paths:
in: query
required: false
type: boolean
- name: status
description: >-
status enables to query for delegations with validators of a
specific bond status (bonded/unbonded/unbonding).

If empty, it queries for all delegations regardless of validator
status.
in: query
required: false
type: string
tags:
- Query
/initia/mstaking/v1/delegators/{delegator_addr}/redelegations:
Expand Down Expand Up @@ -44251,6 +44261,16 @@ paths:
in: path
required: true
type: string
- name: status
description: >-
status enables to query for delegations with validators of a
specific bond status (bonded/unbonded/unbonding).

If empty, it queries for all delegations regardless of validator
status.
in: query
required: false
type: string
tags:
- Query
/initia/mstaking/v1/delegators/{delegator_addr}/unbonding_delegations:
Expand Down
8 changes: 8 additions & 0 deletions proto/initia/mstaking/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ message QueryDelegatorDelegationsRequest {

// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;

// status enables to query for delegations with validators of a specific bond status (bonded/unbonded/unbonding).
// If empty, it queries for all delegations regardless of validator status.
string status = 3;
}

// QueryDelegatorDelegationsResponse is response type for the
Expand Down Expand Up @@ -332,6 +336,10 @@ message QueryDelegatorValidatorResponse {
message QueryDelegatorTotalDelegationBalanceRequest {
// delegator_addr defines the delegator address to query for.
string delegator_addr = 1;

// status enables to query for delegations with validators of a specific bond status (bonded/unbonded/unbonding).
// If empty, it queries for all delegations regardless of validator status.
string status = 2;
}

// QueryDelegatorTotalDelegationBalanceResponse is response type for the
Expand Down
121 changes: 100 additions & 21 deletions x/mstaking/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import (
"github.com/initia-labs/initia/x/mstaking/types"
)

const (
contextKeyValidators = iota
)

// Querier is used as Keeper will have duplicate methods if used directly, and gRPC names take precedence over keeper
type Querier struct {
*Keeper
Expand All @@ -30,7 +34,7 @@ func (q Querier) Validators(ctx context.Context, req *types.QueryValidatorsReque
}

// validate the provided status, return all the validators if the status is empty
if req.Status != "" && !(req.Status == types.Bonded.String() || req.Status == types.Unbonded.String() || req.Status == types.Unbonding.String()) {
if !isValidStatus(req.Status) {
return nil, status.Errorf(codes.InvalidArgument, "invalid validator status %s", req.Status)
}

Expand Down Expand Up @@ -232,17 +236,27 @@ func (q Querier) DelegatorDelegations(ctx context.Context, req *types.QueryDeleg
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}

// validate the provided status, return all the validators if the status is empty
if !isValidStatus(req.Status) {
return nil, status.Errorf(codes.InvalidArgument, "invalid validator status %s", req.Status)
}

// validate the provided delegator address
if req.DelegatorAddr == "" {
return nil, status.Error(codes.InvalidArgument, "delegator address cannot be empty")
}

delAddr, err := q.authKeeper.AddressCodec().StringToBytes(req.DelegatorAddr)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

delegations, pageRes, err := query.CollectionPaginate(
// cache the validators in the context
validators := make(map[string]types.Validator)
ctx = sdk.UnwrapSDKContext(ctx).WithValue(contextKeyValidators, validators)

delegations, pageRes, err := query.CollectionFilteredPaginate(
ctx, q.Keeper.Delegations, req.Pagination,
delegationStatusFilterFunc(ctx, q.Keeper, req.Status),
func(key collections.Pair[[]byte, []byte], delegation types.Delegation) (types.Delegation, error) {
return delegation, nil
}, query.WithCollectionPaginationPairPrefix[[]byte, []byte](delAddr),
Expand Down Expand Up @@ -394,10 +408,15 @@ func (q Querier) DelegatorTotalDelegationBalance(ctx context.Context, req *types
return nil, status.Error(codes.InvalidArgument, "empty request")
}

// validate the provided status, return all the validators if the status is empty
if !isValidStatus(req.Status) {
return nil, status.Errorf(codes.InvalidArgument, "invalid validator status %s", req.Status)
}

// validate the provided delegator address
if req.DelegatorAddr == "" {
return nil, status.Error(codes.InvalidArgument, "delegator address cannot be empty")
}

delAddr, err := q.authKeeper.AddressCodec().StringToBytes(req.DelegatorAddr)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
Expand All @@ -408,17 +427,13 @@ func (q Querier) DelegatorTotalDelegationBalance(ctx context.Context, req *types
return nil, status.Error(codes.Internal, err.Error())
}

delegationResps, err := delegationsToDelegationResponses(ctx, q.Keeper, delegations)
// filter the delegations based on the provided status
totalDelegationBalance, err := totalDelegationBalance(ctx, q.Keeper, delegations, req.Status)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

var allBalances sdk.Coins
for _, delegationResp := range delegationResps {
allBalances = allBalances.Add(delegationResp.Balance...)
}

return &types.QueryDelegatorTotalDelegationBalanceResponse{Balance: allBalances}, nil
return &types.QueryDelegatorTotalDelegationBalanceResponse{Balance: totalDelegationBalance}, nil
}

// Pool queries the pool info
Expand Down Expand Up @@ -531,13 +546,33 @@ func queryAllRedelegations(ctx context.Context, q Querier, req *types.QueryRedel

// util

func delegationToDelegationResponse(ctx context.Context, k *Keeper, del types.Delegation) (types.DelegationResponse, error) {
valAddr, err := k.validatorAddressCodec.StringToBytes(del.GetValidatorAddr())
// lookupValidator looks up a validator in the context cache first, then from the store
func lookupValidator(ctx context.Context, k *Keeper, valAddrStr string) (types.Validator, error) {
if validators, ok := sdk.UnwrapSDKContext(ctx).Value(contextKeyValidators).(map[string]types.Validator); ok {
if val, ok := validators[valAddrStr]; ok {
return val, nil
}
}

valAddr, err := k.validatorAddressCodec.StringToBytes(valAddrStr)
if err != nil {
return types.DelegationResponse{}, err
return types.Validator{}, err
}

val, err := k.Validators.Get(ctx, valAddr)
if err != nil {
return types.Validator{}, err
}

if validators, ok := sdk.UnwrapSDKContext(ctx).Value(contextKeyValidators).(map[string]types.Validator); ok {
validators[valAddrStr] = val
}

return val, nil
}

func delegationToDelegationResponse(ctx context.Context, k *Keeper, del types.Delegation) (types.DelegationResponse, error) {
val, err := lookupValidator(ctx, k, del.GetValidatorAddr())
if err != nil {
return types.DelegationResponse{}, err
}
Expand Down Expand Up @@ -575,19 +610,14 @@ func redelegationsToRedelegationResponses(ctx context.Context, k *Keeper, redels
resp := make(types.RedelegationResponses, len(redels))

for i, redel := range redels {
valDstAddr, err := k.validatorAddressCodec.StringToBytes(redel.ValidatorDstAddress)
if err != nil {
panic(err)
}

val, err := k.Validators.Get(ctx, valDstAddr)
valDst, err := lookupValidator(ctx, k, redel.ValidatorDstAddress)
if err != nil {
return nil, err
}

entryResponses := make([]types.RedelegationEntryResponse, len(redel.Entries))
for j, entry := range redel.Entries {
balance, _ := val.TokensFromShares(entry.SharesDst).TruncateDecimal()
balance, _ := valDst.TokensFromShares(entry.SharesDst).TruncateDecimal()
entryResponses[j] = types.NewRedelegationEntryResponse(
entry.CreationHeight,
entry.CompletionTime,
Expand All @@ -608,3 +638,52 @@ func redelegationsToRedelegationResponses(ctx context.Context, k *Keeper, redels

return resp, nil
}

func delegationStatusFilterFunc(ctx context.Context, k *Keeper, status string) func(key collections.Pair[[]byte, []byte], delegation types.Delegation) (bool, error) {
return func(key collections.Pair[[]byte, []byte], delegation types.Delegation) (bool, error) {
if status == "" {
return true, nil
}

val, err := lookupValidator(ctx, k, delegation.GetValidatorAddr())
if err != nil {
return false, err
}

return strings.EqualFold(val.GetStatus().String(), status), nil
}
}

func totalDelegationBalance(ctx context.Context, k *Keeper, del types.Delegations, status string) (sdk.Coins, error) {
balances := make(sdk.Coins, 0, len(del))
for _, del := range del {
balance, err := delegationBalance(ctx, k, del, status)
if err != nil {
return nil, err
} else if balance != nil {
balances = balances.Add(balance...)
}
}
return balances, nil
}

func delegationBalance(ctx context.Context, k *Keeper, del types.Delegation, status string) (sdk.Coins, error) {
val, err := lookupValidator(ctx, k, del.GetValidatorAddr())
if err != nil {
return nil, err
}

if status != "" && !strings.EqualFold(val.GetStatus().String(), status) {
return nil, nil
}

balances, _ := val.TokensFromShares(del.Shares).TruncateDecimal()
return balances, nil
}

func isValidStatus(s string) bool {
if s != "" && !(s == types.Bonded.String() || s == types.Unbonded.String() || s == types.Unbonding.String()) {
return false
}
return true
}
48 changes: 47 additions & 1 deletion x/mstaking/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,29 @@ func Test_grpcQueryDelegatorDelegations(t *testing.T) {
require.NoError(t, err)
require.Equal(t, delegation, d.Delegation)
}

// query delegator delegations with invalid status
req = types.QueryDelegatorDelegationsRequest{
DelegatorAddr: delAddrStr1,
Status: "invalid",
}
_, err = querier.DelegatorDelegations(ctx, &req)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid validator status invalid")

// set a validator status to unbonding
validator.Status = types.Unbonding
err = input.StakingKeeper.Validators.Set(ctx, valAddr2.Bytes(), validator)
require.NoError(t, err)

// query delegator delegations with valid status
req = types.QueryDelegatorDelegationsRequest{
DelegatorAddr: delAddrStr1,
Status: types.BondStatusUnbonding,
}
res, err = querier.DelegatorDelegations(ctx, &req)
require.NoError(t, err)
require.Len(t, res.DelegationResponses, 1)
}

func Test_grpcQueryDelegatorTotalDelegationBalance(t *testing.T) {
Expand Down Expand Up @@ -203,8 +226,31 @@ func Test_grpcQueryDelegatorTotalDelegationBalance(t *testing.T) {
res, err := querier.DelegatorTotalDelegationBalance(ctx, &req)
require.NoError(t, err)

// 1_000_000 (validator creation) + 2_000_000 (extra bond to valAddr2)
// 2_000_000 (validator creation) + 1_000_000 (extra bond to valAddr2)
require.Equal(t, res.Balance, sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(3_000_000))))

// invalid status
req = types.QueryDelegatorTotalDelegationBalanceRequest{
DelegatorAddr: delAddrStr1,
Status: "invalid",
}
_, err = querier.DelegatorTotalDelegationBalance(ctx, &req)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid validator status invalid")

// set a validator status to unbonding
validator.Status = types.Unbonding
err = input.StakingKeeper.Validators.Set(ctx, valAddr2.Bytes(), validator)
require.NoError(t, err)

// query delegator delegations with valid status
req = types.QueryDelegatorTotalDelegationBalanceRequest{
DelegatorAddr: delAddrStr1,
Status: types.BondStatusUnbonding,
}
res, err = querier.DelegatorTotalDelegationBalance(ctx, &req)
require.NoError(t, err)
require.Equal(t, res.Balance, sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(1_000_000))))
}

func Test_grpcQueryDelegatorUnbondingDelegations(t *testing.T) {
Expand Down
Loading
Loading
0