以太坊机制详解:Gas与Gas Prices深度解析

这篇具有很好参考价值的文章主要介绍了以太坊机制详解:Gas与Gas Prices深度解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

读者可以前往我的博客获得更好的阅读体验。

在以太坊London升级后,以太坊启用了EIP1559进行gas计算。由于EIP1559引入的新的gas机制较为复杂,所以我写了此文介绍了以太坊的gas机制。

本文主要涉及以下内容:

  • EIP1559引入的新的gas price设置方式
  • 交易花费的具体计算方式

另,此文写作日期在以太坊即将进行合并时,所以我们在后文依旧使用了矿工这一称谓。

概念辨析

由于此篇是解析以太坊GAS机制的第一篇,所以我们首先在此处介绍gasgas price的区别。

前者是以太坊转账或者合约操作的基准价值。你可以在此网站查询到每一个操作码的最小GAS消费。如下图:

以太坊机制详解:Gas与Gas Prices深度解析

理论上,我们可以通过合约字节码判断出合约操作所需要的gas值。当然,如果读者使用了Foundry作为智能合约开发工具链,可以在合约代码根目录运行forge test --gas-report获得gas报告,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

上述表格也显示了合约部署消耗的gas值。当然,以太坊中也有一种不需要与智能合约交互的但非常重要的操作就是ETH转账,此操作被规定为21,000。可以参考此交易,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

如果你自定义交易的gas最大限额,但设置的数量小于合约操作所需要的gas,就会出现错误。比如这个交易,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

上图由红框框出的部分就是此交易的gas限制和gas实际用量。此操作实际的gas用量为160,596,此处的最大限额小于合约操作的用量,所以出现了错误。正常的合约操作可以参考此交易。当然此交易虽然失败了,但仍打包到区块内并收取交易手续费并奖励矿工。因为矿工在接受交易时并不清楚交易的gas用量,矿工会运行交易直至gas耗尽,此部分需要补偿矿工。

当Gas的实际用量小于Gas Limit时,剩余部分会退还给用户。

gas并不代表着进行这一操作所消耗的ETH数量。以太坊中存在大量的交易,我们需要根据网络情况调整手续费,为了有效调整手续费,以太坊引入了gas price价值作为计算手续费的单位,具体计算公式为
Transaction Fee = Gas * Gas Price,其中Transaction Fee就是交易手续费的意思。在后文中,我们会详细分析gas price的计算方法。

Gas Limit 的获取

对于Gas Limit的获取,以太坊客户端给出了一个专用的RPC API,被称为eth_estimateGas

此API调用所需要的参数其实就是交易所需要的参数,我们在此处直接给出两个示例帮助大家使用。

在后文中,我们主要使用Cloudflare提供的公用以太坊网关作为RPC API服务商,其地址为https://cloudflare-eth.com/v1/mainnet

为了方便读者学习,此处我们使用以太坊官方文档提供的线上测试功能。读者可以通过以下方法打开测试功能:

以太坊机制详解:Gas与Gas Prices深度解析

首先,我们尝试获取转账交易的Gas消耗,在上图给出的测试栏的的左侧输入以下内容:

{
    "jsonrpc": "2.0",
    "method": "eth_estimateGas",
    "params": [
        {
            "from": "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
            "to": "0xd3CdA913deB6f67967B99D67aCDFa1712C293601",
            "value": "0x186a0"
        }
    ],
    "id": 0
}

输入完成后点击运行按钮,我们可以在右侧获得以下返回:

{
    "jsonrpc": "2.0",
    "result": "0x5208",
    "id": 0
}

其中,result就是此交易的gas,将其转为十进制,结果恰好为21000,与上文给出的结果相符。

当然,更常见的Gas估计是估计合约操作所消耗的Gas值,我们在此处以WETH合约(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)为例获取存储deposit()操作的Gas消耗。

使用此API的具体参数可以参考以下

