以太坊被认为是一个世界计算机,它的运行需要耗费资源,为了确保网络不会被滥用和因错误的使用陷入宕机,任何交易的操作都需要支付一定的费用,这个费用我们可以简单称之为"Gas"。它代表了网络中执行操作所需的计算资源。
以太坊被认为是一个世界计算机,它的运行需要耗费资源,为了确保网络不会被滥用和因错误的使用陷入宕机,任何交易的操作都需要支付一定的费用,这个费用我们可以简单称之为"Gas"。它代表了网络中执行操作所需的计算资源。
做个简单的类比可以帮助我们更好的理解,如果把以太坊网络比作一名工人,那么 Gas
就是工人付出的劳动力。在工人完成工作后,需要支付劳动报酬。劳动报酬则等于每单位劳动力价格乘以付出的总的劳动力。每单位劳动力价格被称作 GasPrice
, 其值由以太坊网络动态决定的。因此总的劳动报酬就是 Gas * GasPrice
GasLimit
可以理解为愿意为多少劳动力买单。假如某项工作需要付出100个单位的劳动力,但你只愿意支付80个单位劳动力的费用。这项工作就无法完成。但当你愿意支付120个单位劳动力的费用时,则会在工作完成后会退还这20个单位的劳动力费用。类比到以太坊网络就是愿意为这笔交易最多支付多少 Gas
。
下图展示了一笔真实的交易
可以看到 Gas
部分由以下部分组成:
Gas Limit & Usage by Txn
: GasLimit
和实际花费的Gas
以及其在GasLimit
中的占比,这笔交易中实际使用的比例达到了 98.83%。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
是 EIP-1559
提案中引入的一个机制,目的是改善以太坊的费用市场并提高用户体验。BaseFee
是每个区块的基础费用,它的目的是通过自适应地调整费用来反映网络的拥堵程度。
源码中 BaseFee
的计算如下:
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
// 首先,函数检查当前区块是否是第一个实施 EIP-1559 的区块。如果是,则返回初始基础费用。
if !config.IsLondon(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee)
}
// 如果不是,函数计算父区块的目标燃气量(gas target)。这是通过父区块的燃气限制除以弹性乘数得到的。
parentGasTarget := parent.GasLimit / config.ElasticityMultiplier()
// 如果父区块的实际燃气使用量等于目标燃气量,基础费用保持不变。
if parent.GasUsed == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
}
var (
num = new(big.Int)
denom = new(big.Int)
)
if parent.GasUsed > parentGasTarget {
// 如果父区块的燃气使用量大于目标值, 基础费用应该增加.
// 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 {
// 如果父区块的燃气使用量小于目标值,基础费用应该减少
// 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区块列表页有如下两个区块
在20229351区块中,我们看到
BaseFee
等于3.05GweigasUsedDelta / parentGasTarget
等于 -23%, 说明需要降低 BaseFee
按照公式计算20229352区块的 BaseFee
等于
$$ BaseFee=max(0, 3.05−3.05∗0.23/8) = 2.963 $$
与图片一致
MaxPriorityFee
是优先费用。是对每单位 Gas
的额外加价, 这部分的费用将支付给矿工,值越大则交易更快的被打包。通过GasTracker可查看当前最新的 Gas
信息
最终的 GasPrice
等于 BaseFee
和 MaxPriorityFee
之和
对于交易:
$ Transaction Fee = GasUsed∗GasPrice =GasUsed∗(BaseFee+MaxPriorityFee) =115855∗(7.407585749+0.05) =863998.597Gwei $
交易费用与图中 Transaction Fee
字段值一致
MaxFee
意为最大的 GasPrice
由于发送的交易不一定会在下一个区块内打包,而 BaseFee
又是在动态的改变,如果交易设置的 MaxPriorityFee
过低,则有可能交易不会被打包。只能等待后续区块的打包。但如果后续区块的 BaseFee
比之前的高,则会导致交易被丢弃。而设置较高的 MaxFee
, 则可以保证交易在未来几个区块内不会因为 BaseFee
设置过低而被丢弃。
还是以劳动力举例。现在每单位的劳动力的价格(BaseFee
)是变动的, 由市场决定。在你发布一个工作后,而且还对每单位的劳动力付出额外报酬(MaxPriorityFee
)的情况下,由于你愿意支付的报酬低于市场价,此时没人愿意为你工作,则会对你发布的工作进行下架处理。因此你给出了最大的每单位的劳动力价格(MaxFee
),只要当前的 BaseFee
加上 MaxPriorityFee
是小于 MaxFee
,就可以继续招工。每单位劳动力价格仍按 BaseFee + MaxPriorityFee
计算
通常情况下 MaxFee
计算遵循公式:
$$ MaxFee=(2∗BaseFee)+MaxPriorityFee $$
可以保证连续 6个区块满 Gas
的情况下仍在内存池中等待打包
燃烧的手续费, 即将这部分费用转入黑洞地址。转入数量由 BaseFee
决定
$$ Burnt=BaseFee∗GasUsed $$
以上图中交易为例计算:
$$ Burnt=BaseFee∗GasUsed =7.407585749∗115855 =858205.846950395 Gwei $$
与图中 Burnt
字段值一致
交易节省的费用,等于最大可接受交易费用减去实际消耗的交易费用
$$ TxSavingsFees=MaxFee∗GasUsed−(BaseFee+MaxPriorityFee)∗GasUsed $$
以上图中交易为例计算
$ TxSavingsFees=MaxFee∗GasUsed−(BaseFee+MaxPriorityFee)∗GasUsed =7.657591636∗115855−863998.597 =23171.68198878Gwei $
与图中 Txn Savings
字段值一致
在发起 EIP1559 交易时, 常需要在交易中手动填入 Gas
相关的参数,这些参数可以通过向节点预发送 http
请求获得,包括了以下 JSON-RPC
方法
eth_estimateGas
eth_maxPriorityFeePerGas
eth_getBlockByNumber
将交易发送到该接口,可以获得预估 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
}
该接口用来获取当前最新的 MaxPriorityFee
// Request Payload
{
"jsonrpc": "2.0",
"method": "eth_maxPriorityFeePerGas",
"params": [],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"result": "0x9b8495", // MaxPriorityFee
"id": 1
}
该接口用于获取区块信息,其中包含了 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
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!