github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-048-consensus-fees.md (about)

     1  # ADR 048: Multi Tire Gas Price System
     2  
     3  ## Changelog
     4  
     5  * Dec 1, 2021: Initial Draft
     6  
     7  ## Status
     8  
     9  Rejected
    10  
    11  ## Abstract
    12  
    13  This ADR describes a flexible mechanism to maintain a consensus level gas prices, in which one can choose a multi-tier gas price system or EIP-1559 like one through configuration.
    14  
    15  ## Context
    16  
    17  Currently, each validator configures it's own `minimal-gas-prices` in `app.yaml`. But setting a proper minimal gas price is critical to protect network from dos attack, and it's hard for all the validators to pick a sensible value, so we propose to maintain a gas price in consensus level.
    18  
    19  Since tendermint 0.34.20 has supported mempool prioritization, we can take advantage of that to implement more sophisticated gas fee system.
    20  
    21  ## Multi-Tier Price System
    22  
    23  We propose a multi-tier price system on consensus to provide maximum flexibility:
    24  
    25  * Tier 1: a constant gas price, which could only be modified occasionally through governance proposal.
    26  * Tier 2: a dynamic gas price which is adjusted according to previous block load.
    27  * Tier 3: a dynamic gas price which is adjusted according to previous block load at a higher speed.
    28  
    29  The gas price of higher tier should bigger than the lower tier.
    30  
    31  The transaction fees are charged with the exact gas price calculated on consensus.
    32  
    33  The parameter schema is like this:
    34  
    35  ```protobuf
    36  message TierParams {
    37    uint32 priority = 1           // priority in tendermint mempool
    38    Coin initial_gas_price = 2    //
    39    uint32 parent_gas_target = 3  // the target saturation of block
    40    uint32 change_denominator = 4 // decides the change speed
    41    Coin min_gas_price = 5        // optional lower bound of the price adjustment
    42    Coin max_gas_price = 6        // optional upper bound of the price adjustment
    43  }
    44  
    45  message Params {
    46    repeated TierParams tiers = 1;
    47  }
    48  ```
    49  
    50  ### Extension Options
    51  
    52  We need to allow user to specify the tier of service for the transaction, to support it in an extensible way, we add an extension option in `AuthInfo`:
    53  
    54  ```protobuf
    55  message ExtensionOptionsTieredTx {
    56    uint32 fee_tier = 1
    57  }
    58  ```
    59  
    60  The value of `fee_tier` is just the index to the `tiers` parameter list.
    61  
    62  We also change the semantic of existing `fee` field of `Tx`, instead of charging user the exact `fee` amount, we treat it as a fee cap, while the actual amount of fee charged is decided dynamically. If the `fee` is smaller than dynamic one, the transaction won't be included in current block and ideally should stay in the mempool until the consensus gas price drop. The mempool can eventually prune old transactions.
    63  
    64  ### Tx Prioritization
    65  
    66  Transactions are prioritized based on the tier, the higher the tier, the higher the priority.
    67  
    68  Within the same tier, follow the default Tendermint order (currently FIFO). Be aware of that the mempool tx ordering logic is not part of consensus and can be modified by malicious validator.
    69  
    70  This mechanism can be easily composed with prioritization mechanisms:
    71  
    72  * we can add extra tiers out of a user control:
    73      * Example 1: user can set tier 0, 10 or 20, but the protocol will create tiers 0, 1, 2 ... 29. For example IBC transactions will go to tier `user_tier + 5`: if user selected tier 1, then the transaction will go to tier 15.
    74      * Example 2: we can reserve tier 4, 5, ... only for special transaction types. For example, tier 5 is reserved for evidence tx. So if submits a bank.Send transaction and set tier 5, it will be delegated to tier 3 (the max tier level available for any transaction). 
    75      * Example 3: we can enforce that all transactions of a sepecific type will go to specific tier. For example, tier 100 will be reserved for evidence transactions and all evidence transactions will always go to that tier.
    76  
    77  ### `min-gas-prices`
    78  
    79  Deprecate the current per-validator `min-gas-prices` configuration, since it would confusing for it to work together with the consensus gas price.
    80  
    81  ### Adjust For Block Load
    82  
    83  For tier 2 and tier 3 transactions, the gas price is adjusted according to previous block load, the logic could be similar to EIP-1559:
    84  
    85  ```python
    86  def adjust_gas_price(gas_price, parent_gas_used, tier):
    87    if parent_gas_used == tier.parent_gas_target:
    88      return gas_price
    89    elif parent_gas_used > tier.parent_gas_target:
    90      gas_used_delta = parent_gas_used - tier.parent_gas_target
    91      gas_price_delta = max(gas_price * gas_used_delta // tier.parent_gas_target // tier.change_speed, 1)
    92      return gas_price + gas_price_delta
    93    else:
    94      gas_used_delta = parent_gas_target - parent_gas_used
    95      gas_price_delta = gas_price * gas_used_delta // parent_gas_target // tier.change_speed
    96      return gas_price - gas_price_delta
    97  ```
    98  
    99  ### Block Segment Reservation
   100  
   101  Ideally we should reserve block segments for each tier, so the lower tiered transactions won't be completely squeezed out by higher tier transactions, which will force user to use higher tier, and the system degraded to a single tier.
   102  
   103  We need help from tendermint to implement this.
   104  
   105  ## Implementation
   106  
   107  We can make each tier's gas price strategy fully configurable in protocol parameters, while providing a sensible default one.
   108  
   109  Pseudocode in python-like syntax:
   110  
   111  ```python
   112  interface TieredTx:
   113    def tier(self) -> int:
   114      pass
   115  
   116  def tx_tier(tx):
   117      if isinstance(tx, TieredTx):
   118        return tx.tier()
   119      else:
   120        # default tier for custom transactions
   121        return 0
   122      # NOTE: we can add more rules here per "Tx Prioritization" section 
   123  
   124  class TierParams:
   125    'gas price strategy parameters of one tier'
   126    priority: int           # priority in tendermint mempool
   127    initial_gas_price: Coin
   128    parent_gas_target: int
   129    change_speed: Decimal   # 0 means don't adjust for block load.
   130  
   131  class Params:
   132      'protocol parameters'
   133      tiers: List[TierParams]
   134  
   135  class State:
   136      'consensus state'
   137      # total gas used in last block, None when it's the first block
   138      parent_gas_used: Optional[int]
   139      # gas prices of last block for all tiers
   140      gas_prices: List[Coin]
   141  
   142  def begin_block():
   143      'Adjust gas prices'
   144      for i, tier in enumerate(Params.tiers):
   145          if State.parent_gas_used is None:
   146              # initialized gas price for the first block
   147  	          State.gas_prices[i] = tier.initial_gas_price
   148          else:
   149              # adjust gas price according to gas used in previous block
   150              State.gas_prices[i] = adjust_gas_price(State.gas_prices[i], State.parent_gas_used, tier)
   151  
   152  def mempoolFeeTxHandler_checkTx(ctx, tx):
   153      # the minimal-gas-price configured by validator, zero in deliver_tx context
   154      validator_price = ctx.MinGasPrice()
   155      consensus_price = State.gas_prices[tx_tier(tx)]
   156      min_price = max(validator_price, consensus_price)
   157  
   158      # zero means infinity for gas price cap
   159      if tx.gas_price() > 0 and tx.gas_price() < min_price:
   160          return 'insufficient fees'
   161      return next_CheckTx(ctx, tx)
   162  
   163  def txPriorityHandler_checkTx(ctx, tx):
   164      res, err := next_CheckTx(ctx, tx)
   165      # pass priority to tendermint
   166      res.Priority = Params.tiers[tx_tier(tx)].priority
   167      return res, err
   168  
   169  def end_block():
   170      'Update block gas used'
   171      State.parent_gas_used = block_gas_meter.consumed()
   172  ```
   173  
   174  ### Dos attack protection
   175  
   176  To fully saturate the blocks and prevent other transactions from executing, attacker need to use transactions of highest tier, the cost would be significantly higher than the default tier.
   177  
   178  If attacker spam with lower tier transactions, user can mitigate by sending higher tier transactions.
   179  
   180  ## Consequences
   181  
   182  ### Backwards Compatibility
   183  
   184  * New protocol parameters.
   185  * New consensus states.
   186  * New/changed fields in transaction body.
   187  
   188  ### Positive
   189  
   190  * The default tier keeps the same predictable gas price experience for client.
   191  * The higher tier's gas price can adapt to block load.
   192  * No priority conflict with custom priority based on transaction types, since this proposal only occupy three priority levels.
   193  * Possibility to compose different priority rules with tiers
   194  
   195  ### Negative
   196  
   197  * Wallets & tools need to update to support the new `tier` parameter, and semantic of `fee` field is changed.
   198  
   199  ### Neutral
   200  
   201  ## References
   202  
   203  * https://eips.ethereum.org/EIPS/eip-1559
   204  * https://iohk.io/en/blog/posts/2021/11/26/network-traffic-and-tiered-pricing/