{
    "jsonrpc": "2.0",
    "method": "eth_estimateGas",
    "params": [
        {
            "type": "2",
            "from": "0x8D97689C9818892B700e27F316cc3E41e17fBeb9",
            "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
            "value": "0x186a0",
            "input": "0xd0e30db0"
        }
    ],
    "id": 0
}

其中各个参数意义如下:

  • from 调用合约的用户地址
  • to 目标合约地址
  • value 在调用合约时发送的ETH
  • input 调用合约时发送的Calldata

input可以在此网站获得。获得deposit()函数调用Calldata的形式如下图:

以太坊机制详解:Gas与Gas Prices深度解析

由于此处deposit()没有参数,所以我们没有在此处使用Add argument增加参数。

发送上述请求,我们可以获得以下返回值:

{
    "jsonrpc": "2.0",
    "result": "0xafee",
    "id": 0
}

result转换为十进制得到45038,这与我们在此页面查询得到的结果一致。

对于获取gas的估计值,我们也可以使用cast获得,在此处,我们仍使用WETH合约。

在终端内输入

cast estimate 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \
	--value 1.1ether "deposit()" \
	--rpc-url https://cloudflare-eth.com/v1/mainnet

我们可以获得返回值为27938。读者可以发现此交易的gas正是27938

上述两者的不同原因是在EIP2929。简单来说,SSTORE操作符的gas决定方式较为特殊。此操作符用于向合约特定的存储槽内写入数据。其gas决定方法如下:

  • 当写入存储槽本来无数据时,使用SSTORE写入数据消耗22100。如果读者的地址未持有WETH时,我们需要消耗此数值的gas
  • 当写入存储槽内存在非零数据时,使用SSTORE写入数据消耗5000

当我们使用cast estimate评估gas时,默认使用的地址内存在WETH,而在我们上文使用的 RPC API 时,使用的地址内不持有WETH。更加详细的Gas分析我们会在后面几篇内给出。

Gas Price计算

我们主要考虑在London升级后的符合EIP1559标准的交易,这些交易均被标记为type 2

名词解释

在此处,我们给出一个交易的实例:

以太坊机制详解:Gas与Gas Prices深度解析

我们主要考察Gas Price这一栏。内部由以下构成:

  • Gas Limit & Usage by Txn 我们在上文进行了解释,前者表示合约操作的Gas限额,后者表示本次交易的Gas用量
  • Gas Fees 给出Gas Price的各个计算参数
    • Base 基础Gas Price
    • Max 最大Gas Price
    • Max Priority 支付给以太坊节点矿工的Gas Price
  • Burnt & Txn Savings Fees 燃烧掉的手续费和给予矿工的手续费
    • Burnt 燃烧的手续费。EIP1559规定了每次交易的手续费部分进行燃烧,这一行为有效避免了ETH通货膨胀
    • Txn Savings 给予矿工的手续费

我们会在下文给出每个参数的计算方法。

Base Fee

此参数由以太坊网络计算得到,在同一区块内是固定的。如果你设置的Base Fee小于当前网络的Gas Fee,则交易永远不会被打包。

我们在此处给出go-ethereum的源代码:

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

其中parent为上一区块的区块头。我们在此处不再详细解释此结构体内的变量,读者可自行查找对应源代码。此处用到的一个重要参数为parent.GasLimit,含义为区块内各个交易的Gas累加最大值,读者可以通过此网站查看历史上的GasLimit变化。目前(2022年8月),此值大概为3千万。

Miner: miner.Config{
        GasCeil:  30000000,
        GasPrice: big.NewInt(params.GWei),
        Recommit: 3 * time.Second,
    }

当然,区块的GasLimit并不是固定不变的,会在小范围内波动,具体的计算逻辑位于go-ethereum内的CalcGasLimit(parentGasLimit, desiredLimit uint64)函数,此函数使用的参数desiredLimit即为 3千万 。限于篇幅且此计算函数较为简单,我们不对计算函数进行详细解释,读者有兴趣可以自行研究此函数。

params.ElasticityMultiplier值已经在源代码进行了硬编码为2。通过parentGasTarget := parent.GasLimit / params.ElasticityMultiplier代码,我们可以计算出目前目标区块容量为1.5千万。

