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

Enable Fee Delegation #5207

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
abce938
Add docs
ethanfrey Oct 16, 2019
ea6fe7d
Add BasicFeeAllowance implementation
ethanfrey Oct 16, 2019
c46153c
Add expiration structs and complete basic fee
ethanfrey Oct 16, 2019
42c17f5
Add delegation messages, add validation logic
ethanfrey Oct 16, 2019
0c137cb
Add keeper and helper structs
ethanfrey Oct 16, 2019
67cb830
Add alias and handler to top level
ethanfrey Oct 16, 2019
f6ba9bc
Add delegation module
ethanfrey Oct 16, 2019
a002366
Add basic querier
ethanfrey Oct 16, 2019
15c6361
Add types tests
ethanfrey Oct 16, 2019
410d1f1
Add types tests
ethanfrey Oct 16, 2019
f65db09
More internal test coverage
ethanfrey Oct 16, 2019
e0f6327
Solid internal test coverage
ethanfrey Oct 16, 2019
93c9b7e
Expose Querier to top level module
ethanfrey Oct 16, 2019
7968441
Add FeeAccount to auth/types, like StdTx, SignDoc
ethanfrey Oct 16, 2019
7e82fb8
Fix all tests in x/auth
ethanfrey Oct 16, 2019
64d5159
All tests pass
ethanfrey Oct 16, 2019
8b2b64b
Appease the Golang Linter
ethanfrey Oct 16, 2019
4e22edd
Merge remote-tracking branch 'origin/master' into cg-key-management/f…
ethanfrey Oct 16, 2019
be12b44
Add fee-account command line flag
ethanfrey Oct 16, 2019
c703e59
Start on DelegatedDeductFeeDecorator
ethanfrey Oct 17, 2019
907776f
Cleanup the Decorator
ethanfrey Oct 17, 2019
005afd9
Wire up delegation module in simapp
ethanfrey Oct 17, 2019
45c64d2
add basic test for decorator (no delegation)
ethanfrey Oct 17, 2019
87fd0ce
Table tests for deduct fees
ethanfrey Oct 17, 2019
d03c650
Table tests over all conditions of delegated fee decorator
ethanfrey Oct 17, 2019
e476c03
Build full ante handler stack and test it
ethanfrey Oct 17, 2019
182eb34
Start genesis
ethanfrey Oct 17, 2019
071e111
Implement Genesis
ethanfrey Oct 17, 2019
0a362b8
Merge branch 'master' into cg-key-management/fee-delegation-new
alexanderbez Oct 17, 2019
98b88a3
Rename package delegation to subkeys
ethanfrey Oct 18, 2019
376e9cd
Clarify antes test cases, handle empty account w/o fees
ethanfrey Oct 18, 2019
529764f
Allow paying delegated fees with no account
ethanfrey Oct 18, 2019
d293aae
Pull mempool into delegated ante, for control on StdFee
ethanfrey Oct 18, 2019
a8b3e31
Use custom DelegatedTx, DelegatedFee for subkeys
ethanfrey Oct 18, 2019
e939e98
Revert all changes to x/auth.StdTx
ethanfrey Oct 18, 2019
6aef5c8
Appease scopelint
ethanfrey Oct 18, 2019
1212772
Register DelegatedTx with codec
ethanfrey Oct 18, 2019
f688116
Merge branch 'master' into cg-key-management/fee-delegation-new
alexanderbez Oct 18, 2019
61df0c6
Address PR comments
ethanfrey Oct 21, 2019
d684f92
Remove unnecessary DelegatedMempoolFeeDecorator
ethanfrey Oct 21, 2019
aacf6fb
Cleaned up errors in querier
ethanfrey Oct 21, 2019
92f64cc
Clean up message sign bytes
ethanfrey Oct 21, 2019
4daf4af
Minor PR comments
ethanfrey Oct 24, 2019
a497575
Replace GetAllFees... with Iterator variants
ethanfrey Oct 24, 2019
73eab92
PrepareForExport adjusts grant expiration height
ethanfrey Oct 24, 2019
546fa8f
Panic on de/serialization error in keeper
ethanfrey Oct 24, 2019
5f9e391
Move custom ante handler chain to tests, update docs
ethanfrey Oct 24, 2019
c45dc71
More cleanup
ethanfrey Oct 24, 2019
2be48ef
More doc cleanup
ethanfrey Oct 24, 2019
e8a624f
Renamed subkeys module to fee_grant
ethanfrey Oct 24, 2019
6756099
Rename subkeys/delegation to fee grant in all strings
ethanfrey Oct 24, 2019
320bad4
Modify Msg and Keeper methods to use Grant not Delegate
ethanfrey Oct 24, 2019
a625fd0
Merge remote-tracking branch 'origin/master' into cg-key-management/f…
ethanfrey Oct 24, 2019
4b4391b
Add PeriodicFeeAllowance
ethanfrey Oct 29, 2019
9ec284b
Update aliases
ethanfrey Oct 29, 2019
4b34cf6
Cover all accept cases for PeriodicFeeAllowance
ethanfrey Oct 29, 2019
78f1f7a
Et tu scopelint?
ethanfrey Oct 29, 2019
a1951b4
Update docs as requested
ethanfrey Nov 7, 2019
17c39ca
Remove error return from GetFeeGrant
ethanfrey Nov 7, 2019
9c399bb
Code cleanup as requested by PR
ethanfrey Nov 7, 2019
6b9e06e
Merge remote-tracking branch 'origin/master' into cg-key-management/f…
ethanfrey Nov 7, 2019
21b0ac5
Updated all errors to use new sdk/errors package
ethanfrey Nov 7, 2019
2222d0e
Use test suite for keeper tests
ethanfrey Nov 7, 2019
eda42d4
Clean up alias.go file
ethanfrey Nov 7, 2019
a56b472
Define expected interfaces in exported, rather than importing from ac…
ethanfrey Nov 7, 2019
be29328
Remove dependency on auth/ante
ethanfrey Nov 7, 2019
786bf01
Improve godoc, Logger
ethanfrey Nov 7, 2019
5f2bcb5
Cleaned up ExpiresAt
ethanfrey Nov 7, 2019
314b099
Improve error reporting with UseGrantedFee
ethanfrey Nov 7, 2019
29958e0
Enforce period limit subset of basic limit
ethanfrey Nov 7, 2019
fb5b97a
Add events
ethanfrey Nov 7, 2019
eedb8d8
Rename fee_grant to feegrant
ethanfrey Nov 7, 2019
88df255
Ensure KeeperTestSuite actually runs
ethanfrey Nov 7, 2019
dbf3578
Move types/tx to types
ethanfrey Nov 7, 2019
375f114
Update alias file, include ante
ethanfrey Nov 7, 2019
9af9f43
I do need nolint in alias.go
ethanfrey Nov 7, 2019
3c390e5
Properly emit events in the handler. Use cosmos-sdk in amino types
ethanfrey Nov 7, 2019
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: 2 additions & 0 deletions client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
FlagRPCWriteTimeout = "write-timeout"
FlagOutputDocument = "output-document" // inspired by wget -O
FlagSkipConfirmation = "yes"
FlagFeeAccount = "fee-account"
)

