From e2024d3d8596cb0dfe0931f0e1ea7eb4907c5f37 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 2 Dec 2021 00:02:55 +0800 Subject: [PATCH 1/7] adr: ADR-048 Propose a multi-tier in-protocol gas price system Update docs/architecture/adr-048-consensus-fees.md Co-authored-by: Robert Zaremba updates - remove comparison - better explanation - update consequences update workings fix the gas price adjustment pseudo-code Update docs/architecture/adr-048-consensus-fees.md Co-authored-by: Aleksandr Bezobchuk review suggestions add parameters and extension options --- docs/architecture/adr-048-consensus-fees.md | 194 ++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 docs/architecture/adr-048-consensus-fees.md diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md new file mode 100644 index 000000000000..25d37a9b06ab --- /dev/null +++ b/docs/architecture/adr-048-consensus-fees.md @@ -0,0 +1,194 @@ +# ADR 048: Consensus Gas Price + +## Changelog + +- Dec 1, 2021: Initial Draft + +## Status + +Proposed + +## Abstract + +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. + +## Context + +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. + +Since tendermint 0.35 has supported mempool prioritization, we can take advantage of that to implement more sophisticated gas fee system. + +## Multi-Tier Price System + +We propose a multi-tier price system on consensus to provide maximum flexibility: + +- Tier 1: a constant gas price, which could only be modified occasionally through governance proposal. +- Tier 2: a dynamic gas price which is adjusted according to previous block load. +- Tier 3: a dynamic gas price which is adjusted according to previous block load at a higher speed. + +The gas price of higher tier should bigger than the lower tier. + +The transaction fees are charged with the exact gas price calculated on consensus. + +The parameter schema is like this: + +```protobuf +message TierParams { + uint32 priority = 1 // priority in tendermint mempool + Coin initial_gas_price = 2 // + uint32 parent_gas_target = 3 // the target saturation of block + uint32 change_denominator = 4 // decides the change speed + Coin min_gas_price = 5 // optional lower bound of the price adjustment + Coin max_gas_price = 6 // optional upper bound of the price adjustment +} + +message Params { + repeated TierParams tiers = 1; +} +``` + +### Extension Options + +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`: + +```protobuf +message ExtensionOptionsTieredTx { + uint32 tier = 1 +} +``` + +The value of `tier` is just the index to the `tiers` parameter list. + +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. + +### Tx Prioritization + +Transactions are prioritized based on the tier, the higher the tier, the higher the priority. + +Within the same tier/priority, the transactions are processed in FIFO manner. + +### `min-gas-prices` + +Deprecate the current per-validator `min-gas-prices` configuration, since it would confusing for it to work together with the consensus gas price. + +### Adjust For Block Load + +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: + +```python +def adjust_gas_price(gas_price, parent_gas_used, tier): + if parent_gas_used == tier.parent_gas_target: + return gas_price + elif parent_gas_used > tier.parent_gas_target: + gas_used_delta = parent_gas_used - tier.parent_gas_target + gas_price_delta = max(gas_price * gas_used_delta // tier.parent_gas_target // tier.change_speed, 1) + return gas_price + gas_price_delta + else: + gas_used_delta = parent_gas_target - parent_gas_used + gas_price_delta = gas_price * gas_used_delta // parent_gas_target // tier.change_speed + return gas_price - gas_price_delta +``` + +### Block Segment Reservation + +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. + +We need help from tendermint to implement that. (TODO Is the current proposed `ABCI++` spec support this usage?) + +## Implementation + +We can make each tier's gas price strategy fully configurable in protocol parameters, while providing a sensible default one. + +Pseudocode in python-like syntax: + +```python +interface TieredTx: + def tier(self) -> int: + pass + +def tx_tier(tx): + if isinstance(tx, TieredTx): + return tx.tier() + else: + # default tier for custom transactions + return 0 + +class TierParams: + 'gas price strategy parameters of one tier' + priority: int # priority in tendermint mempool + initial_gas_price: Coin + parent_gas_target: int + change_speed: Decimal # 0 means don't adjust for block load. + +class Params: + 'protocol parameters' + tiers: List[TierParams] + +class State: + 'consensus state' + # total gas used in last block, None when it's the first block + parent_gas_used: Optional[int] + # gas prices of last block for all tiers + gas_prices: List[Coin] + +def begin_block(): + 'Adjust gas prices' + for i, tier in enumerate(Params.tiers): + if State.parent_gas_used is None: + # initialized gas price for the first block + State.gas_prices[i] = tier.initial_gas_price + else: + # adjust gas price according to gas used in previous block + State.gas_prices[i] = adjust_gas_price(State.gas_prices[i], State.parent_gas_used, tier) + +def mempoolFeeTxHandler_checkTx(ctx, tx): + # the minimal-gas-price configured by validator, zero in deliver_tx context + validator_price = ctx.MinGasPrice() + consensus_price = State.gas_prices[tx_tier(tx)] + min_price = max(validator_price, consensus_price) + + if tx.gas_price() < min_price: + return 'insufficient fees' + return next_CheckTx(ctx, tx) + +def txPriorityHandler_checkTx(ctx, tx): + res, err := next_CheckTx(ctx, tx) + # pass priority to tendermint + res.Priority = Params.tiers[tx_tier(tx)].priority + return res, err + +def end_block(): + 'Update block gas used' + State.parent_gas_used = block_gas_meter.consumed() +``` + +### Dos attack protection + +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. + +If attacker spam with lower tier transactions, user can mitigate by sending higher tier transactions. + +## Consequences + +### Backwards Compatibility + +- New protocol parameters. +- New consensus states. +- New/changed fields in transaction body. + +### Positive + +- The default tier keeps the same predictable gas price experience for client. +- The higher tier's gas price can adapt to block load. +- No priority conflict with custom priority based on transaction types, since this proposal only occupy three priority levels. + +### Negative + +- Wallets & tools need to update to support the new `tier` parameter, and semantic changed `gasPriceCap` field. + +### Neutral + +## References + +- https://eips.ethereum.org/EIPS/eip-1559 +- https://iohk.io/en/blog/posts/2021/11/26/network-traffic-and-tiered-pricing/ \ No newline at end of file From ff773ae191c815822c2a80822832345d09739748 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 14 Mar 2022 23:45:18 +0800 Subject: [PATCH 2/7] minor updates --- docs/architecture/adr-048-consensus-fees.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 25d37a9b06ab..028023f13f8b 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -57,9 +57,9 @@ message ExtensionOptionsTieredTx { } ``` -The value of `tier` is just the index to the `tiers` parameter list. +The value of `tier` is just the index to the `tiers` parameter list. -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. +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. ### Tx Prioritization @@ -93,7 +93,7 @@ def adjust_gas_price(gas_price, parent_gas_used, tier): 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. -We need help from tendermint to implement that. (TODO Is the current proposed `ABCI++` spec support this usage?) +We need help from tendermint to implement this. ## Implementation @@ -184,7 +184,7 @@ If attacker spam with lower tier transactions, user can mitigate by sending high ### Negative -- Wallets & tools need to update to support the new `tier` parameter, and semantic changed `gasPriceCap` field. +- Wallets & tools need to update to support the new `tier` parameter, and semantic of `fee` field is changed. ### Neutral From e2638822f5de305e0b10bbc0330a55f472f754ad Mon Sep 17 00:00:00 2001 From: HuangYi Date: Tue, 15 Mar 2022 08:45:46 +0800 Subject: [PATCH 3/7] fix review suggestions --- docs/architecture/adr-048-consensus-fees.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 028023f13f8b..5b5a9ecd76bb 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -1,4 +1,4 @@ -# ADR 048: Consensus Gas Price +# ADR 048: Multi Tire Gas Price System ## Changelog @@ -53,11 +53,11 @@ We need to allow user to specify the tier of service for the transaction, to sup ```protobuf message ExtensionOptionsTieredTx { - uint32 tier = 1 + uint32 fee_tier = 1 } ``` -The value of `tier` is just the index to the `tiers` parameter list. +The value of `fee_tier` is just the index to the `tiers` parameter list. 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. From 712011f0d36c4d9ab6e4c142ead1315186dfabf8 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 17 Mar 2022 18:47:56 +0800 Subject: [PATCH 4/7] fix review suggestions --- docs/architecture/adr-048-consensus-fees.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 5b5a9ecd76bb..00d27a9f8d08 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -65,7 +65,7 @@ We also change the semantic of existing `fee` field of `Tx`, instead of charging Transactions are prioritized based on the tier, the higher the tier, the higher the priority. -Within the same tier/priority, the transactions are processed in FIFO manner. +Within the same tier/priority, the transactions are processed in FIFO manner which is the default behavior of tendermint. Be aware of that the mempool tx ordering logic is not part of consensus and can be modified by malicious validator. ### `min-gas-prices` @@ -147,7 +147,8 @@ def mempoolFeeTxHandler_checkTx(ctx, tx): consensus_price = State.gas_prices[tx_tier(tx)] min_price = max(validator_price, consensus_price) - if tx.gas_price() < min_price: + # zero means infinity for gas price cap + if tx.gas_price() > 0 and tx.gas_price() < min_price: return 'insufficient fees' return next_CheckTx(ctx, tx) From c85d663971b64e2a57027ca76155278138aaead0 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Thu, 17 Mar 2022 12:53:26 +0100 Subject: [PATCH 5/7] Add more information about transaction composability. --- docs/architecture/adr-048-consensus-fees.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 00d27a9f8d08..60e9a1ab71be 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -65,7 +65,13 @@ We also change the semantic of existing `fee` field of `Tx`, instead of charging Transactions are prioritized based on the tier, the higher the tier, the higher the priority. -Within the same tier/priority, the transactions are processed in FIFO manner which is the default behavior of tendermint. Be aware of that the mempool tx ordering logic is not part of consensus and can be modified by malicious validator. +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. + +This mechanism can be easily composed with prioritization mechanisms: +* we can add extra tires out of a user control: + * Example 1: user can set tire 0, 10 or 20, but the protocol will create tires 0, 1, 2 ... 29. For example IBC transactions will go to tire `user_tire + 5`: if user selected tire 1, then the transaction will go to tire 15. + * Example 2: we can reserve tire 4, 5, ... only for special transaction types. For example, tire 5 is reserved for evidence tx. So if submits a bank.Send transaction and set tire 5, it will be delegated to tire 3 (the max tire level available for any transaction). + * Example 3: we can enforce that all transactions of a sepecific type will go to specific tire. For example, tire 100 will be reserved for evidence transactions and all evidence transactions will always go to that tire. ### `min-gas-prices` @@ -112,6 +118,7 @@ def tx_tier(tx): else: # default tier for custom transactions return 0 + # NOTE: we can add more rules here per "Tx Prioritization" section class TierParams: 'gas price strategy parameters of one tier' @@ -182,6 +189,7 @@ If attacker spam with lower tier transactions, user can mitigate by sending high - The default tier keeps the same predictable gas price experience for client. - The higher tier's gas price can adapt to block load. - No priority conflict with custom priority based on transaction types, since this proposal only occupy three priority levels. +- Possibility to compose different priority rules with tires ### Negative @@ -192,4 +200,4 @@ If attacker spam with lower tier transactions, user can mitigate by sending high ## References - https://eips.ethereum.org/EIPS/eip-1559 -- https://iohk.io/en/blog/posts/2021/11/26/network-traffic-and-tiered-pricing/ \ No newline at end of file +- https://iohk.io/en/blog/posts/2021/11/26/network-traffic-and-tiered-pricing/ From 3598e7238e66bd9a4b172d222b3784ad8b417657 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Fri, 18 Mar 2022 23:30:11 +0800 Subject: [PATCH 6/7] tire -> tier --- docs/architecture/adr-048-consensus-fees.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 60e9a1ab71be..413cc99d6450 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -68,10 +68,10 @@ Transactions are prioritized based on the tier, the higher the tier, the higher 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. This mechanism can be easily composed with prioritization mechanisms: -* we can add extra tires out of a user control: - * Example 1: user can set tire 0, 10 or 20, but the protocol will create tires 0, 1, 2 ... 29. For example IBC transactions will go to tire `user_tire + 5`: if user selected tire 1, then the transaction will go to tire 15. - * Example 2: we can reserve tire 4, 5, ... only for special transaction types. For example, tire 5 is reserved for evidence tx. So if submits a bank.Send transaction and set tire 5, it will be delegated to tire 3 (the max tire level available for any transaction). - * Example 3: we can enforce that all transactions of a sepecific type will go to specific tire. For example, tire 100 will be reserved for evidence transactions and all evidence transactions will always go to that tire. +* we can add extra tiers out of a user control: + * 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. + * 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). + * 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. ### `min-gas-prices` @@ -189,7 +189,7 @@ If attacker spam with lower tier transactions, user can mitigate by sending high - The default tier keeps the same predictable gas price experience for client. - The higher tier's gas price can adapt to block load. - No priority conflict with custom priority based on transaction types, since this proposal only occupy three priority levels. -- Possibility to compose different priority rules with tires +- Possibility to compose different priority rules with tiers ### Negative From 304816817e106273a5072fe81e9354f929395062 Mon Sep 17 00:00:00 2001 From: yihuang Date: Thu, 28 Apr 2022 06:11:56 +0800 Subject: [PATCH 7/7] Update docs/architecture/adr-048-consensus-fees.md Co-authored-by: Aleksandr Bezobchuk --- docs/architecture/adr-048-consensus-fees.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-048-consensus-fees.md b/docs/architecture/adr-048-consensus-fees.md index 413cc99d6450..4ce4c81375d9 100644 --- a/docs/architecture/adr-048-consensus-fees.md +++ b/docs/architecture/adr-048-consensus-fees.md @@ -6,7 +6,7 @@ ## Status -Proposed +Rejected ## Abstract