自從看到了這篇文章,我便一直很想深入了解以太坊的無狀態(tài)客戶端。當然,經過15個月的摸索,我對以太坊中的狀態(tài)、軟件、網絡的認知都發(fā)生了很大的變化,比如說,我現在認為要引入無狀態(tài)客戶端,則硬分叉在所難免;這與我之前的想法不同。但即便如此,我還是很高興能分享一些學習上的收獲,并給出后續(xù)發(fā)展建議。
無狀態(tài)客戶端的思想
在闡述無狀態(tài)客戶端之前,我們先來看看在以太坊客戶端軟件中一般的交易處理過程:
在區(qū)塊中執(zhí)行交易需要交易數據(藍色矩形所示),及當前狀態(tài)(黃色矩形所示);執(zhí)行結束后,原本的當前狀態(tài)變?yōu)闅v史狀態(tài),同時產生新的當前狀態(tài)。處理交易可能還需要一些別的數據(如區(qū)塊時間戳,或是前個區(qū)塊哈希值),但我們先忽略這些大小固定且無關緊要的部分。執(zhí)行交易還會產生明細(receipt)(綠色部分),但在討論無狀態(tài)客戶端時也可暫時忽略。
無狀態(tài)客戶端的核心思想是:在區(qū)塊中執(zhí)行交易過程時,不訪問整個狀態(tài)。區(qū)塊創(chuàng)建者以分離的數據結構作為區(qū)塊補充,里面提取了執(zhí)行交易所需的所有狀態(tài);為了讓執(zhí)行交易的人相信這些額外數據的確來自當前狀態(tài),還要附上默克爾證明。這是可以做到的,因為每個區(qū)塊頭都帶有 “狀態(tài)根”,這個默克爾樹根凝結了區(qū)塊內交易執(zhí)行完成后所有的狀態(tài)值。有了這些證明,我們能夠以下面這種形式執(zhí)行交易:
值得一提的是,這些區(qū)塊信息(我們稱之為 “區(qū)塊證明”)對于有狀態(tài)客戶端(那些想要保有全部狀態(tài)和歷史數據的客戶端)來說也是非常有用的。當前情況下,所謂的 “全節(jié)點” (在接收到區(qū)塊時)會依憑本地保存的狀態(tài)執(zhí)行區(qū)塊交易,計算新狀態(tài)的默克爾根并驗證是否與區(qū)塊頭的默克爾樹根相同(譯者注:以此驗證新區(qū)塊的合法性)。計算默克爾樹根需要大量的計算資源,包括大量內存以及大量 I/O 讀寫,詳細的描述見此。如果擁有前面的補充信息,全節(jié)點驗證執(zhí)行過程的正確性會變得非常容易,大部分情況下將不需要涉及讀寫操作,就能夠基于執(zhí)行結果更新當前和歷史的狀態(tài)數據庫;全節(jié)點無需緩存狀態(tài)樹,與當前狀態(tài)和歷史狀態(tài)數據庫的交互轉為以寫入為主。具體過程如下:
區(qū)塊證明占多大空間?
無狀態(tài)客戶端最大的缺點是——除了區(qū)塊,還需要在網路中傳輸一些額外的數據(區(qū)塊證明)。究竟這些額外的數據有多少呢?為了計算區(qū)塊證明的大小,我基于 Turbo-Geth 創(chuàng)建了一個無狀態(tài)客戶端原型,以下是它做的事情:
1. 從以太坊主網逐一收集區(qū)塊
2. 對每一個區(qū)塊分別抽取出交易需要訪問的狀態(tài),以及被交易訪問的智能合約字節(jié)碼
3. (通過計算狀態(tài)默克爾樹根)選取出足以驗證所提取狀態(tài)確實屬于狀態(tài)的最少哈希值集合
4. 添加一些結構信息,將上述證明(哈希值集合)和狀態(tài)抽取物編碼為樹型結構??赡懿捎玫木幋a方式不是最好的,不過結果表明編譯出來的輸出遠小于其他部分,所以可接受。
5. 將區(qū)塊證明編碼為字節(jié)數組。
6. 將字節(jié)數組解碼出區(qū)塊證明(即那個樹型結構的數據,一些節(jié)點來自于狀態(tài),一些則是無關狀態(tài)部分的哈希值)。
7. 再次執(zhí)行區(qū)塊,不過這回不訪問當前狀態(tài),而是直接與區(qū)塊證明進行交互。
8. 驗證狀態(tài)根和所有存儲根的正確性。
整個流程花了不少時間,但我希望得到的數據足夠精確。如我所料,雖然這個原型已經能處理許多瑣碎的問題(因為 hexary 的默克爾帕特里夏樹包含葉節(jié)點、擴展節(jié)點、嵌入節(jié)點等等復雜的結構),還是有一些罕見情況導致我的原型報錯(在區(qū)塊 5340939、5361803、5480357、5480507、5480722、5632299、5707052、5769636 。..。.. );不過考慮到 670 萬個區(qū)塊中只出現數十個報錯,我有信心這些小問題不會影響數據分析的結果(當然,我會盡快 debug)。
目前我的數據只采集至第6757045 個區(qū)塊,但我想很快就能超過這個數字。
第一張圖表表示區(qū)塊證明的總體量(注:所有圖表都經過窗口 = 1024 的移動平均計算)。
在偽龍硬分叉(區(qū)塊 2675000)之前,少量的 gas 消耗也可能產生非常大的區(qū)塊證明, 這個缺陷已經在 2016 年秋天被修復。
下面我們只看偽龍硬分叉后至今的數據表現:
為了清除余額和 nounce 值為零的賬戶狀態(tài),偽龍硬分叉后進行 “狀態(tài)清理”,導致圖表最左側 “峰值” 的出現。但 2017 年下半年,以及 2018 年的 “區(qū)塊證明” 的規(guī)模已經超過了當時的水平。
分解
首先,我們將區(qū)塊證明分解為三個部分:1)所有與 “主” 狀態(tài)樹有關的部分;2)所有與合約存儲樹有關的部分;3)智能合約字節(jié)碼。
可能從圖上有點看不清楚,不過可以看到 2016 年發(fā)生的垃圾攻擊造成當時的字節(jié)碼(紅線)和 “主” 狀態(tài)樹的區(qū)塊證明(黃線)激升。有些人還記得,當時對于智能合約的字節(jié)碼大小并沒有限制(現在限制小于 24k),造成當時能夠部署超大的智能合約,并通過 EXTCODESIZE 之類的函數進行查詢。
實施偽龍硬分叉后的圖表表明,主默克爾樹的區(qū)塊證明在過去大部分時候體量占比較大,不過合約存儲證明(藍線)和字節(jié)碼隨后開始趕上。
分解狀態(tài)樹區(qū)塊證明
這里我們會進一步將狀態(tài)樹區(qū)塊證明分解為四個部分。前兩部分是交易需要讀取的鍵值對,或者是為了滿足默克爾帕特里夏樹的一些特殊需求所需的數據。
看起來狀態(tài)清除操作對于讀取鍵值對還是造成很大的影響。我們會發(fā)現鍵值對中值的大?。ㄒ话闶?80 字節(jié))通常比鍵要大得多( key 一般小于 64 字節(jié),而我剛剛才意識到鍵可以被壓縮,因為我把每個十六進制數都算為一個字節(jié))。
接下來,我們看看用來建構默克爾證明的哈希值,以及那些我稱之為 “mask(掩碼)” 的結構化信息:
可以輕易發(fā)現,相比于哈希值,結構化信息的大小是可以忽略不計的;也可以進一步說明哈希值占區(qū)塊證明大小的主要組成部分(對于合約存儲證明來說也是如此)。
分解合約存儲證明
與前面相同,首先是鍵值對:
有意思的是,合約存儲證明中鍵值對的值(最多占 32 字節(jié))通常遠小于鍵。
接著是哈希值和結構化信息:
可以看到,與狀態(tài)樹區(qū)塊證明的表現相似。
與狀態(tài)費用研究的相關性
我搞這個研究的其中一個目的是想弄清楚使用無狀態(tài)客戶端是否能規(guī)避某些類型的租金(譯者注:所指應是 “狀態(tài)存儲租金”)。因為當前最大的挑戰(zhàn)在于:合約存儲租金的存在,可能會導致許多現有的合約遭受 griefing 攻擊(譯者注:大意為盡管不能讓攻擊者受益,但會讓受害者感覺很苦惱的攻擊形式)。為了保護智能合約避免遭此類攻擊,“狀態(tài)費用” 協(xié)議第三版提出了費用預付(押金)的概念,能暫時保護合約免受攻擊,給已部署合約提供一些緩沖時間,能夠升級為不受影響的代碼。
無狀態(tài)客戶端的構想,至少在讓智能合約存儲免受 griefing 攻擊方面(如上文所示),提出了一種成本雖高但永久有效的預防方法。
不過如果真的引入某種無狀態(tài)客戶端,交易發(fā)送方無可避免的必須針對交易產生的區(qū)塊證明,按比例支付額外的 gas,這樣一來合約存儲操作(SLOAD、SSTORE)的成本可能比現在高昂。附帶一提,Martin Swende 最近的分析表明 SLOAD 費用似乎被嚴重的低估。
我們可以將無狀態(tài)客戶端用于已有的智能合約,并讓新的智能合約自主選擇是否使用存儲租金。我估計這個租金會比使用 “區(qū)塊證明” 的成本便宜許多,但總有一天這個臨時費用會消失(因為大家都使用無狀態(tài)客戶端啦)。我們不會強迫已有的智能合約在某個時間點必須轉變?yōu)榧嫒菘蛇x費用的部署形式,相反地我們希望以激勵手段鼓勵大家進行遷移。
后續(xù)的建議
顯然,我要增加數據收集至當前區(qū)塊,并修復數據分析中存在的 bug 。
即使我們去掉與合約儲存相關的部分,區(qū)塊證明的大小仍是個值得重視問題。我認為有兩種方案能降低它的大小:
1. 在無狀態(tài)客戶端中引入多一點的 “富狀態(tài)性”。如果大多數客戶端都持有最新的 N 個區(qū)塊證明(N = 1, 2, 3 。..),現在你(以太坊節(jié)點之一)知道你的對等節(jié)點保有許多以前的區(qū)塊證明,那么你可能會選擇只發(fā)送后 N 個區(qū)塊證明中不包含的部分。因為你知道節(jié)點有能力重構完整的狀態(tài)證明。這個方案還需要經過更多分析驗證。
2. 使用 SNARK 證明,將現在區(qū)塊證明中可變的 “哈希值” 部分壓縮為固定大?。ň臀宜?,目前 SNARK 產生的證明大小約 60k 左右)。這個方案還需要投入更多的研究和開發(fā)工作,比如圍繞 Keccak256 算法創(chuàng)建 SNARK 證明,要考慮證明需要付出多少成本,是否需要用 CPU 加速證明過程、保證出塊速度足夠快等等。
評論
查看更多