Constantinopole硬分叉介绍
扫描二维码
随时随地手机看文章
Constantinopole预计于区块708000(2019/1/16)进行主链的分叉。此次硬分叉除了新的CREATE2 opcode,并没有带来太多让人振奋的新东西。
ConstanTInopole硬分叉所包含的EIP如下:
EIP 145: 在EVM里新增Bitwise shifTIng的opcode
EIP 1014:新增CREATE2 opcode
EIP 1052:新增EXTCODEHASH opcode
EIP 1283:调整SSTORE opcode的计价方式
EIP 1234:延后难度炸弹并调降区块奖赏
以下没有按照号码而是从简单的EIP开始介绍。
EIP 1234:延后难度炸弹并调降区块奖赏
基本上和上次的拆解差不多—延后一年并调降奖赏。
其中有三个提案:
1. 将奖赏由原本的3 ETH降到1 ETH
2. 将奖赏降到2 ETH
3. 维持3 ETH但调整uncle reward并移除nephew reward以借此降低ETH发行量。
最后由第二个方案胜出。
EIP 1052:新增EXTCODEHASH opcode
由于很多情况在合约里会限制使用者必须是单纯的帐户(External Owned Account,EOA)或是一个合约,这时我们需要去检查该地址是不是一个合约。或是希望和我们进行互动的合约要符合特定的模式,从而避免和未知、恶意的合约来往,这时我们需要去取回该地址的合约并检查。
如果是要单纯检查该地址是不是合约,则使用EXTCODESIZE这个opcode来取得指定地址的合约代码大小,若回传结果不为零,代表该地址是一个合约。
如果要检查合约内容是不是我们预期的,则透过EXTCODECOPY这个opcode来取回指定地址的合约代码,把回传结果拿去做哈希,并把哈希结果和我们事先存在合约里的哈希值做比对。
EXTCODESIZE花费700 gas,而EXTCODECOPY除了基本的700 gas之外,还要加收把合约代码复制到內存的成本(每四个byte收3 gas),所以若是对方合约代码非常庞大,则每次要做这个检查都要额外再花费不少gas。
因此提出了EXTCODEHASH opcode,直接回传指定地址的合约代码的哈希值。
其opcode值为0x3F,固定花费400 gas,如果指定位址不存在(即在链上没任何数据),则回传0;如果存在但是是单纯的帐户(EOA),则回传对empty data做哈希的结果。
EIP 145:在EVM里新增Bitwise shifTIng的opcode
目前EVM里没有原生的左右移运算子,而是要靠其他算术运算子组合来模拟(虽然在Solidity里有平移运算的写法,但实际编译的bytecode是用指数搭配乘法/除法来模拟的)。而每一次的模拟共需要花费35 gas。
这个EIP新增了左移(SHL)、逻辑右移(SHR)和算术右移(SAR)三个指令,三个opcode的值分别为0x1b 0x1c 0x1d,每个运算都只需花费3 gas。
EIP 1283:调整SSTORE opcode的计价方式
这个EIP是以EIP 1087为基础做的改进,目的都是让某些情况下使用storage的成本能更合理、更便宜。
目前SSTORE opcode的计价方式是
1. 从0设为非0的值:20000 gas
2. 从非0修改为非0:5000 gas
3. 从非0改为0:-10000 gas(即退还)
如果一个交易里对同一个storage slot做了多次改动,则以目前的计价方式,每一次改动都会被收费,即便交易完成后它们才会被一次写入磁盘里(对storage做的改动只有在交易成功完成后才会写入磁盘里)。
所以假设你的交易在过程中
· 对某个原本为0的storage slot做了5次修改,则你会被收取20000 + 5 * 5000 = 45000 gas
· 或是把某个storage slot从0改为非0,又从非0改为0(例如Mutex的使用),则你会被收取20000 + 5000–10000 = 15000 gas
· 或是做了两次的代币交换,把代币从A身上转到B,再转到C身上,则你会被收取5000 * 4 = 20000 gas
EIP 1087的做法
在每笔交易执行的时候,建立一个暂时的对应表来记录每个被修改到的storage slot,交易的最后再一次结算(看是收费还是退费)。
EIP 1283的做法
在每一次SSTORE执行时,比对storage slot的
(1)原始值(即交易执行前这个storage slot的值)
(2)目前的值(即交易执行到一半,当下该storage slot的值,可能和原始值相同,也可能不同)
(3)新的值(即这个SSTORE动作会赋予该storage slot的值)如果需要退还gas,则当下就会记录该退还多少,而不是等到交易的最后。
两者的不同
EIP 1087和EIP 1283的不同在于,EIP 1087是以整个交易为一个单位,建立一个对应表,交易执行的过程中每个SSTORE执行时会来对这个表做修改,最后再做结算;而EIP 1283则是以每个SSTORE为一个单位,有可能当下这个SSTORE就是该笔交易最后一个SSTORE了,也有可能不是,但每个SSTORE执行完都会去做结算的动作。
而EIP 1283的优点在于不需要额外建立并维护一个对应表,而且不需要对原本客户端的SSTORE代码做太大修改,只需要新增一些判断式而已。
注:两个做法在每次SSTORE执行时都会收取最基本的200 gas(看修改的内容和原始值而定,有可能是收取5000 gas或20000 gas,但最少就是200 gas)。
将新的计价方式套用在上面的例子,假设你的交易在过程中
· 对某个原本为0的storage slot做了5次修改,则你会被收取20000 + 5 * 200 = 21000 gas
· 或是把某个storage slot从0改为非0,又从非0改为0,则你会被收取20000 + 200–19800= 400 gas
· 或是做了两次的代币交换,把代币从A身上转到B,再转到C身上,则你会被收取5000 * 3 + 200–4800= 10400 gas
EIP 1014:新增CREATE2 opcode
原本Metropolis规划的新功能 — 帐户抽象化(Account AbstracTIon)除了CREATE2 opcode本身,还包含了ENTRY POINT(是一个帐户,地址是0xfff…fff,可以把它视为系统的代理人)。
Account Abstraction
在Account Abstraction的世界中,没有EOA,每个帐户都是一个合约。每个人需要为自己建立一个合约来处理交易的细节(而不再是由系统去帮你处理):这个合约要持有足够的以太币(或代币)以支付交易手续费、要验证合约主人的身份、要记录并检查nonce值(也可以不做nonce值检查)、要选择使用哪种签章或加密算法(而不是像现在只能用ECDSA)。
注:需要nonce值来避免交易重放攻击(transaction replay attack)的使用者可以自己在合约里实现这个功能:在合约内自己记录一个nonce值、或甚至多个。
假设今天你要产生一笔用来发送代币的交易,这笔交易的发送者要填上ENTRY POINT(而不是你的帐户),交易的接收者才是填入你的合约地址,交易内容会包含你身份的证明(例如签章)、要送到代币合约的相关数据等等,但不会附带Ether(因为ENTRY POINT本身也不会有钱),也不会有发送者对这笔交易的签章(原本这种签章是用来证明的确是发送者产生这笔交易的,因此交易如果不再附带这种签章则表示任何人都可以伪造其他人交易)。
矿工收到这笔交易后会先去试跑这笔交易做检查,看你的合约会不会付手续费、看交易内容里的数据有没有通过你自己设的身份检查和nonce值检查,检查都通过后就会去呼叫代币合约发送代币。
Account Abstraction的问题
Account Abstraction大大提升了使用者的自由度,但也大大提高了使用的门坎,使用者和开发者得要习惯全新一套运作的机制。另外因为任何人都可以伪造交易,矿工要先试跑每一笔交易来确认是否会收到手续费,这大大增加了矿工被无效交易DoS攻击的可能性。
目前ENTRY POINT的功能被推延到未来的升级中。
CREATE2
留下来的CREATE2 opcode基本上和原本的CREATE opcode差异不大,目的都是用来产生新的合约,但不同的地方在于,藉由CREATE2你可以自由地控制合约产生的地址。CREATE2的opcode值为0xF5。
CREATE计算新的合约该部署在哪个地址的时候会受到交易发起人(即sender)和发起人当下的nonce值决定:
new_address =
keccak256( 0xff ++ sender ++ salt ++ keccak256(init_code)))[12:]
· 0xff是用来区分新旧地址产生的方式,旧的方式因为会先对sender和nonce做rlp编码,编码完后最前面会是一个0x01~0xff之间的值,但0xff只有在被编码的数据超级大才有可能出现,所以新的方式在最前面加了一个0xff来和旧的方式做区分。
· init_code是你要部署的合约的代码。
· salt是使用者可以任意指定的值,让你可以把同一份合约部署在不同地址(如果没有这个salt值,而且大家部署的init_code又都一样,则大家都会部署到相同的地址)。
有了CREATE2之后,你可以预先知道你的合约会部署到哪个地址且不用耗费太多成本,这对许多应用有不小的影响,像是State Channel或是用来躲避Front Running攻击的Submarine Send。
测试链(Testnet)测试状况
这次部署到Ropsten测试链进行硬分叉预演的时候出了点差错而导致测试链出现多条分叉:
· Parity和Geth在计算SSTORE的gas收费上有不同(是Parity这边的问题)
· 原先的测试链使用者没有更新版本
· Parity和Geth各自实作了不同的内存块回溯(revert)限制(可以视为他们自己加上的Finality条件),所以Parity的分叉链在距离分叉点超过一定数量的内存块后就再也没办法切换回正确的链了(除非删掉原本的链的数据并重新同步一次)。
不过这次的插曲也凸显了在测试链预演、同时有不同的客户端软件参加的重要性。Lane Rettig在Ethereum Magicians的论坛上也整理了许多从这个事件所记取的教训及能改进的地方。