以太坊 Gas 机制详解(EIP-1559)

以太坊 Gas 机制详解(EIP-1559)

以太坊中的 Gas 是执行智能合约和进行交易所需的一种衡量单位。它代表了网络中执行操作所需的计算资源。Gas 用于防止网络滥用和保证交易和智能合约的执行效率。每个操作和交易都有 Gas 消耗,用户通过设置 Gas 价格来为其支付。Gas 的机制帮助保持以太坊网络的健康运行,通过对资源消耗的计费来调节网络负载。

如果把以太坊网络比作一名工人,那么 Gas 就是工人付出的劳动力。在工人完成工作后,需要支付劳动报酬。劳动报酬则等于每单位劳动力价格乘以付出的总的劳动力。每单位劳动力价格被称作 GasPrice, 其值由以太坊网络动态决定的。因此总的劳动报酬就是 Gas * GasPrice

GasLimit 可以理解为愿意为多少劳动力买单。假如某项工作需要付出 100 的劳动力,但你只愿意支付 80 劳动力的费用。因此这项工作就无法完成。但当你愿意支付 120 劳动力的费用时,则会在工作完成后会退还这 20 劳动力的费用。类比到以太坊网络就是愿意为这笔交易最多支付多少 Gas

现有一笔如下所示的交易:

可以看到 Gas 部分由以下部分组成:

  • Gas Limit & Usage by Txn: GasLimit 和 实际花费的 Gas 以及其在 GasLimit 中的占比
  • Gas Fees
    • Base: 基础 GasPrice
    • Max: 最大 GasPrice
    • Max Priority: 支付给以太坊节点矿工的 GasPrice
  • Burnt & Txn Savings Fees
    • Burnt: 燃烧的手续费
    • Txn Savings: 交易节省的费用

Txn Type: 2(EIP-1559): 根据 EIP-2718 明确交易类型为 2, 指明这是一笔 EIP-1559 的交易。EIP 详情请看 这里

BaseFee

BaseFeeEIP-1559 提案中引入的一个机制,目的是改善以太坊的费用市场并提高用户体验。BaseFee 是每个区块的基础费用,它的目的是通过自适应地调整费用来反映网络的拥堵程度。

源码中 BaseFee 的计算如下:

go 复制代码
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
	// If the current block is the first EIP-1559 block, return the InitialBaseFee.
	if !config.IsLondon(parent.Number) {
		return new(big.Int).SetUint64(params.InitialBaseFee)
	}

	parentGasTarget := parent.GasLimit / config.ElasticityMultiplier()
	// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
	if parent.GasUsed == parentGasTarget {
		return new(big.Int).Set(parent.BaseFee)
	}

	var (
		num   = new(big.Int)
		denom = new(big.Int)
	)

	if parent.GasUsed > parentGasTarget {
		// If the parent block used more gas than its target, the baseFee should increase.
		// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
		num.SetUint64(parent.GasUsed - parentGasTarget)
		num.Mul(num, parent.BaseFee)
		num.Div(num, denom.SetUint64(parentGasTarget))
		num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator()))
		baseFeeDelta := math.BigMax(num, common.Big1)

		return num.Add(parent.BaseFee, baseFeeDelta)
	} else {
		// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
		// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
		num.SetUint64(parentGasTarget - parent.GasUsed)
		num.Mul(num, parent.BaseFee)
		num.Div(num, denom.SetUint64(parentGasTarget))
		num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator()))
		baseFee := num.Sub(parent.BaseFee, num)

		return math.BigMax(baseFee, common.Big0)
	}
}

计算 BaseFee 过程如下:

  • 如果当前区块是第一个 EIP-1559 区块,则返回 InitialBaseFee, 其值为 1 Gwei

  • 计算 parentGasTarget 等于父区块 GasLimit 的一半

    • 父区块的 GasUsed 等于 parentGasTarget: BaseFee 不变
    • 父区块的 GasUsed 大于 parentGasTarget: BaseFee 增加, 计算遵循公式

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> p a r e n t B a s e F e e + m a x ( 1 , p a r e n t B a s e F e e ∗ g a s U s e d D e l t a / p a r e n t G a s T a r g e t / b a s e F e e C h a n g e D e n o m i n a t o r ) \mathtt{parentBaseFee + max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)} </math>parentBaseFee+max(1,parentBaseFee∗gasUsedDelta/parentGasTarget/baseFeeChangeDenominator)

  • 父区块的 GasUsed 小于 parentGasTarget: BaseFee 减少, 计算遵循公式

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> m a x ( 0 , p a r e n t B a s e F e e − p a r e n t B a s e F e e ∗ g a s U s e d D e l t a / p a r e n t G a s T a r g e t / b a s e F e e C h a n g e D e n o m i n a t o r ) \mathtt{max(0, parentBaseFee - parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)} </math>max(0,parentBaseFee−parentBaseFee∗gasUsedDelta/parentGasTarget/baseFeeChangeDenominator)

