From 90579c0a99de4c17b108289b24913cb271c65c7f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 9 Oct 2020 11:09:17 -0400 Subject: [PATCH] ADR 029: Fee Grant Module (#7106) * Add ADR 029 stub * ADR 029 first draft * Cleanup * Updates from code review * Updates from review Co-authored-by: Alessio Treglia --- docs/architecture/README.md | 1 + docs/architecture/adr-029-fee-grant-module.md | 162 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 docs/architecture/adr-029-fee-grant-module.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index b60a1b38a..2e17fb394 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -54,3 +54,4 @@ Please add a entry below in your Pull Request for an ADR. - [ADR 025: IBC Passive Channels](./adr-025-ibc-passive-channels.md) - [ADR 026: IBC Client Recovery Mechanisms](./adr-026-ibc-client-recovery-mechanisms.md) - [ADR 027: Deterministic Protobuf Serialization](./adr-027-deterministic-protobuf-serialization.md) +- [ADR 029: Fee Grant Module](./adr-029-fee-grant-module.md) \ No newline at end of file diff --git a/docs/architecture/adr-029-fee-grant-module.md b/docs/architecture/adr-029-fee-grant-module.md new file mode 100644 index 000000000..8ef0f722f --- /dev/null +++ b/docs/architecture/adr-029-fee-grant-module.md @@ -0,0 +1,162 @@ +# ADR 029: Fee Grant Module + +## Changelog + +- 2020/08/18: Initial Draft + +## Status + +Accepted + +## Context + +In order to make blockchain transactions, the signing account must possess a sufficient balance of the right denomination +in order to pay fees. There are classes of transactions where needing to maintain a wallet with sufficient fees is a +barrier to adoption. + +For instance, when proper permissions are setup, someone may temporarily delegate the ability to vote on proposals to +a "burner" account that is stored on a mobile phone with only minimal security. + +Other use cases include workers tracking items in a supply chain or farmers submitting field data for analytics +or compliance purposes. + +For all of these use cases, UX would be significantly enhanced by obviating the need for these accounts to always +maintain the appropriate fee balance. This is especially true if we wanted to achieve enterprise adoption for something +like supply chain tracking. + +While one solution would be to have a service that fills up these accounts automatically with the appropriate fees, a better UX +would be provided by allowing these accounts to pull from a common fee pool account with proper spending limits. +A single pool would reduce the churn of making lots of small "fill up" transactions and also more effectively leverages +the resources of the organization setting up the pool. + +## Decision + +As a solution we propose a module, `x/feegrant` which allows one account, the "granter" to grant another account, the "grantee" +an allowance to spend the granter's account balance for fees within certain well-defined limits. + +Fee allowances are defined by the extensible `FeeAllowanceI` interface: + +```go +type FeeAllowanceI { + // 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) +} +``` + +Two basic fee allowance types, `BasicFeeAllowance` and `PeriodicFeeAllowance` are defined to support known use cases: + +```proto +// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens +// that optionally expires. The delegatee can use up to SpendLimit to cover fees. +message BasicFeeAllowance { + // spend_limit specifies the maximum amount of tokens that can be spent + // by this allowance and will be updated as tokens are spent. If it is + // empty, there is no spend limit and any amount of coins can be spent. + repeated cosmos_sdk.v1.Coin spend_limit = 1; + + // expires_at specifies an optional time when this allowance expires + ExpiresAt expiration = 2; +} + +// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap, +// as well as a limit per time period. +message PeriodicFeeAllowance { + BasicFeeAllowance basic = 1; + + // period specifies the time duration in which period_spend_limit coins can + // be spent before that allowance is reset + Duration period = 2; + + // period_spend_limit specifies the maximum number of coins that can be spent + // in the period + repeated cosmos_sdk.v1.Coin period_spend_limit = 3; + + // period_can_spend is the number of coins left to be spent before the period_reset time + repeated cosmos_sdk.v1.Coin period_can_spend = 4; + + // period_reset is the time at which this period resets and a new one begins, + // it is calculated from the start time of the first transaction after the + // last period ended + ExpiresAt period_reset = 5; +} + +// ExpiresAt is a point in time where something expires. +// It may be *either* block time or block height +message ExpiresAt { + oneof sum { + google.protobuf.Timestamp time = 1; + uint64 height = 2; + } + } + +// Duration is a repeating unit of either clock time or number of blocks. +message Duration { + oneof sum { + google.protobuf.Duration duration = 1; + uint64 blocks = 2; + } +} + +``` + +Allowances can be granted and revoked using `MsgGrantFeeAllowance` and `MsgRevokeFeeAllowance`: + +```proto +message MsgGrantFeeAllowance { + string granter = 1; + string grantee = 2; + google.protobuf.Any allowance = 3; + } + + // MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee. + message MsgRevokeFeeAllowance { + string granter = 1; + string grantee = 2; + } +``` + +In order to use allowances in transactions, we add a new field `granter` to the transaction `Fee` type: +```proto +package cosmos.tx.v1beta1; + +message Fee { + repeated cosmos.base.v1beta1.Coin amount = 1; + uint64 gas_limit = 2; + string payer = 3; + string granter = 4; +} +``` + +`granter` must either be left empty or must correspond to an account which has granted +a fee allowance to fee payer (either the first signer or the value of the `payer` field). + +A new `AnteDecorator` named `DeductGrantedFeeDecorator` will be created in order to process transactions with `fee_payer` +set and correctly deduct fees based on fee allowances. + +## Consequences + +### Positive + +- improved UX for use cases where it is cumbersome to maintain an account balance just for fees + +### Negative + +### Neutral + +- a new field must be added to the transaction `Fee` message and a new `AnteDecorator` must be +created to use it + +## References + +- Blog article describing initial work: https://medium.com/regen-network/hacking-the-cosmos-cosmwasm-and-key-management-a08b9f561d1b +- Initial public specification: https://gist.github.com/aaronc/b60628017352df5983791cad30babe56 +- Original subkeys proposal from B-harvest which influenced this design: https://github.com/cosmos/cosmos-sdk/issues/4480