moon

moon

Build for builders on blockchain
github
twitter

以太坊 Gas 機制詳解(EIP-1559)

以太坊中的 Gas 是執行智能合約和進行交易所需的一種衡量單位。它代表了網絡中執行操作所需的計算資源。Gas 用於防止網絡濫用和保證交易和智能合約的執行效率。每個操作和交易都有 Gas 消耗,用戶通過設置 Gas 價格來為其支付。Gas 的機制幫助保持以太坊網絡的健康運行,通過對資源消耗的計費來調節網絡負載。

如果把以太坊網絡比作一名工人,那麼 Gas 就是工人付出的勞動力。在工人完成工作後,需要支付勞動報酬。勞動報酬則等於每單位勞動力價格乘以付出的總的勞動力。每單位勞動力價格被稱作 GasPrice,其值由以太坊網絡動態決定的。因此總的勞動報酬就是 Gas * GasPrice

GasLimit 可以理解為願意為多少勞動力買單。假如某項工作需要付出 100 的勞動力,但你只願意支付 80 勞動力的費用。因此這項工作就無法完成。但當你願意支付 120 勞動力的費用時,則會在工作完成後會退還這 20 勞動力的費用。類比到以太坊網絡就是願意為這筆交易最多支付多少 Gas

現有一筆如下所示的交易:

transaction

可以看到 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 的交易

BaseFee#

BaseFeeEIP-1559 提案中引入的一個機制,目的是改善以太坊的費用市場並提高用戶體驗。BaseFee 是每個區塊的基礎費用,它的目的是通過自適應地調整費用來反映網絡的擁堵程度。

源碼中 BaseFee 的計算如下:

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 增加,計算遵循公式
parentBaseFee + max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
  • 父區塊的 GasUsed 小於 parentGasTarget: BaseFee 減少,計算遵循公式
max(0, parentBaseFee - parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)

其中:

  • GasUsed: 實際使用的 Gas
  • parentBaseFee 等於 父區塊的 BaseFee
  • gasUsedDelta 等於 parent.GasUsed - parentGasTarget,即父區塊的 Gas 實際使用量與目標總量之間的差額
  • parentGasTarget 父區塊 GasLimit 的一半,一般為 15000000
  • BaseFeeChangeDenominator 常量,值為 8

etherscan 區塊列表頁 有如下兩個區塊

blocks

在 18247918 區塊中,可以知道

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

按照公式計算 18247919 區塊的 BaseFee 等於

BaseFee=max(0,6.796.790.24/8)=6.5863\mathrm{BaseFee = max(0, 6.79 - 6.79 * 0.24 / 8) = 6.5863}

與圖示一致

MaxPriorityFee#

MaxPriorityFee 是優先費用。是對每單位 Gas 的額外加價,這部分的費用將支付給礦工,值越大則交易更快的被打包。通過GasTracker可查看當前最新的 Gas 信息

最終的 GasPrice 等於 BaseFeeMaxPriorityFee 之和

對於交易:

transaction

Transaction Fee=GasUsedGasPrice=GasUsed(BaseFee+MaxPriorityFee)=115855(7.407585749+0.05)=863998.597Gwei\begin{aligned} \tt{Transaction\ Fee} & = \tt{GasUsed * GasPrice} \\ &= \tt{GasUsed * (BaseFee + MaxPriorityFee)} \\ &= 115855 * (7.407585749 + 0.05) \\ & = 863998.597\tt{Gwei} \end{aligned}

交易費用與圖中 Transaction Fee 字段值一致

MaxFee#

MaxFee 意為最大的 GasPrice

由於發送的交易不一定會在下個區塊內打包,而 BaseFee 又是在動態的改變,如果交易設置的 MaxPriorityFee 過低,則有可能交易不會被打包。只能等待後續區塊的打包。但如果後續區塊的 BaseFee 比之前的高,則會導致交易被丟棄。而設置較高的 MaxFee, 則可以保證交易在未來幾個區塊內不會因為 BaseFee 設置過低而被丟棄。

還是以勞動力舉例。現在每單位的勞動力的價格 (BaseFee) 是變動的,由市場決定。在你發布一個工作後,而且還對每單位的勞動力付出額外報酬 (MaxPriorityFee) 的情況下,由於你願意支付的報酬低於市場價,此時沒有人願意為你工作,則會對你發布的工作進行下架處理。因此你給出了最大的每單位的勞動力價格 (MaxFee),只要當前的 BaseFee 加上 MaxPriorityFee 是小於 MaxFee,就可以繼續招工。每單位勞動力價格仍按 BaseFee + MaxPriorityFee計算

通常情況下 MaxFee 計算遵循公式:

MaxFee=(2BaseFee)+MaxPriorityFee\tt{Max Fee = (2 * BaseFee) + MaxPriorityFee}

可以保證連續 6 個區塊滿 Gas 的情況下仍在內存池中等待打包

Burnt#

燃燒的手續費,即將這部分費用轉入黑洞地址。轉入數量由 BaseFee 決定

Burnt=BaseFeeGasUsed\mathtt{Burnt = BaseFee * GasUsed}

以上圖中交易為例計算

Burnt=BaseFeeGasUsed=7.407585749115855=858205.846950395 Gwei\begin{aligned} \tt{Burnt} & = \tt{BaseFee * GasUsed} \\ & = 7.407585749 * 115855 \\ &= 858205.846950395 \ \tt{Gwei} \end{aligned}

與圖中 Burnt 字段值一致

Txn Savings#

交易節省的費用,等於最大可接受交易費用減去實際消耗的交易費用

TxSavingsFees=MaxFeeGasUsed(BaseFee+MaxPriorityFee)GasUsed\tt{Tx Savings Fees = MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed}

以上圖中交易為例計算

TxSavingsFees=MaxFeeGasUsed(BaseFee+MaxPriorityFee)GasUsed=7.657591636115855863998.597=23171.68198878Gwei\begin{aligned} \tt{Tx Savings Fees} & = \tt{MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed} \\ & = 7.657591636 * 115855 - 863998.597 \\ & = 23171.68198878 \tt{Gwei} \\ \end{aligned}

與圖中 Txn Savings 字段值一致

JSON-RPC#

在發起 EIP1559 交易時,常需要在交易中手動填入 Gas 相關的參數,這些參數可以通過向節點預發送 http 請求獲得,包括了以下 JSON-RPC 方法

  • eth_estimateGas
  • eth_maxPriorityFeePerGas
  • eth_getBlockByNumber

eth_estimateGas#

將交易發送到該接口,可以獲得預估 Gas, 常用來設置交易的 GasLimit

// 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

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

eth_getBlockByNumber#

該接口用於獲取區塊信息,其中包含了 BaseFee 等信息

// 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

Max Fee = (2 * BaseFee) + MaxPriorityFee
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。