// LineBreak can be included in a command list to provide a blank line
Expand Down Expand Up @@ -99,6 +100,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")
c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible and the node operates offline)")
c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation")
c.Flags().String(FlagFeeAccount, "", "Set a fee account to pay fess with if they have been delegated by this account")

// --gas can accept integers and "simulate"
c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf(
Expand Down
10 changes: 9 additions & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/feegrant"
"github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
Expand Down Expand Up @@ -55,6 +56,7 @@ var (
params.AppModuleBasic{},
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
feegrant.AppModuleBasic{},
evidence.AppModuleBasic{},
)

Expand Down Expand Up @@ -105,6 +107,7 @@ type SimApp struct {
DistrKeeper distr.Keeper
GovKeeper gov.Keeper
CrisisKeeper crisis.Keeper
FeeGrantKeeper feegrant.Keeper
ParamsKeeper params.Keeper
EvidenceKeeper evidence.Keeper

Expand All @@ -130,6 +133,7 @@ func NewSimApp(
keys := sdk.NewKVStoreKeys(
bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey,
distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey,
feegrant.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)

Expand Down Expand Up @@ -182,6 +186,9 @@ func NewSimApp(
app.CrisisKeeper = crisis.NewKeeper(
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
)
app.FeeGrantKeeper = feegrant.NewKeeper(
app.cdc, keys[feegrant.StoreKey],
)

// create evidence keeper with router
evidenceKeeper := evidence.NewKeeper(
Expand Down Expand Up @@ -221,6 +228,7 @@ func NewSimApp(
distr.NewAppModule(app.DistrKeeper, app.SupplyKeeper),
slashing.NewAppModule(app.SlashingKeeper, app.StakingKeeper),
staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.SupplyKeeper),
feegrant.NewAppModule(app.FeeGrantKeeper),
evidence.NewAppModule(app.EvidenceKeeper),
)

Expand All @@ -235,7 +243,7 @@ func NewSimApp(
app.mm.SetOrderInitGenesis(
auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
crisis.ModuleName, genutil.ModuleName, evidence.ModuleName,
crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, feegrant.ModuleName,
)

app.mm.RegisterInvariants(&app.CrisisKeeper)
Expand Down
1 change: 0 additions & 1 deletion types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ func ConvertError(err error) Error {
if sdkError, ok := err.(Error); ok {
return sdkError
}

space, code, log := sdkerrors.ABCIInfo(err, false)
return NewError(CodespaceType(space), CodeType(code), log)
}
Expand Down
73 changes: 73 additions & 0 deletions x/feegrant/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// nolint
package feegrant

import (
"github.com/cosmos/cosmos-sdk/x/feegrant/internal/ante"
"github.com/cosmos/cosmos-sdk/x/feegrant/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/internal/types"
)

// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/cosmos/cosmos-sdk/x/feegrant/internal/ante
// ALIASGEN: github.com/cosmos/cosmos-sdk/x/feegrant/internal/types
// ALIASGEN: github.com/cosmos/cosmos-sdk/x/feegrant/internal/keeper

const (
DefaultCodespace = types.DefaultCodespace
EventTypeUseFeeGrant = types.EventTypeUseFeeGrant
EventTypeRevokeFeeGrant = types.EventTypeRevokeFeeGrant
EventTypeSetFeeGrant = types.EventTypeSetFeeGrant
AttributeKeyGranter = types.AttributeKeyGranter
AttributeKeyGrantee = types.AttributeKeyGrantee
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute
QueryGetFeeAllowances = keeper.QueryGetFeeAllowances
)

var (
// functions aliases
NewDeductGrantedFeeDecorator = ante.NewDeductGrantedFeeDecorator
DeductFees = ante.DeductFees
RegisterCodec = types.RegisterCodec
ExpiresAtTime = types.ExpiresAtTime
ExpiresAtHeight = types.ExpiresAtHeight
ClockDuration = types.ClockDuration
BlockDuration = types.BlockDuration
FeeAllowanceKey = types.FeeAllowanceKey
FeeAllowancePrefixByGrantee = types.FeeAllowancePrefixByGrantee
NewMsgGrantFeeAllowance = types.NewMsgGrantFeeAllowance
NewMsgRevokeFeeAllowance = types.NewMsgRevokeFeeAllowance
NewFeeGrantTx = types.NewFeeGrantTx
CountSubKeys = types.CountSubKeys
NewGrantedFee = types.NewGrantedFee
StdSignBytes = types.StdSignBytes
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier

// variable aliases
ModuleCdc = types.ModuleCdc
ErrFeeLimitExceeded = types.ErrFeeLimitExceeded
ErrFeeLimitExpired = types.ErrFeeLimitExpired
ErrInvalidDuration = types.ErrInvalidDuration
ErrNoAllowance = types.ErrNoAllowance
FeeAllowanceKeyPrefix = types.FeeAllowanceKeyPrefix
)

type (
GrantedFeeTx = ante.GrantedFeeTx
DeductGrantedFeeDecorator = ante.DeductGrantedFeeDecorator
BasicFeeAllowance = types.BasicFeeAllowance
ExpiresAt = types.ExpiresAt
Duration = types.Duration
FeeAllowanceGrant = types.FeeAllowanceGrant
MsgGrantFeeAllowance = types.MsgGrantFeeAllowance
MsgRevokeFeeAllowance = types.MsgRevokeFeeAllowance
PeriodicFeeAllowance = types.PeriodicFeeAllowance
FeeGrantTx = types.FeeGrantTx
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
GrantedFee = types.GrantedFee
DelegatedSignDoc = types.DelegatedSignDoc
Keeper = keeper.Keeper
)
28 changes: 28 additions & 0 deletions x/feegrant/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Package feegrant provides functionality for delegating the payment of transaction fees
from one account (key) to another account (key).

Effectively, this allows for a user to pay fees using the balance of an account
different from their own. Example use cases would be allowing a key on a device to
pay for fees using a master wallet, or a third party service allowing users to
pay for transactions without ever really holding their own tokens. This package
provides ways for specifying fee allowances such that delegating fees
to another account can be done with clear and safe restrictions.

A user would delegate fees to a user using MsgDelegateFeeAllowance and revoke
Copy link
Contributor

@jaekwon jaekwon Nov 7, 2019

Choose a reason for hiding this comment

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

Seems like "delegate" is used inconsistently here. It should be a unidirection relationship but it appears to be used bidirecitonally?

A tx signer, say Alice, might "delegate" the responsibility of paying fees to someone else, say Bob.
But I wouldn't say that Bob in this case is delegating anything to Alice.

So I'd call this MsgAddFeeAllowance. or MsgRegisteerFeeAllowance.

Copy link
Member

Choose a reason for hiding this comment

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

I've been advocating for the language of grant and revoke, so how about MsgGrantFeeAllowance?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, that is the name of the actual struct. Looks like the docs just need to be updated...

that delegation using MsgRevokeFeeAllowance. In both cases Granter is the one
who is delegating fees and Grantee is the one who is receiving the delegation.
So grantee would correspond to the one who is signing a transaction and the
granter would be the address they place in DelegatedFee.FeeAccount.

The fee allowance that a grantee receives is specified by an implementation of
the FeeAllowance interface. Two FeeAllowance implementations are provided in
this package: BasicFeeAllowance and PeriodicFeeAllowance.

In order to integrate this into an application, we must use the DeductDelegatedFeeDecorator
ante handler from this package instead of the default DeductFeeDecorator from auth.
To allow handling txs from empty accounts (with fees paid from an existing account),
we have to re-order the decorators as well. You can see an example in
`x/delegate_fees/internal/ante/fee_test.go:newAnteHandler()`
*/
package feegrant
21 changes: 21 additions & 0 deletions x/feegrant/exported/external.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package exported

import (
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth/exported"
supply "github.com/cosmos/cosmos-sdk/x/supply/exported"
)

// SupplyKeeper defines the expected supply Keeper (noalias)
type SupplyKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error
GetModuleAccount(ctx sdk.Context, moduleName string) supply.ModuleAccountI
GetModuleAddress(moduleName string) sdk.AccAddress
}

// SupplyKeeper defines the expected auth Account Keeper (noalias)
type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) auth.Account
GetAccount(ctx sdk.Context, addr sdk.AccAddress) auth.Account
SetAccount(ctx sdk.Context, acc auth.Account)
}
32 changes: 32 additions & 0 deletions x/feegrant/exported/fees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package exported

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// FeeAllowance implementations are tied to a given fee delegator and delegatee,
// and are used to enforce fee grant limits.
type FeeAllowance interface {
// Accept can use fee payment requested as well as timestamp/height of the current block
// to determine whether or not to process this. This is checked in
// Keeper.UseGrantedFees and the return values should match how it is handled there.
//
// If it returns an error, the fee payment is rejected, otherwise it is accepted.
// The FeeAllowance implementation is expected to update it's internal state
// and will be saved again after an acceptance.
//
// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (remove bool, err error)

// If we export fee allowances the timing info will be quite off (eg. go from height 100000 to 0)
// This callback allows the fee-allowance to change it's state and return a copy that is adjusted
// given the time and height of the actual dump (may safely return self if no changes needed)
PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowance

// ValidateBasic should evaluate this FeeAllowance for internal consistency.
// Don't allow negative amounts, or negative periods for example.
ValidateBasic() error
}
41 changes: 41 additions & 0 deletions x/feegrant/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package feegrant

import sdk "github.com/cosmos/cosmos-sdk/types"

// GenesisState contains a set of fee allowances, persisted from the store
type GenesisState []FeeAllowanceGrant

// ValidateBasic ensures all grants in the genesis state are valid
func (g GenesisState) ValidateBasic() error {
for _, f := range g {
err := f.ValidateBasic()
if err != nil {
return err
}
}
return nil
}

// InitGenesis will initialize the keeper from a *previously validated* GenesisState
func InitGenesis(ctx sdk.Context, k Keeper, gen GenesisState) {
for _, f := range gen {
k.GrantFeeAllowance(ctx, f)
}
}

// ExportGenesis will dump the contents of the keeper into a serializable GenesisState
//
// All expiration heights will be thrown off if we dump state and start at a new
// chain at height 0. Thus, we allow the Allowances to "prepare themselves"
// for export, like if they have expiry at 5000 and current is 4000, they export with
// expiry of 1000. Every FeeAllowance has a method `PrepareForExport` that allows
// them to perform any changes needed prior to export.
func ExportGenesis(ctx sdk.Context, k Keeper) (GenesisState, error) {
time, height := ctx.BlockTime(), ctx.BlockHeight()
var grants []FeeAllowanceGrant
err := k.IterateAllFeeAllowances(ctx, func(grant FeeAllowanceGrant) bool {
grants = append(grants, grant.PrepareForExport(time, height))
return false
})
return grants, err
}
36 changes: 36 additions & 0 deletions x/feegrant/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package feegrant

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
ctx = ctx.WithEventManager(sdk.NewEventManager())

switch msg := msg.(type) {
case MsgGrantFeeAllowance:
return handleGrantFee(ctx, k, msg)

case MsgRevokeFeeAllowance:
return handleRevokeFee(ctx, k, msg)

default:
errMsg := fmt.Sprintf("Unrecognized data Msg type: %s", ModuleName)
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}

func handleGrantFee(ctx sdk.Context, k Keeper, msg MsgGrantFeeAllowance) sdk.Result {
grant := FeeAllowanceGrant(msg)
k.GrantFeeAllowance(ctx, grant)
return sdk.Result{Events: ctx.EventManager().Events()}
}

func handleRevokeFee(ctx sdk.Context, k Keeper, msg MsgRevokeFeeAllowance) sdk.Result {
k.RevokeFeeAllowance(ctx, msg.Granter, msg.Grantee)
return sdk.Result{Events: ctx.EventManager().Events()}
}
Loading