params.InitialBaseFee此值为EIP1559启动时区块的baseFee,从后文我们可以看到计算baseFee依赖于上一区块的baseFee,而初始区块的上一区块没有通过此属性,所以我们需要进行初始化。此变量被初始化为const InitialBaseFee untyped int = 10000000001000000000的单位为wei,即1 gwei

if parent.GasUsed == parentGasTarget {
    return new(big.Int).Set(parent.BaseFee)
}

此代码说明,当目前区块交易Gas累加值为1.5千万时,区块与上一区块的Base Fee相同。这也意味着当前Gas Price很好平衡了交易数量与交易费用,不需要进行调整。

除了这种相同的情况,还有大于和小于的情况,下面先展示上一区块没有大于目标Gas总量的情况。

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

在注释中,我们可以看到当前区块的baseFee的计算公式为

parent.BaseFee + 
max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)

其中各个参数意义如下:

  • parentBaseFeeparent.BaseFee,即上一区块的baseFee
  • gasUsedDeltaparent.GasUsed - parentGasTarget,即上一区块的Gas总量与目标总量之间的差额
  • parentGasTarget为上一区块的目标值,在一定时期内可以认为是常量,目前为1.5千万Gas
  • BaseFeeChangeDenominator,定义为const BaseFeeChangeDenominator untyped int = 8

我们计算极限情况,即当前区块的上一区块的Gas总量到达限额3千万,此时gasUsedDelta1.5parentGasTarget1.5,简单计算可以得出当前区块的BaseFee应为上一区块的112.5 %

接下来我们使用Etherscan Blocks提供的真实数据进行计算。

以太坊机制详解:Gas与Gas Prices深度解析

我们计算15406316区块的BaseFee,我们需要参照该区块的上一区块15406315的参数进行计算,我们可以看到上一区块的gasUsedDelta/parentGasTarget+ 11%,计算得到此时15406316BaseFee的值应为6.38 Gwei * 0.11 / 8,计算得到0.885225 gwei,即15406316baseFee应为6.38 * 0.11 / 8 + 6.38,计算得到结果为6.467725,与etherscan给出的相同。

以下给出上一区块Gas总量小于目标总量的代码:

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

根据代码,我们可以得出计算公式如下:

parent.BaseFee - 
max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)

这意味着如果上一区块的Gas总量为0,则当前区块的baseFee为上一区块baseFee87.5 %。我们不再给出具体的计算过程,读者可自行使用Etherscan Blocks提供的数据进行验算。

BaseFee的动态调整可以很好平衡以太坊网络流量,一旦单一区块的交易Gas到达1.5千万,那么根据上述机制,下一区块就会提高BaseFee以增加用户的交易手续费,抑制用户交易。反之,当交易需求不足时,以太坊网络则会降低交易手续费以提高用户的交易欲望。

以太坊机制详解:Gas与Gas Prices深度解析

在上图中,我们可以明显考到这一趋势。在15406535区块出现了交易Gas为0的情况,导致BaseFee下降,在下一区块15406536则出现了大量交易。

我使用了部分区块的数据绘制了以下图像:

以太坊机制详解:Gas与Gas Prices深度解析

在此图像中,条形图展示了区块的大小,而折线图展示了Base Fee的变化,我们可以很明显的看出Base Fee对区块大小的调整作用。

此图主要使用了eth_getBlockByNumberAPI方法获得区块数据。

根据EIP1559规定,baseFee不归属于矿工而会被直接燃烧。这种燃烧行为有效避免ETH通货膨胀。通过Etherscan EIP1559 Dashboard可以获得对应的数据,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

在作者写作此文的过程中,ETHW项目作为以太坊合并后的POS分支废除了EIP1559,很明显,EIP1559没有将所以的手续费分配给矿工的行为不被部分以太坊矿工认可。

Max Priority Fee