其中:

  • GasUsed: 实际使用的 Gas
  • parentBaseFee 等于 父区块的 BaseFee
  • gasUsedDelta 等于 parent.GasUsed - parentGasTarget,即父区块的 Gas 实际使用量与目标总量之间的差额
  • parentGasTarget 父区块 GasLimit 的一半, 一般为 15000000
  • BaseFeeChangeDenominator 常量,值为 8

etherscan 区块列表页 有如下两个区块

在 18247918 区块中,可以知道

  • BaseFee 等于 6.79 Gwei
  • gasUsedDelta / parentGasTarget 等于 -24%, 说明需要降低 BaseFee

按照公式计算 18247919 区块的 BaseFee 等于
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> B a s e F e e = m a x ( 0 , 6.79 − 6.79 ∗ 0.24 / 8 ) = 6.5863 \mathrm{BaseFee = max(0, 6.79 - 6.79 * 0.24 / 8) = 6.5863} </math>BaseFee=max(0,6.79−6.79∗0.24/8)=6.5863

与图示一致

MaxPriorityFee

优先费用。是对每单位 Gas 的加价, 这部分的费用将支付给矿工,值越大则交易更快的被打包。可以通过这个页面查看

最终的 GasPrice 就是 BaseFeeMaxPriorityFee 之和

对于交易:


<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T r a n s a c t i o n F e e = G a s U s e d ∗ G a s P r i c e = G a s U s e d ∗ ( B a s e F e e + M a x P r i o r i t y F e e ) = 115855 ∗ ( 7.407585749 + 0.05 ) = 863998.597 G w e i \begin{aligned} \tt{Transaction\ Fee} & = \tt{GasUsed * GasPrice} \\ &= \tt{GasUsed * (BaseFee + MaxPriorityFee)} \\ &= 115855 * (7.407585749 + 0.05) \\ & = 863998.597\tt{Gwei} \end{aligned} </math>Transaction Fee=GasUsed∗GasPrice=GasUsed∗(BaseFee+MaxPriorityFee)=115855∗(7.407585749+0.05)=863998.597Gwei

与图中 Transaction Fee 字段值一致

MaxFee

MaxFee 为最大 GasPrice

由于发送的交易不一定会在下一个区块内打包,而 BaseFee 又是在动态的改变,如果交易设置的 MaxPriorityFee 过低,则有可能交易不会被打包。只能等待后续区块的打包。但如果后续区块的 BaseFee 比之前的高,则会导致交易被丢弃。而设置较高的 MaxFee, 则可以保证交易在未来几个区块内不会因为 BaseFee 设置过低而被丢弃。

还是以劳动力举例。现在每单位的劳动力的价格(BaseFee)是变动的, 由市场决定。在你发布一个工作后,而且还对每单位的劳动力付出额外报酬(MaxPriorityFee)的情况下,由于你愿意支付的报酬低于市场价,此时没人愿意为你工作,则会对你发布的工作进行下架处理。因此你给出了最大的每单位的劳动力价格(MaxFee),只要当前的 BaseFee 加上 MaxPriorityFee 是小于 MaxFee,就可以继续招工。每单位劳动力价格仍按 BaseFee + MaxPriorityFee计算

通常情况下 MaxFee 计算遵循公式:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M a x F e e = ( 2 ∗ B a s e F e e ) + M a x P r i o r i t y F e e \tt{Max Fee = (2 * BaseFee) + MaxPriorityFee} </math>MaxFee=(2∗BaseFee)+MaxPriorityFee

可以保证连续 6 个区块满 Gas 的情况下仍在内存池中等待打包

Burnt

燃烧的手续费, 即将这部分费用转入黑洞地址。转入数量由 BaseFee 决定
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> B u r n t = B a s e F e e ∗ G a s U s e d \mathtt{Burnt = BaseFee * GasUsed} </math>Burnt=BaseFee∗GasUsed

以上图中交易为例计算
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> B u r n t = B a s e F e e ∗ G a s U s e d = 7.407585749 ∗ 115855 = 858205.846950395 G w e i \begin{aligned} \tt{Burnt} & = \tt{BaseFee * GasUsed} \\ & = 7.407585749 * 115855 \\ &= 858205.846950395 \ \tt{Gwei} \end{aligned} </math>Burnt=BaseFee∗GasUsed=7.407585749∗115855=858205.846950395 Gwei

与图中 Burnt 字段值一致

Txn Savings

