学习以太坊Gas机制

2024-02-04 01:59
文章标签 学习 机制 以太 gas

本文主要是介绍学习以太坊Gas机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Gas

基本概念

为了避免网络滥用及回避由于图灵完备而带来的一些不可避免的问题(the halting problem),在以太坊中所有的程序执行都收费。Gas是基本的工作量成本单位,用于计量在以太坊区块链上执行操作所需的计算、存储资源和带宽,其目的是限制执行交易所需的工作量。各种操作的费用以gas为单位计算。任意的程序片段(包括合约创建、消息调用、分配资源以及访问账户storage、在虚拟机上执行操作等)都有一个普遍认同的gas成本。[1] Gas有两个作用[5]:

  • 以太坊(不稳定的)价格和矿工工作报酬之间的缓冲
  • 对拒绝服务(DoS)攻击的防御.

每一个交易都要指定一个 gas 上限:gasLimit。发送者通过在交易中指定gas price来购买gas,系统预先从发送者的账户余额中扣除gasLimit * gasPrice的交易费,即采用预付费机制。Gas price是指当你将交易发送到以太坊网络时,愿意支付的每单位gas的价格。[5]如果账户余额不足,交易会被视为无效交易。[1]之所以将其命名为 gasLimit,是因为剩余的 gas会在交易完成后被返还(与购买时同样价格)到发送者账户。每个矿工自己选择他们想要接受和拒绝的gas价格。交易者们则需要在降低 gas 价格和使交易能尽快被矿工打包间进行权衡。
通常来说,以太币(Ether)是用来购买 gas 的,未返还的部分就会移交到 beneficiary 的地址(即一般由矿工所控制的一个账户地址)。以太币最小的单位是 Wei(伟),所有货币值都以 Wei 的整数倍来记录。[1]
ether-wei
注意:Gas只存在于EVM中,用来给计算的工作量计数。发送方用ether支付交易费,然后将其转换为gas用于EVM核算,最后将剩余的gas转换为ether返还给发送方,未返还的同样转换为ether作为交易费付给矿工[5]。

block gas limit

block gas limit是一个块中所有交易可以消耗的最大gas量,并且限制了一个块中可以容纳多少个交易。如果矿工试图包含一个需要比block gas limit更多gas的交易,则该块将被网络拒绝。[5]
以太坊采用投票系统来设定block gas limit。网络上的矿工共同决定block gas limit。以太坊协议有一个内置的机制,矿工可以对block gas limit进行投票,从而增加或减少后续区块的容量。矿工有权将当前区块的gas限定值设定在最后区块的gas限定值的0.0975% (1/1024)内[3]。所以最终的gas限定值应该是矿工们设置的中间值。

Gas成本的确定

EVM可执行的各种操作的相对gas cost经过精心设计,以最好地保护以太坊区块链不受攻击。操作进行的计算越多,gas成本越高[5]。
2016年,一名攻击者发现并利用了gas成本与实际资源成本不匹配的问题,证明了将gas成本与实际资源成本相匹配的重要性。 这个问题通过一个硬分叉(代号为“橘子口哨”,EIP 150)解决,它通过改变IO重型操作长期的gas费率来抵抗垃圾交易攻击,并增加了63/64规则。

Gas收费情况

三种情况下会收取执行费用(以gas来结算)[1]:

  1. 最普遍的情况就是计算操作费用。
  2. 执行一个低级别的消息调用或者合约创建可能需要扣除 gas,这也就是执行 CREATE,CALL和CALLCODE 的费用的一部分。
  3. 内存使用的增加也会消耗一定的 gas。
    gas cost

gas消耗计算还有以下特点[3]:

  • 对于任何交易,都先收取21000 gas的基本费用(base fee)。这些费用可用于支付运行椭圆曲线算法(该算法旨在从签名中恢复发送者的地址)以及存储交易所花费的硬盘空间和带宽所需的费用。

  • 交易可以包括无限量的“数据”。虚拟机中的某些操作码,可以让合约允许交易对这些数据的访问。数据的固定费用(intrinsic gas)计算:每个零字节4 gas,非零字节68 gas。

  • 合约提供的消息数据是没有成本的。因为在消息调用期间不需要实际复制任何数据,调用数据可以简单地视为指向父合约内存的指针,该指针在子进程执行时不会改变。

  • 某些操作码的计算时间极度依赖参数,gas成本是动态变化的。例如,EXP的的开销是指数级别的(ie. x^0 = 1 gas, x^1 … x^255 = 2 gas, x^256 … x^65535 = 3 gas, etc)。

  • 如果操作码CALL(以及CALLCODE)的值不是零,会额外消耗9000 gas。这是因为任何值传输都会引起归档节点的历史存储显著增大。请注意,实际消耗是6700,在此基础上,以太坊强制增加了一个自动给予接收方的gas值,这个值最小是2300。这样做是为了让接受交易的钱包至少有足够的gas来记录交易。

