EIP2930是第一个在EIP2718的基础上,引入的交易类型。
自从有了2718,在以太坊中增加新的交易类型就简单了,目前以太坊中有三种交易类型:
const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType )
其中EIP2930对应就是AccessListTxType。本文简要描述EIP2930的作用。
TL;DR
state access code消耗的gas非常便宜,导致以太坊容易早上攻击,因此EIP2929提高state初始访问(cold access)的gas费,同时引入了缓存机制,降低了后续访问(warm access)state的gas费。
EIP2929带来的问题之一是contract breakage,导致部分在合约中硬编码了gas费的合约不可用。
EIP2930修正了contract breakage问题,可以在tx中指定合约需要访问的state,evm在执行tx之前会打折预加载对应的state,后续对state的访问属于warm access,减少EIP2929带来的cold access问题。
EIP2929
EIP2930是对EIP2929打的补丁。
以太坊中合约的状态存储在磁盘中,每次读取都需要消耗一定的gas费,这些读取操作叫做state access code。问题是,state access code的gas费非常便宜。
在2016年的上海DOS攻击中,其中几个攻击的手法就是透过恶意交易大量读取帐户资讯、大量的创造合约再销毁,或是不断用 EXTCODESIZE 来读合约大小等等,让Client必须花大量的IO资源处理交易(需要读写disk的动作特别慢)。
EIP2929的思路就是提高state access code的gas消耗,比如以前读取一次state,消耗gas 800,现在提升到2100,提高了攻击成本。但是提高gas消耗对普通用户不利,因此EIP2929中引入了缓存机制,第一次读取状态,叫做cold access,收取2100,读取的状态缓存起来,后续再读取,叫做warm access,只需收取100,读区的次数越多,消耗的gas反而便宜了。
就相当于天冷了发动机第一次启动不太容易,启动后再重启就容易多了。
其中读取的状态缓存在以下存储中:
- accessed_addresses。缓存合约地址
- accessed_storage_keys。缓存合约数据
EIP2929的引入,会引起部分合约的gas费增高。对于普通用户还好,但以太坊中存在大量的合约调用合约,比如A合约代码中,调用了B合约,开发者往往会硬编码写死调用B合约的gas费。
这样就带来了麻烦,EIP2929的引入,比如原本B合约需要800gas,现在变成了2100gas,而A代码已经写死了B合约的调用费用为800gas,会导致B合约调用的失败,进而导致已经部署的A合约不可用,这种情况叫做contract breakage。
于是就诞生了EIP2930,缓解这种情况,叫做Contract breakage mitigation。
EIP2930
EIP2930目的是为了在Tx执行之前,预先执行cold access。解决contract breakage问题。
EIP2930新增了一种Tx类型,其中包含AccessList字段:
type AccessListTx struct { ChainID *big.Int // destination chain ID Nonce uint64 // nonce of sender account GasPrice *big.Int // wei per gas Gas uint64 // gas limit To *common.Address `rlp:"nil"` // nil means contract creation Value *big.Int // wei amount Data []byte // contract invocation input data AccessList AccessList // EIP-2930 access list V, R, S *big.Int // signature values }
传统的Tx在执行过程中,根据实际操作执行扣除gas费。而AccessList内部包含了accessed_addresses和accessed_storage_keys,说明本Tx可能会涉及到读取哪些合约,以及合约状态。那么系统在执行Tx之前,预先把指定的状态读取出来,缓存起来,然后再正常执行Tx,执行过程中需要扣费的话,属于warm access,费用就降低了。
还是前面A合约调用B合约的例子,假设A合约在引入EIP2929之前就部署在了以太坊上,其中调用了B合约,B合约会从存储中读取状态S1,消耗800gas,于是A合约中有如下一段伪代码:
callContractB(800)
即在A合约中,硬编码了800 gas来调用B合约。
那么,在引入EIP2929之前,读取S1的gas固定为800,tx执行为步骤为:
- 用户发送调用A合约的tx, gas limit为3000(或者任何大于800的值)。
- EVM开始执行tx。
- A合约调用B合约,发送800gas。
- B合约从存储中读区状态S1,消耗800gas。执行成功。
- A合约执行成功。总消耗gas: 800,返回用户gas: 3000-800=2200。
引入EIP2929之后,读取S1的gas发生变化:cold access为2100,warm access为100。tx执行步骤为:
- 用户发送调用A合约的tx, gas limit为3000。
- EVM开始执行tx。
- A合约调用B合约,发送800gas。
- B合约从存储中读区状态S1,消耗2100gas(cold access)。执行失败。
- A合约执行失败。
可以看到,虽然用户设置了gas为3000,大于B合约需要的2100,但由于硬编码的缘故,A只给了800gas调用B,导致合约调用失败。而且这样的合约在链上大量存在。毕竟,当初A合约创建的时候,并没有EIP2929。
于是引入EIP2930,读取S1的gas依然为:cold access为2100,warm access为100,tx执行步骤为:
- 用户发送调用A合约的tx, gas limit为3000。并且在Tx的AccessList中指定了要访问的状态S1。
- EVM执行tx之前,预先往缓存中加载了S1,扣除gas 1900(没错,扣除1900,而不是2100,因为通过EIP2930的方式加载缓存比直接cold access更便宜)
- A合约调用B合约,发送800gas。
- B合约从存储中读区状态,消耗100gas(此时属于warm access,只需要100,而不是2100)。执行成功。
- A合约执行执行成功。总消耗gas: 1900+100=2000。返回用户gas:3000-1000=2000。
总结
- EIP2919提高了state access code的初始访问费用,降低了多次访问费用。
- EIP2930缓解了EIP2919带来的Contract breakage问题。
Ref
回复 agodelo 取消回复