在此交易的实例中,我们可以看到Max Priority1 Gwei。相比于上文给出的BaseFee而言,此变量完全由交易者自己规定,而不涉及计算问题。Max Priority FeeBase Fee不同,此手续费完全交给矿工。所以此值越高则意味着被提前打包的概率越大。

此数值可以通过交易内存池(mempool)中的交易数据进行推测,目前市面由很多网站提供Max Priority Fee的参考数值,比如:

  • Etherscan GasTracker
  • BlockNative GasEsmator

我们在此处以BlockNative提供的数据为例,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

BlockNative显示了在当前区块确认交易所需要的Priority FeeMax Fee以及当前区块的Base Fee。关于Max Fee的设置,我们会在下文进行介绍。

此处我们以MetaMask为例(版本为10.18.3),给出EIP1559的设置方法。在进行转账或其他操作时,我们可以点击编辑,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

在弹出页面内选择高级选项,我们就可以手动调整各个参数,如下图:

以太坊机制详解:Gas与Gas Prices深度解析

由于此处为转账操作,所以燃料限制,即Gas Limit21000。其他数值我们可以自行调整。一般来说,MetaMask填入的默认数值是可以直接使用的,但当遇到铸造NFT等场景时,我们可以手动调高Max Priority Fee以提高铸造成功率。

有了以上参数,我们可以计算具体的交易手续费。我们仍是使用示例交易为大家介绍。

我们可以看到此交易的Base7.326319867 Gwei,而Max Priority1 Gwei。将上述两个数累加即gas price,此处计算得到8.326319867 Gwei。然后我们将gas price * gas,即8.326319867 * 45038,得到此交易的手续费为375000.79416994605 Gwei,基本与Transaction Fee的值一致。

Max Fee

我们最后介绍Max Fee。此数值规定交易的最大gas price。可能有读者会疑惑,我们已经设置了Base FeeMax Priority Fee,为什么还需要Max Fee

原因在于用户提交给以太坊节点的交易不一定在下一个区块内完成。如果读者还记得上文给出的BaseFee就知道此数值是随着区块Gas总量不断变化的。假如我们根据区块0计算出下一区块1的BaseFee7 Gwei,同时手动设置了Max Priority Fee1 Gwei,由于我们给出的矿工小费太少,我们的交易会进入打包序列但可能无法在区块1内打包。只能等待区块2进行打包,但极有可能出现区块2的BaseFee7.875 Gwei高于区块1,我们给出的BaseFee小于区块2的BaseFee,此时交易会被直接抛弃,造成交易失败。

如果我们给出Max Fee参数为9 Gwei,当交易进入区块2时,区块2会根据Max Fee计算出我们可以承受的Base FeeMax Fee - Max Priority Fee8 Gwei,此数值大于区块2的Base Fee,交易仍会保存在序列中等待打包。

简单来说,Max Fee的设置可以保证交易不会在未来几个区块内因为Base Fee设置过低问题而被抛出打包序列。此数值设置越高,你的交易会在打包序列中保存的时间越长,避免因手续费问题而交易失败。

比如这个Binance的交易给出了超高的Max Fee,彻底避免在因Base Fee而出现交易失败的问题。

读者可以估计以下自己目标交易在几个区块内完成,然后设置Max Fee。当然,BlockNative提供了一种简单的计算方法,公式如下:

Max Fee = (2 * Base Fee) + Max Priority Fee

这种计算方法可以保证即使用户遇到连续6个满区块(即区块Gas总额均达到3千万)仍可以保证交易不会被提出打包序列。

连续6个满区块会导致相对于当前的BaseFee(1.125)^6,计算可知此倍数为2.027

读者可以根据自己的情况设置Max Fee。但不建议Max FeeBase Fee的值差距较小,这可能会导致交易无法完成。

总结

本篇主要介绍了以下内容:

  • 以太坊中的GasGas PriceTransaction Fee之间的区别
  • EIP1559 中各个参数的计算方法和功能

我们可以通过下图简单总结本文:

以太坊机制详解:Gas与Gas Prices深度解析

