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的論壇上也整理了許多從這個事件所記取的教訓及能改進的地方。
評論
查看更多