对于一个账户的执行,内存的总费用和其内存索引(无论是读还是写)的范围成正比;这个内存范围是32字节的倍数,不足32字节以32字节计。这是实时(just-in-time)结算的;也就是说,任何对超出先前已索引的内存区域的访问,都会实时地结算为额外的内存使用费。
存储费用则有一个细微差别——激励存储的最小化使用。清除一个存储中的记录项或账户不仅不收费,而且还会返还一定gas作为奖励。[1] EVM中有两种操作会出现这种情况,具体可以参考SSTORE操作码的gas计算函数(core/vm/gas_table.go/gasSStore)[5]:

  1. 删除一份合约(自毁)将会得到奖励。
  2. 将一个存储地址的非零值改为零(SSTORE[x] = 0)可以获得退款。

为了避免退款机制被利用,每笔交易的最高退款额被设定为gas总用量的50%(向下取整)[5]。这种退款机制会激励人们清理存储器。正因为缺乏这样的激励,许多合约并未有效使用存储空间,从而导致存储快速膨胀。这样既获得了存储收费的大部分好处,又不会失去合约一旦确立就可以永久存在的保证。延迟退款机制是必要的,因为可以防止拒绝服务攻击。攻击者发送一笔含有少量gas的交易,循环清理大量的存储,直到用光gas,这样消耗了大量的验证算力,但实际并没有真正清理存储也没有花费大量gas。50%的上限是为了确保:给定一个具有一定数量gas的交易,矿工依然可以根据gasLimit确定用于执行此交易的计算时间上限。[3]

gas在执行过程中的使用

当EVM需要完成一个交易时,它首先被给予一个等于交易中gas limit所指定数量的gas supply。执行的每个操作码都有一个gas成本,因此EVM的gas supply会随着程序向前一步步执行而逐渐减少。在每个操作之前,EVM检查是否有足够的gas来支付操作的执行费用。以太坊在操作执行前收取费用。如果没有足够的gas,EVM就会停止执行并将本次交易修改的状态回滚。[5]
gas and fee

  • 如果EVM成功完成了执行,并且没有耗尽gas,则使用的gas将作为交易费支付给矿工,并根据交易中指定的gas价格转换为ether,即交易费= gas used * gas price。gas supply中剩余的gas将退还给发送方,同样是根据交易中指定的gas价格转换为ether。

  • 如果交易在执行期间“耗尽gas”,操作将立即终止,抛出“out of gas(OOG)”异常。交易被恢复,对状态的所有更改都回滚。

虽然交易没成功执行,但发送方仍需支付交易费,因为到那时为止,矿工已经执行了计算工作,必须为此进行补偿。[5] 收取的交易费为发送方提供的全部gas,即gas limit * gas price。当一个合约发送消息给另一个合约,可以对这个消息引起的子执行设置一个gas限制。如果子执行耗尽了gas,则子执行被恢复,但gas仍然消耗。[3]

gas相关源代码(geth)

gas成本定义和指令gas成本的计算代码集中在core/vm/gas.gocore/vm/gas_table.go两个文件。
core/vm/gas.go

// Gas costs
const (GasQuickStep   uint64 = 2GasFastestStep uint64 = 3GasFastStep    uint64 = 5GasMidStep     uint64 = 8GasSlowStep    uint64 = 10GasExtStep     uint64 = 20
)// calcGas returns the actual gas cost of the call.
// calcGas返回实际用于调用的gas成本。。
//
// The cost of gas was changed during the homestead price change HF.
// As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64.
// 在homestead价格变动中,gas成本发生了变化。 HF ??
// 作为 EIP 150 (TangerineWhistle 橘子口哨硬分叉)的一部分,返回的gas = (gas - base) * 63 / 64.
//
// EIP150是通过重新调整gas价格彻底解决DoS问题的硬分叉主要备选方案。
// “针对IO重型操作长期的gas费率改变以抵抗垃圾交易攻击” https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
// 添加规则:一个调用的子调用不能消耗超过父调用剩余gas的63/64。也就是说,如果调用者最初投入了数量为 a 的 gas, 在 10 层递归调用后,最内层的函数最多只有 (63/64)^10*a 的 gas.
// 有两个目的:(Rationale 片段)
// 1. 用一个更软的基于gas的限制("softer" gas-based restriction)取代最大调用栈深度的“硬限制”,这将使得深度调用需要的gas数量呈指数增长。
//    这将堆栈深度限制攻击这一个类别的攻击彻底从合约开发者需要担心的问题清单中剔除,从而提高了合约编程的安全性。
// 2. 把事实上的最大堆栈调用深度从1024减少到约300,在一定程度上缓解未来客户端受二次Dos攻击的可能。
func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint64, error) {if isEip150 {availableGas = availableGas - basegas := availableGas - availableGas/64 // gas = (availableGas - base) * 63 / 64// If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150// is smaller than the requested amount. Therefor we return the new gas instead// of returning an error.// 如果位长超过64位,我们知道新计算的EIP150的“gas”要比请求的量小。// 因此,我们返回新的gas,而不是返回一个错误。// 若callCost超过64位,条件直接成立,不用比较即返回gas;// 若callCost不超过64位,新计算的gas肯定小于CallCost,仍返回gas。if !callCost.IsUint64() || gas < callCost.Uint64() {return gas, nil}}if !callCost.IsUint64() {return 0, errGasUintOverflow}return callCost

这篇关于学习以太坊Gas机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/676071

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识