如果读者可以很好的理解本篇文章,且想进一步了解以太坊交易,可以参考以太坊机制详解:交易与交易池文章来源地址https://www.toymoban.com/news/detail-439913.html

到了这里,关于以太坊机制详解:Gas与Gas Prices深度解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 点云深度学习系列博客(五): 注意力机制原理概述

    目录 1. 注意力机制由来 2. Nadaraya-Watson核回归 3. 多头注意力与自注意力 4. Transformer模型 Reference 随着Transformer模型在NLP,CV甚至CG领域的流行,注意力机制(Attention Mechanism)被越来越多的学者所注意,将其引入各种深度学习任务中,以提升性能。清华大学胡世民教授团队近期发

    2024年02月10日
    浏览(33)
  • 注意力机制详解系列(一):注意力机制概述

    👨‍💻 作者简介: 大数据专业硕士在读,CSDN人工智能领域博客专家,阿里云专家博主,专注大数据与人工智能知识分享。 公众号: GoAI的学习小屋,免费分享书籍、简历、导图等资料,更有交流群分享AI和大数据,加群方式公众号回复“加群”或➡️点击链接。 🎉 专栏推

    2024年01月25日
    浏览(32)
  • 用信号量机制解决读者-写者问题C语言实现

    文章目录 介绍 一、什么是进程同步,进程互斥 二、读者-写者问题概述 1.概念图 2.实例代码 总结 通过实验模拟读者和写者之间的关系,了解并掌握他们之间的关系及其原理。由此增加对进程同步的问题的了解。具体如下:   1)掌握基本的同步互斥算法,理解读者和写者模型

    2024年02月02日
    浏览(29)
  • 掌控MySQL并发:深度解析锁机制与并发控制

    前一篇MySQL读取的记录和我想象的不一致——事物隔离级别和MVCC 讲了事务在并发执行时可能引发的一致性问题的各种现象。一般分为下面3种情况: 读 - 读情况:并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,不会引起什么问题,所以允许这种情况发生

    2024年02月04日
    浏览(27)
  • 深度解析:算法推荐服务的安全评估与备案机制

    在当今数字化快速发展的时代,算法推荐服务已经成为行业内不可或缺的重要组成部分。这些服务,通常采用复杂的机器学习算法,可以有效地匹配用户需求,提供定制化的内容推荐。然而,算法推荐服务的广泛应用同时也带来了一系列的安全和隐私问题。因此,对这些服务

    2024年02月16日
    浏览(32)
  • 深度解析C++异常处理机制:分类、处理方式、常见错误及11新增功能

    异常是程序在运行过程中出现非正常情况的处理机制。当出现异常时程序会停止运行并调用异常处理程序。 异常可以分为内置异常和自定义异常 2.1 内置异常 C++ 标准库提供了许多预定义的异常类,称为内置异常,包括以下几种: std::exception :所有标准异常类的基类。 std::

    2024年01月18日
    浏览(36)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(39)
  • HTTP与HTTPS:深度解析两种网络协议的工作原理、安全机制、性能影响与现代Web应用中的重要角色

    HTTP (HyperText Transfer Protocol) 和 HTTPS (Hypertext Transfer Protocol Secure) 是互联网通信中不可或缺的两种协议,它们共同支撑了全球范围内的Web内容传输与交互。本文将深度解析HTTP与HTTPS的工作原理、安全机制、性能影响,并探讨它们在现代Web应用中的核心角色。 HTTP 是一种应用层协议

    2024年04月11日
    浏览(47)
  • 计算机网络—TCP协议详解:协议构成、深度解析(1)

                                               🎬慕斯主页 : 修仙—别有洞天                                        ♈️ 今日夜电波: マリンブルーの庭園—ずっと真夜中でいいのに。                                                            0:34━━━

    2024年04月16日
    浏览(27)
  • 计算机网络—TCP协议详解:协议构成、深度解析(3)

                                               🎬慕斯主页 : 修仙—别有洞天                                        ♈️ 今日夜电波: マリンブルーの庭園—ずっと真夜中でいいのに。                                                            0:34━━━

    2024年04月29日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包