以太坊中的 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
的交易
BaseFee#
BaseFee
是 EIP-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
的一半,一般為 15000000BaseFeeChangeDenominator
常量,值為 8
在 etherscan
區塊列表頁 有如下兩個區塊
在 18247918 區塊中,可以知道
BaseFee
等於 6.79 GweigasUsedDelta / parentGasTarget
等於 -24%, 说明需要降低BaseFee
按照公式計算 18247919 區塊的 BaseFee
等於
與圖示一致
MaxPriorityFee#
MaxPriorityFee
是優先費用。是對每單位 Gas
的額外加價,這部分的費用將支付給礦工,值越大則交易更快的被打包。通過GasTracker可查看當前最新的 Gas
信息
最終的 GasPrice
等於 BaseFee
和 MaxPriorityFee
之和
對於交易:
交易費用與圖中 Transaction Fee
字段值一致
MaxFee#
MaxFee
意為最大的 GasPrice
由於發送的交易不一定會在下個區塊內打包,而 BaseFee
又是在動態的改變,如果交易設置的 MaxPriorityFee
過低,則有可能交易不會被打包。只能等待後續區塊的打包。但如果後續區塊的 BaseFee
比之前的高,則會導致交易被丟棄。而設置較高的 MaxFee
, 則可以保證交易在未來幾個區塊內不會因為 BaseFee
設置過低而被丟棄。
還是以勞動力舉例。現在每單位的勞動力的價格 (BaseFee
) 是變動的,由市場決定。在你發布一個工作後,而且還對每單位的勞動力付出額外報酬 (MaxPriorityFee
) 的情況下,由於你願意支付的報酬低於市場價,此時沒有人願意為你工作,則會對你發布的工作進行下架處理。因此你給出了最大的每單位的勞動力價格 (MaxFee
),只要當前的 BaseFee
加上 MaxPriorityFee
是小於 MaxFee
,就可以繼續招工。每單位勞動力價格仍按 BaseFee + MaxPriorityFee
計算
通常情況下 MaxFee
計算遵循公式:
可以保證連續 6 個區塊滿 Gas
的情況下仍在內存池中等待打包
Burnt#
燃燒的手續費,即將這部分費用轉入黑洞地址。轉入數量由 BaseFee
決定
以上圖中交易為例計算
與圖中 Burnt
字段值一致
Txn Savings#
交易節省的費用,等於最大可接受交易費用減去實際消耗的交易費用
以上圖中交易為例計算
與圖中 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