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

feat: add feegrant query to see allowances from a given granter #10947

Merged
merged 7 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10507](https://github.com/cosmos/cosmos-sdk/pull/10507) Add middleware for tx priority.
* [\#10311](https://github.com/cosmos/cosmos-sdk/pull/10311) Adds cli to use tips transactions. It adds an `--aux` flag to all CLI tx commands to generate the aux signer data (with optional tip), and a new `tx aux-to-fee` subcommand to let the fee payer gather aux signer data and broadcast the tx
* [\#10430](https://github.com/cosmos/cosmos-sdk/pull/10430) ADR-040: Add store/v2 `MultiStore` implementation
* [\#10947](https://github.com/cosmos/cosmos-sdk/pull/10947) Add `AllowancesByGranter` query to the feegrant module

### API Breaking Changes

Expand Down
1,341 changes: 1,284 additions & 57 deletions api/cosmos/feegrant/v1beta1/query.pulsar.go

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions api/cosmos/feegrant/v1beta1/query_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions proto/cosmos/feegrant/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ service Query {
rpc Allowances(QueryAllowancesRequest) returns (QueryAllowancesResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/allowances/{grantee}";
}

// AllowancesByGranter returns all the grants given by an address
// Since v0.46
rpc AllowancesByGranter(QueryAllowancesByGranterRequest) returns (QueryAllowancesByGranterResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/issued/{granter}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like the IssuedAllowances name. It's sounds confusing with Allowances.

Maybe AllowancesByGranter to be more explicit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to change Allowances to AllowancesByGrantee or leave it as is?

Copy link
Contributor

@amaury1093 amaury1093 Jan 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be proto-breaking, so no, though it would be the ideal choice.

I still prefer Allowances+AllowancesByGranter over Allowances+IssuedAllowances

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will make the change

}
}

// QueryAllowanceRequest is the request type for the Query/Allowance RPC method.
Expand Down Expand Up @@ -54,3 +60,20 @@ message QueryAllowancesResponse {
// pagination defines an pagination for the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryAllowancesByGranterRequest is the request type for the Query/AllowancesByGranter RPC method.
message QueryAllowancesByGranterRequest {
string granter = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

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

// QueryAllowancesByGranterResponse is the response type for the Query/AllowancesByGranter RPC method.
message QueryAllowancesByGranterResponse {
// allowances that have been issued by the granter.
repeated cosmos.feegrant.v1beta1.Grant allowances = 1;

// pagination defines an pagination for the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
56 changes: 53 additions & 3 deletions x/feegrant/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func GetQueryCmd() *cobra.Command {

feegrantQueryCmd.AddCommand(
GetCmdQueryFeeGrant(),
GetCmdQueryFeeGrants(),
GetCmdQueryFeeGrantsByGrantee(),
GetCmdQueryFeeGrantsByGranter(),
)

return feegrantQueryCmd
Expand Down Expand Up @@ -80,8 +81,8 @@ $ %s query feegrant grant [granter] [grantee]
return cmd
}

// GetCmdQueryFeeGrants returns cmd to query for all grants for a grantee.
func GetCmdQueryFeeGrants() *cobra.Command {
// GetCmdQueryFeeGrantsByGrantee returns cmd to query for all grants for a grantee.
func GetCmdQueryFeeGrantsByGrantee() *cobra.Command {
cmd := &cobra.Command{
Use: "grants [grantee]",
Args: cobra.ExactArgs(1),
Expand Down Expand Up @@ -128,3 +129,52 @@ $ %s query feegrant grants [grantee]

return cmd
}

// GetCmdQueryFeeGrantsByGranter returns cmd to query for all grants by a granter.
func GetCmdQueryFeeGrantsByGranter() *cobra.Command {
cmd := &cobra.Command{
Use: "grants [granter]",
Args: cobra.ExactArgs(1),
Short: "Query all grants by a granter",
Long: strings.TrimSpace(
fmt.Sprintf(`Queries all the grants issued for a granter address.

Example:
$ %s query feegrant grants [granter]
`, version.AppName),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := feegrant.NewQueryClient(clientCtx)

granterAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}

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

res, err := queryClient.AllowancesByGranter(
cmd.Context(),
&feegrant.QueryAllowancesByGranterRequest{
Granter: granterAddr.String(),
Pagination: pageReq,
},
)

if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "grants")

return cmd
}
1 change: 1 addition & 0 deletions x/feegrant/client/testutil/cli_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build norace
// +build norace

package testutil
Expand Down
62 changes: 59 additions & 3 deletions x/feegrant/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrant() {
}
}

func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGrantee() {
val := s.network.Validators[0]
grantee := s.addedGrantee
clientCtx := val.ClientCtx
Expand All @@ -219,7 +219,7 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
true, nil, 0,
},
{
"non existed grantee",
"non existent grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
Expand All @@ -240,7 +240,63 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrants()
cmd := cli.GetCmdQueryFeeGrantsByGrantee()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add integration tests for GetCmdQueryFeeGrantsByGranter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍


if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String())
s.Require().Len(tc.resp.Allowances, tc.expectLength)
}
})
}
}

func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGranter() {
val := s.network.Validators[0]
granter := s.addedGranter
clientCtx := val.ClientCtx

testCases := []struct {
name string
args []string
expectErr bool
resp *feegrant.QueryAllowancesByGranterResponse
expectLength int
}{
{
"wrong grantee",
[]string{
"wrong_grantee",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, nil, 0,
},
{
"non existent grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &feegrant.QueryAllowancesByGranterResponse{}, 0,
},
{
"valid req",
[]string{
granter.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &feegrant.QueryAllowancesByGranterResponse{}, 1,
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrantsByGranter()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)

if tc.expectErr {
Expand Down
39 changes: 39 additions & 0 deletions x/feegrant/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,42 @@ func (q Keeper) Allowances(c context.Context, req *feegrant.QueryAllowancesReque

return &feegrant.QueryAllowancesResponse{Allowances: grants, Pagination: pageRes}, nil
}

// AllowancesByGranter queries all the allowances granted by the given granter
func (q Keeper) AllowancesByGranter(c context.Context, req *feegrant.QueryAllowancesByGranterRequest) (*feegrant.QueryAllowancesByGranterResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

granterAddr, err := sdk.AccAddressFromBech32(req.Granter)
if err != nil {
return nil, err
}

ctx := sdk.UnwrapSDKContext(c)

var grants []*feegrant.Grant

store := ctx.KVStore(q.storeKey)
pageRes, err := query.Paginate(store, req.Pagination, func(key []byte, value []byte) error {
var grant feegrant.Grant

granter, _ := feegrant.ParseAddressesFromFeeAllowanceKey(key)
if !granter.Equals(granterAddr) {
return nil
}

if err := q.cdc.Unmarshal(value, &grant); err != nil {
return err
}

grants = append(grants, &grant)
return nil
})
Comment on lines +113 to +127
Copy link
Contributor

@atheeshp atheeshp Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think iterating all the store keys might not be a better idea (it can be possible that this store can be big).
cc: @aaronc , @AmauryM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the more efficient solution would be to have a secondary index and I think in the future we may want to have the feegrant module use orm. For now I think this suffices due to the following reasons:

  • The range scan is only parsing the keys and not the values to determine whether the allowance comes from the granter
  • I can't imagine chains having a state size greater than a few hundred entries
  • This is just a query endpoint (only affects individual nodes) and can be turned off from the public if need be.
  • Most use cases will be for individuals checking grants on their own machines. Most "commercial" level products will be using something like a postgres indexer for serving feegrant information


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

return &feegrant.QueryAllowancesByGranterResponse{Allowances: grants, Pagination: pageRes}, nil
}
Loading