交易节省的费用,等于最大可接受交易费用减去实际消耗的交易费用
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T x S a v i n g s F e e s = M a x F e e ∗ G a s U s e d − ( B a s e F e e + M a x P r i o r i t y F e e ) ∗ G a s U s e d \tt{Tx Savings Fees = MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed} </math>TxSavingsFees=MaxFee∗GasUsed−(BaseFee+MaxPriorityFee)∗GasUsed

以上图中交易为例计算
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T x S a v i n g s F e e s = M a x F e e ∗ G a s U s e d − ( B a s e F e e + M a x P r i o r i t y F e e ) ∗ G a s U s e d = 7.657591636 ∗ 115855 − 863998.597 = 23171.68198878 G w e i \begin{aligned} \tt{Tx Savings Fees} & = \tt{MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed} \\ & = 7.657591636 * 115855 - 863998.597 \\ & = 23171.68198878 \tt{Gwei} \\ \end{aligned} </math>TxSavingsFees=MaxFee∗GasUsed−(BaseFee+MaxPriorityFee)∗GasUsed=7.657591636∗115855−863998.597=23171.68198878Gwei

与图中 Txn Savings 字段值一致

JSON-RPC

在发起交易时, 常需要在交易中手动填入 Gas 相关的参数,这些参数可以通过向节点预发送 http 请求获得,包括了以下 JSON-RPC 方法

  • eth_estimateGas
  • eth_maxPriorityFeePerGas
  • eth_getBlockByNumber

eth_estimateGas

将交易发送到该接口,可以获得预估 Gas, 可用来设置交易的 GasLimit

ts 复制代码
// Request Payload
{
  "jsonrpc": "2.0",
  "method": "eth_estimateGas",
  "params": [
    {
      "from": "0xD28C383dd3a1C0154129F67067175884e933cf4e",
      "to": "0x7071D6EF9FaF45aA48c22bae7d4a295aD68DC038",
      "value": "0x186a0"
    }
  ],
  "id": 1
}
// Response
{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x5208" // 21000
}

eth_maxPriorityFeePerGas

该接口用来获取当前最新的 MaxPriorityFee

ts 复制代码
// Request Payload
{
  "jsonrpc": "2.0",
  "method": "eth_maxPriorityFeePerGas",
  "params": [],
  "id": 1
}
// Response
{
  "jsonrpc": "2.0",
  "result": "0x9b8495", // MaxPriorityFee
  "id": 1
}

eth_getBlockByNumber

该接口用于获取区块信息,其中包含了 BaseFee 等信息

ts 复制代码
// Request Payload
{
  "jsonrpc": "2.0",
  "method": "eth_getBlockByNumber",
  "params": [
    "latest",
    false
  ],
  "id": 1
}
// Response
{
  "jsonrpc": "2.0",
  "result": {
    "baseFeePerGas": "0x1bc47470a", // baseFee
    "difficulty": "0x0",
    "extraData": "0x546974616e2028746974616e6275696c6465722e78797a29",
    "gasLimit": "0x1c9c380",
    "gasUsed": "0xced6fd",
    "hash": "0xbb9b314d0b8208e655a0afc17384f56f44659a63e3ba4e244609105da497a7d9",
    ...
  },
  "id": 1
}

获取最新的区块信息后, 字段 baseFeePerGas 的值就是 BaseFee。结合上面获取的 MaxPriorityFee, 可以用来设置 MaxFee

ini 复制代码
Max Fee = (2 * BaseFee) + MaxPriorityFee
相关推荐
星尘安全1 小时前
安全工程师入侵加密货币交易所获罪
安全·区块链·漏洞·加密货币
Thanks_ks5 小时前
探索计算机互联网的奇妙世界:从基础到前沿的无尽之旅
物联网·云计算·区块链·tcp/ip协议·计算机互联网·万维网·未来科技
BlockOne115 小时前
应用链风口下,一键发链该如何选择?
区块链
Footprint_Analytics5 小时前
Footprint Analytics 助力 Sei 游戏生态增长
游戏·web3·区块链
BSV区块链5 小时前
如何在BSV区块链上实现可验证AI
人工智能·区块链
电报号dapp1195 小时前
DeFi 4.0峥嵘初现:主权金融时代的来临
金融·区块链
搬砖的小码农_Sky8 小时前
什么是零知识证明?
区块链·密码学·零知识证明
TinTin Land8 小时前
高活跃社区 Doge 与零知识证明的强强联手,QED 重塑可扩展性
区块链·零知识证明
Roun31 天前
去中心化存储:Web3中的数据安全新标准
web3·去中心化·区块链
请不要叫我菜鸡1 天前
分布式——一致性模型与共识算法
分布式·后端·区块链·raft·共识算法·zab