Skip to content

Commit

Permalink
auth: query all accounts stored via gRPC (cosmos#8522)
Browse files Browse the repository at this point in the history
* Added the ability to query all accounts stored using gRPC

* Added CHANGELOG entry

* Fixed linting errors

* Update CHANGELOG.md

Co-authored-by: Amaury <amaury.martiny@protonmail.com>

* Update proto/cosmos/auth/v1beta1/query.proto

Co-authored-by: Amaury <amaury.martiny@protonmail.com>

* Run make proto-all

* Merged origin/master

* Applied suggestions

* Added CLI command

* Updated CHANGELOG

* Fixed merge conflicts

Co-authored-by: Amaury <amaury.martiny@protonmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored and zakir committed Apr 14, 2022
1 parent 842d5b2 commit 1925123
Show file tree
Hide file tree
Showing 7 changed files with 751 additions and 57 deletions.
21 changes: 21 additions & 0 deletions proto/cosmos/auth/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
syntax = "proto3";
package cosmos.auth.v1beta1;

import "cosmos/base/query/v1beta1/pagination.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "google/api/annotations.proto";
Expand All @@ -11,6 +12,11 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types";

// Query defines the gRPC querier service.
service Query {
// Accounts returns all the existing accounts
rpc Accounts(QueryAccountsRequest) returns (QueryAccountsResponse) {
option (google.api.http).get = "/cosmos/auth/v1beta1/accounts";
}

// Account returns account details based on address.
rpc Account(QueryAccountRequest) returns (QueryAccountResponse) {
option (google.api.http).get = "/cosmos/auth/v1beta1/accounts/{address}";
Expand All @@ -22,6 +28,21 @@ service Query {
}
}

// QueryAccountsRequest is the request type for the Query/Accounts RPC method.
message QueryAccountsRequest {
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// QueryAccountsResponse is the response type for the Query/Accounts RPC method.
message QueryAccountsResponse {
// accounts are the existing accounts
repeated google.protobuf.Any accounts = 1 [(cosmos_proto.accepts_interface) = "AccountI"];

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryAccountRequest is the request type for the Query/Account RPC method.
message QueryAccountRequest {
option (gogoproto.equal) = false;
Expand Down
50 changes: 25 additions & 25 deletions proto/cosmos/staking/v1beta1/staking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ message CommissionRates {
option (gogoproto.goproto_stringer) = false;

// rate is the commission rate charged to delegators, as a fraction.
string rate = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string rate = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
// max_rate defines the maximum commission rate which validator can ever charge, as a fraction.
string max_rate = 2 [
(gogoproto.moretags) = "yaml:\"max_rate\"",
Expand All @@ -49,9 +49,9 @@ message Commission {
option (gogoproto.goproto_stringer) = false;

// commission_rates defines the initial commission rates to be used for creating a validator.
CommissionRates commission_rates = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
CommissionRates commission_rates = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
// update_time is the last time the commission rate was changed.
google.protobuf.Timestamp update_time = 2
google.protobuf.Timestamp update_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"update_time\""];
}

Expand All @@ -61,15 +61,15 @@ message Description {
option (gogoproto.goproto_stringer) = false;

// moniker defines a human-readable name for the validator.
string moniker = 1;
string moniker = 1;
// identity defines an optional identity signature (ex. UPort or Keybase).
string identity = 2;
string identity = 2;
// website defines an optional website link.
string website = 3;
string website = 3;
// security_contact defines an optional email for security contact.
string security_contact = 4 [(gogoproto.moretags) = "yaml:\"security_contact\""];
// details define other optional details.
string details = 5;
string details = 5;
}

// Validator defines a validator, together with the total amount of the
Expand All @@ -86,12 +86,12 @@ message Validator {
option (gogoproto.goproto_getters) = false;

// operator_address defines the address of the validator's operator; bech encoded in JSON.
string operator_address = 1 [(gogoproto.moretags) = "yaml:\"operator_address\""];
string operator_address = 1 [(gogoproto.moretags) = "yaml:\"operator_address\""];
// consensus_pubkey is the consensus public key of the validator, as a Protobuf Any.
google.protobuf.Any consensus_pubkey = 2
[(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\""];
// jailed defined whether the validator has been jailed from bonded status or not.
bool jailed = 3;
bool jailed = 3;
// status is the validator status (bonded/unbonding/unbonded).
BondStatus status = 4;
// tokens define the delegated tokens (incl. self-delegation).
Expand All @@ -103,16 +103,16 @@ message Validator {
(gogoproto.nullable) = false
];
// description defines the description terms for the validator.
Description description = 7 [(gogoproto.nullable) = false];
Description description = 7 [(gogoproto.nullable) = false];
// unbonding_height defines, if unbonding, the height at which this validator has begun unbonding.
int64 unbonding_height = 8 [(gogoproto.moretags) = "yaml:\"unbonding_height\""];
int64 unbonding_height = 8 [(gogoproto.moretags) = "yaml:\"unbonding_height\""];
// unbonding_time defines, if unbonding, the min time for the validator to complete unbonding.
google.protobuf.Timestamp unbonding_time = 9
google.protobuf.Timestamp unbonding_time = 9
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
// commission defines the commission parameters.
Commission commission = 10 [(gogoproto.nullable) = false];
Commission commission = 10 [(gogoproto.nullable) = false];
// min_self_delegation is the validator's self declared minimum self delegation.
string min_self_delegation = 11 [
string min_self_delegation = 11 [
(gogoproto.moretags) = "yaml:\"min_self_delegation\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
Expand Down Expand Up @@ -201,9 +201,9 @@ message UnbondingDelegation {
option (gogoproto.goproto_stringer) = false;

// delegator_address is the bech32-encoded address of the delegator.
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
// validator_address is the bech32-encoded address of the validator.
string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
// entries are the unbonding delegation entries.
repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries
}
Expand All @@ -214,7 +214,7 @@ message UnbondingDelegationEntry {
option (gogoproto.goproto_stringer) = false;

// creation_height is the height which the unbonding took place.
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
// completion_time is the unix time for unbonding completion.
google.protobuf.Timestamp completion_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
Expand All @@ -234,7 +234,7 @@ message RedelegationEntry {
option (gogoproto.goproto_stringer) = false;

// creation_height defines the height which the redelegation took place.
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
// completion_time defines the unix time for redelegation completion.
google.protobuf.Timestamp completion_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
Expand All @@ -257,13 +257,13 @@ message Redelegation {
option (gogoproto.goproto_stringer) = false;

// delegator_address is the bech32-encoded address of the delegator.
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
// validator_src_address is the validator redelegation source operator address.
string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""];
string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""];
// validator_dst_address is the validator redelegation destination operator address.
string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""];
string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""];
// entries are the redelegation entries.
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
}

// Params defines the parameters for the staking module.
Expand All @@ -275,13 +275,13 @@ message Params {
google.protobuf.Duration unbonding_time = 1
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
// max_validators is the maximum number of validators.
uint32 max_validators = 2 [(gogoproto.moretags) = "yaml:\"max_validators\""];
uint32 max_validators = 2 [(gogoproto.moretags) = "yaml:\"max_validators\""];
// max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio).
uint32 max_entries = 3 [(gogoproto.moretags) = "yaml:\"max_entries\""];
uint32 max_entries = 3 [(gogoproto.moretags) = "yaml:\"max_entries\""];
// historical_entries is the number of historical entries to persist.
uint32 historical_entries = 4 [(gogoproto.moretags) = "yaml:\"historical_entries\""];
// bond_denom defines the bondable coin denomination.
string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""];
string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""];
}

// DelegationResponse is equivalent to Delegation except that it contains a
Expand Down
33 changes: 33 additions & 0 deletions x/auth/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func GetQueryCmd() *cobra.Command {

cmd.AddCommand(
GetAccountCmd(),
GetAccountsCmd(),
QueryParamsCmd(),
)

Expand Down Expand Up @@ -111,6 +112,38 @@ func GetAccountCmd() *cobra.Command {
return cmd
}

// GetAccountsCmd returns a query command that will display a list of accounts
func GetAccountsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "accounts",
Short: "Query all the accounts",
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.Accounts(cmd.Context(), &types.QueryAccountsRequest{Pagination: pageReq})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "all-accounts")

return cmd
}

// QueryTxsByEventsCmd returns a command to search through transactions by events.
func QueryTxsByEventsCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down
31 changes: 31 additions & 0 deletions x/auth/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package keeper
import (
"context"

"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/cosmos/cosmos-sdk/types/query"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

Expand All @@ -13,6 +16,34 @@ import (

var _ types.QueryServer = AccountKeeper{}

func (ak AccountKeeper) Accounts(c context.Context, req *types.QueryAccountsRequest) (*types.QueryAccountsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(c)
store := ctx.KVStore(ak.key)
accountsStore := prefix.NewStore(store, types.AddressStoreKeyPrefix)

var accounts []*codectypes.Any
pageRes, err := query.Paginate(accountsStore, req.Pagination, func(key, value []byte) error {
account := ak.decodeAccount(value)
any, err := codectypes.NewAnyWithValue(account)
if err != nil {
return err
}

accounts = append(accounts, any)
return nil
})

if err != nil {
return nil, status.Errorf(codes.Internal, "paginate: %v", err)
}

return &types.QueryAccountsResponse{Accounts: accounts, Pagination: pageRes}, err
}

// Account returns account details based on address
func (ak AccountKeeper) Account(c context.Context, req *types.QueryAccountRequest) (*types.QueryAccountResponse, error) {
if req == nil {
Expand Down
58 changes: 58 additions & 0 deletions x/auth/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,64 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

func (suite *KeeperTestSuite) TestGRPCQueryAccounts() {
var (
req *types.QueryAccountsRequest
)
_, _, first := testdata.KeyTestPubAddr()
_, _, second := testdata.KeyTestPubAddr()

testCases := []struct {
msg string
malleate func()
expPass bool
posttests func(res *types.QueryAccountsResponse)
}{
{
"success",
func() {
suite.app.AccountKeeper.SetAccount(suite.ctx,
suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, first))
suite.app.AccountKeeper.SetAccount(suite.ctx,
suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, second))
req = &types.QueryAccountsRequest{}
},
true,
func(res *types.QueryAccountsResponse) {
for _, acc := range res.Accounts {
var account types.AccountI
err := suite.app.InterfaceRegistry().UnpackAny(acc, &account)
suite.Require().NoError(err)

suite.Require().True(
first.Equals(account.GetAddress()) || second.Equals(account.GetAddress()))
}
},
},
}

for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset

tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)

res, err := suite.queryClient.Accounts(ctx, req)

if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
} else {
suite.Require().Error(err)
suite.Require().Nil(res)
}

tc.posttests(res)
})
}
}

func (suite *KeeperTestSuite) TestGRPCQueryAccount() {
var (
req *types.QueryAccountRequest
Expand Down
Loading

0 comments on commit 1925123

Please sign in to comment.