Skip to content

Commit

Permalink
feat: add DenomOwners gRPC method for x/bank (#9533)
Browse files Browse the repository at this point in the history
# Description

Adds a new gRPC method, `DenomOwners`, to the `x/bank` module. This method queries for all account addresses that own a particular token denomination (paginated). _Naming subject to change based on reviews._

closes: #9393

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [x] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [x] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [x] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [x] confirmed all author checklist items have been addressed 
- [x] reviewed state machine logic
- [x] reviewed API design and naming
- [x] reviewed documentation is accurate
- [x] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
alexanderbez authored Jun 28, 2021
1 parent e17be87 commit d8059ff
Show file tree
Hide file tree
Showing 7 changed files with 1,182 additions and 74 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Features

* [\#9533](https://github.com/cosmos/cosmos-sdk/pull/9533) Added a new gRPC method, `DenomOwners`, in `x/bank` to query for all account holders of a specific denomination.

### API Breaking Changes

* (client/tx) [\#9421](https://github.com/cosmos/cosmos-sdk/pull/9421/) `BuildUnsignedTx`, `BuildSimTx`, `PrintUnsignedStdTx` functions are moved to
the Tx Factory as methods.

Expand Down
52 changes: 51 additions & 1 deletion docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,15 @@
- [GenesisState](#cosmos.bank.v1beta1.GenesisState)

- [cosmos/bank/v1beta1/query.proto](#cosmos/bank/v1beta1/query.proto)
- [DenomOwner](#cosmos.bank.v1beta1.DenomOwner)
- [QueryAllBalancesRequest](#cosmos.bank.v1beta1.QueryAllBalancesRequest)
- [QueryAllBalancesResponse](#cosmos.bank.v1beta1.QueryAllBalancesResponse)
- [QueryBalanceRequest](#cosmos.bank.v1beta1.QueryBalanceRequest)
- [QueryBalanceResponse](#cosmos.bank.v1beta1.QueryBalanceResponse)
- [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest)
- [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse)
- [QueryDenomOwnersRequest](#cosmos.bank.v1beta1.QueryDenomOwnersRequest)
- [QueryDenomOwnersResponse](#cosmos.bank.v1beta1.QueryDenomOwnersResponse)
- [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest)
- [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse)
- [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest)
Expand Down Expand Up @@ -1703,6 +1706,19 @@ GenesisState defines the bank module's genesis state.



<a name="cosmos.bank.v1beta1.DenomOwner"></a>

### DenomOwner

DenomOwner defines structure representing an account that owns or holds a
particular denominated token. It contains the account address and account
balance of the denominated token.

| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | address defines the address that owns a particular denomination. |
| `balance` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | balance is the balance of the denominated coin for an account. |

<a name="cosmos.bank.v1beta1.QueryAllBalancesRequest"></a>

### QueryAllBalancesRequest
Expand Down Expand Up @@ -1798,6 +1814,40 @@ method.



<a name="cosmos.bank.v1beta1.QueryDenomOwnersRequest"></a>

### QueryDenomOwnersRequest
QueryDenomOwnersRequest defines the request type for the DenomOwners RPC query,
which queries for a paginated set of all account holders of a particular
denomination.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom` | [string](#string) | | denom defines the coin denomination to query all account holders for. |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an optional pagination for the request. |






<a name="cosmos.bank.v1beta1.QueryDenomOwnersResponse"></a>

### QueryDenomOwnersResponse
QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom_owners` | [DenomOwner](#cosmos.bank.v1beta1.DenomOwner) | repeated | |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines the pagination in the response. |






<a name="cosmos.bank.v1beta1.QueryDenomsMetadataRequest"></a>

### QueryDenomsMetadataRequest
Expand Down Expand Up @@ -1938,6 +1988,7 @@ Query defines the gRPC querier service.
| `Params` | [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#cosmos.bank.v1beta1.QueryParamsResponse) | Params queries the parameters of x/bank module. | GET|/cosmos/bank/v1beta1/params|
| `DenomMetadata` | [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest) | [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse) | DenomsMetadata queries the client metadata of a given coin denomination. | GET|/cosmos/bank/v1beta1/denoms_metadata/{denom}|
| `DenomsMetadata` | [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest) | [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse) | DenomsMetadata queries the client metadata for all registered coin denominations. | GET|/cosmos/bank/v1beta1/denoms_metadata|
| `DenomOwners` | [QueryDenomOwnersRequest](#cosmos.bank.v1beta1.QueryDenomOwnersRequest) | [QueryDenomOwnersResponse](#cosmos.bank.v1beta1.QueryDenomOwnersResponse) | DenomOwners queries for all account addresses that own a particular token denomination. | GET|/cosmos/bank/v1beta1/denom_owners/{denom}|

<!-- end services -->

Expand Down Expand Up @@ -8259,4 +8310,3 @@ still be used for delegating and for governance votes even while locked.
| <a name="bool" /> bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |

39 changes: 38 additions & 1 deletion proto/cosmos/bank/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ service Query {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata/{denom}";
}

// DenomsMetadata queries the client metadata for all registered coin denominations.
// DenomsMetadata queries the client metadata for all registered coin
// denominations.
rpc DenomsMetadata(QueryDenomsMetadataRequest) returns (QueryDenomsMetadataResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata";
}

// DenomOwners queries for all account addresses that own a particular token
// denomination.
rpc DenomOwners(QueryDenomOwnersRequest) returns (QueryDenomOwnersResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denom_owners/{denom}";
}
}

// QueryBalanceRequest is the request type for the Query/Balance RPC method.
Expand Down Expand Up @@ -157,3 +164,33 @@ message QueryDenomMetadataResponse {
// metadata describes and provides all the client information for the requested token.
Metadata metadata = 1 [(gogoproto.nullable) = false];
}

// QueryDenomOwnersRequest defines the request type for the DenomOwners RPC query,
// which queries for a paginated set of all account holders of a particular
// denomination.
message QueryDenomOwnersRequest {
// denom defines the coin denomination to query all account holders for.
string denom = 1;

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

// DenomOwner defines structure representing an account that owns or holds a
// particular denominated token. It contains the account address and account
// balance of the denominated token.
message DenomOwner {
// address defines the address that owns a particular denomination.
string address = 1;

// balance is the balance of the denominated coin for an account.
cosmos.base.v1beta1.Coin balance = 2 [(gogoproto.nullable) = false];
}

// QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query.
message QueryDenomOwnersResponse {
repeated DenomOwner denom_owners = 1;

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
58 changes: 58 additions & 0 deletions x/bank/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,61 @@ func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetada
Metadata: metadata,
}, nil
}

func (k BaseKeeper) DenomOwners(
goCtx context.Context,
req *types.QueryDenomOwnersRequest,
) (*types.QueryDenomOwnersResponse, error) {

if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}

if req.Denom == "" {
return nil, status.Error(codes.InvalidArgument, "empty denom")
}

ctx := sdk.UnwrapSDKContext(goCtx)

store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)

var denomOwners []*types.DenomOwner
pageRes, err := query.FilteredPaginate(
balancesStore,
req.Pagination,
func(key []byte, value []byte, accumulate bool) (bool, error) {
var balance sdk.Coin
if err := k.cdc.Unmarshal(value, &balance); err != nil {
return false, err
}

if req.Denom != balance.Denom {
return false, nil
}

if accumulate {
address, err := types.AddressFromBalancesStore(key)
if err != nil {
return false, err
}

denomOwners = append(
denomOwners,
&types.DenomOwner{
Address: address.String(),
Balance: balance,
},
)
}

return true, nil
},
)

if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

return &types.QueryDenomOwnersResponse{DenomOwners: denomOwners, Pagination: pageRes}, nil
}
94 changes: 91 additions & 3 deletions x/bank/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import (
"fmt"

"github.com/cosmos/cosmos-sdk/simapp"

minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"

"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
)

func (suite *IntegrationTestSuite) TestQueryBalance() {
Expand Down Expand Up @@ -305,3 +304,92 @@ func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() {
})
}
}

func (suite *IntegrationTestSuite) TestGRPCDenomOwners() {
ctx := suite.ctx

authKeeper, keeper := suite.initKeepersWithmAccPerms(make(map[string]bool))
suite.Require().NoError(keeper.MintCoins(ctx, minttypes.ModuleName, initCoins))

for i := 0; i < 10; i++ {
acc := authKeeper.NewAccountWithAddress(ctx, authtypes.NewModuleAddress(fmt.Sprintf("account-%d", i)))
authKeeper.SetAccount(ctx, acc)

bal := sdk.NewCoins(sdk.NewCoin(
sdk.DefaultBondDenom,
sdk.TokensFromConsensusPower(initialPower/10, sdk.DefaultPowerReduction),
))
suite.Require().NoError(keeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, acc.GetAddress(), bal))
}

testCases := map[string]struct {
req *types.QueryDenomOwnersRequest
expPass bool
numAddrs int
hasNext bool
total uint64
}{
"empty request": {
req: &types.QueryDenomOwnersRequest{},
expPass: false,
},
"invalid denom": {
req: &types.QueryDenomOwnersRequest{
Denom: "foo",
},
expPass: true,
numAddrs: 0,
hasNext: false,
total: 0,
},
"valid request - page 1": {
req: &types.QueryDenomOwnersRequest{
Denom: sdk.DefaultBondDenom,
Pagination: &query.PageRequest{
Limit: 6,
CountTotal: true,
},
},
expPass: true,
numAddrs: 6,
hasNext: true,
total: 10,
},
"valid request - page 2": {
req: &types.QueryDenomOwnersRequest{
Denom: sdk.DefaultBondDenom,
Pagination: &query.PageRequest{
Offset: 6,
Limit: 10,
CountTotal: true,
},
},
expPass: true,
numAddrs: 4,
hasNext: false,
total: 10,
},
}

for name, tc := range testCases {
suite.Run(name, func() {
resp, err := suite.queryClient.DenomOwners(gocontext.Background(), tc.req)
if tc.expPass {
suite.NoError(err)
suite.NotNil(resp)
suite.Len(resp.DenomOwners, tc.numAddrs)
suite.Equal(tc.total, resp.Pagination.Total)

if tc.hasNext {
suite.NotNil(resp.Pagination.NextKey)
} else {
suite.Nil(resp.Pagination.NextKey)
}
} else {
suite.Require().Error(err)
}
})
}

suite.Require().True(true)
}
Loading

0 comments on commit d8059ff

Please sign in to comment.