1 以太坊燃料的作用
以太坊燃料在交易过程中起着重要作用。从以太坊系统的角度,通过燃料费用提高恶意交易的成本减少攻击者的攻击收益,增强了以太坊交易的安全性,也促使合约开发者优化设计减少合约对以太坊算力的消耗;从交易发起者角度,通过设定燃料相关的参数,以尽可能小的代价完成交易,以及通过燃料费用上限的设置,防止合约自身漏洞或者调用不安全合约导致的攻击。
2 以太坊燃料的构成
以太坊交易的燃料费用GasFee是燃料价格Gasprice与实际使用燃料Gasused的乘积,即GasFee = Gasprice × Gasused,当实际消耗的燃料费用大于交易发起者在交易中预设的燃料上限Gaslimit时,当前交易所做的所有状态修改将回滚,但燃料费用不会退回。
3 以太坊燃料定价机制的缺陷
伦敦分叉之前和之后采用不同的燃料定价机制。
伦敦分叉之前燃料价格由交易发起者根据历史交易的统计值预先设定,矿工选择出价高的交易打包。这种类似于拍卖的燃料价格生成机制导致四方面的低效:
1)交易费用水平的波动与交易的社会成本不匹配。交易发起者自主设定的燃料价格通常导致交易费用的变化远大于交易成本的变化。
2)非必要的用户交易延迟。每个区块总燃料的硬性限制以及交易数量的自然波动,导致交易经常需要等好几个区块才能被打包。
3)低效的首次出价。交易发起者为了提高交易成功概率,会设置较高的交易费用,导致经常性的多支付交易费用。
4)没有出块奖励时区块链变得不稳定。矿工通过挖掘“姐妹区块”的方式“偷取”交易手续费导致了区块链的不稳定。
4 伦敦分叉燃料定价机制的优化
4.1 新定价机制
为了解决以太坊燃料定价机制的缺陷,EIP-1559提出了燃料费用生成机制的优化,EIP-1559是伦敦分叉引入的主要变化。
EIP-1559引入每份燃料最大小费maxPriorityFeePerGas、每份燃料最大费用maxFeePerGas、每份燃料基础费用baseFeePerGas这几个新的参数。
maxPriorityFeePerGas代表每份燃料支付给矿工的最大小费。
maxFeePerGas代表每份燃料支付给矿工的最大费用,包含baseFeePerGas和maxPriorityFeePerGas。
baseFeePerGas代表每份燃料的基础费用,由系统自动生成,且会被以太坊系统燃烧掉。
交易所消耗的燃料费用计算公式是:
GasFee = Gasused × (maxFeePerGas) = Gasused × (baseFeePerGas + maxPriorityFeePerGas)。
4.2 基础燃料费的算法
go-ethereum的eip1559.go实现了基础燃料费的算法,算法总结如下:
伦敦分叉之后的初始基础燃料费InitialBaseFee是1Gwei,基础费用变化因子BaseFeeChangeDenominator定义了区块间基础费用变化值的边界,值为8,弹性倍乘系数ElasticityMultiplier定义了燃料上限的最大倍乘边界,值为2;
父区块的燃料目标值parentGasTarget = parent.GasLimit / ElasticityMultiplier;
当父区块的实际燃料值parent.GasUsed等于父区块的燃料目标值parentGasTarget时,当前区块的基础费用就等于父区块的基础费用parent.BaseFee;
当父区块的实际燃料值parent.GasUsed大于父区块的燃料目标值parentGasTarget时,说明当前区块的基础费用应该增加,以减小区块包含交易的计算量,基础费用的增加值取(1,parent.BaseFee * gasUsedDelta / parentGasTarget / BaseFeeChangeDenominator)的最大值;
当父区块的实际燃料值parent.GasUsed小于父区块的燃料目标值parentGasTarget时,说明当前区块的基础费用应该减小,以增加区块包含交易的计算量,基础费用的减小值取(0,parent.BaseFee * gasUsedDelta / parentGasTarget / BaseFeeChangeDenominator)的最大值。
实现基础燃料费算法的代码如下:
// CalcBaseFee calculates the basefee of the header.
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 / params.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(params.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(params.BaseFeeChangeDenominator))
baseFee := num.Sub(parent.BaseFee, num)
return math.BigMax(baseFee, common.Big0)
}
}
5 以太坊燃料价格查询
在网站中https://ethereumprice.org/gas/可以查询到最近一周的平均燃料价格。
参考
[1] 以太坊开发文档, Gas and fees | ethereum.org
[2] 以太坊改进提议EIP-1559, https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md
[3] 开源以太坊客户端go-ethereum源代码eip1559.go,go-ethereum/eip1559.go at master · ethereum/go-ethereum · GitHub
[4] 以太坊燃料费用查询网站,Ethereum Gas Price Charts & Historical Gas Fees – ethereumprice