以太坊無狀態客戶端初探

買賣虛擬貨幣
自從看到了這篇文章,我便一直很想深入瞭解以太坊的無狀態客戶端。當然,經過 15 個月的摸索,我對以太坊中的狀態、軟體、網路的認知都發生了很大的變化,比如說,我現在認為要引入無狀態客戶端,則硬分叉在所難免;這與我之前的想法不同。但即便如此,我還是很高興能分享一些學習上的收穫,並給出後續發展建議。無狀態客戶端的思想

在闡述無狀態客戶端之前,我們先來看看在以太坊客戶端軟體中一般的交易處理過程:

在區塊中執行交易需要交易資料(藍色矩形所示),及當前狀態(黃色矩形所示);執行結束後,原本的當前狀態變為歷史狀態,同時產生新的當前狀態。處理交易可能還需要一些別的資料(如區塊時間戳,或是前個區塊雜湊值),但我們先忽略這些大小固定且無關緊要的部分。執行交易還會產生明細(receipt)(綠色部分),但在討論無狀態客戶端時也可暫時忽略。

無狀態客戶端的核心思想是:在區塊中執行交易過程時,不訪問整個狀態。區塊建立者以分離的資料結構作為區塊補充,裡面提取了執行交易所需的所有狀態;為了讓執行交易的人相信這些額外資料的確來自當前狀態,還要附上默克爾證明。這是可以做到的,因為每個區塊頭都帶有 “狀態根”,這個默克爾樹根凝結了區塊內交易執行完成後所有的狀態值。有了這些證明,我們能夠以下面這種形式執行交易:

值得一提的是,這些區塊資訊(我們稱之為 “區塊證明”)對於有狀態客戶端(那些想要保有全部狀態和歷史資料的客戶端)來說也是非常有用的。當前情況下,所謂的 “全節點” (在接收到區塊時)會依憑本地儲存的狀態執行區塊交易,計算新狀態的默克爾根並驗證是否與區塊頭的默克爾樹根相同(譯者注:以此驗證新區塊的合法性)。計算默克爾樹根需要大量的計算資源,包括大量記憶體以及大量 I/O 讀寫,詳細的描述見此。如果擁有前面的補充資訊,全節點驗證執行過程的正確性會變得非常容易,大部分情況下將不需要涉及讀寫操作,就能夠基於執行結果更新當前和歷史的狀態資料庫;全節點無需快取狀態樹,與當前狀態和歷史狀態資料庫的互動轉為以寫入為主。具體過程如下:

區塊證明佔多大空間?

無狀態客戶端最大的缺點是——除了區塊,還需要在網路中傳輸一些額外的資料(區塊證明)。究竟這些額外的資料有多少呢?為了計算區塊證明的大小,我基於 Turbo-Geth 建立了一個無狀態客戶端原型,以下是它做的事情:

1. 從以太坊主網逐一收集區塊
2. 對每一個區塊分別抽取出交易需要訪問的狀態,以及被交易訪問的智慧合約位元組碼
3. (透過計算狀態默克爾樹根)選取出足以驗證所提取狀態確實屬於狀態的最少雜湊值集合
4. 新增一些結構資訊,將上述證明(雜湊值集合)和狀態抽取物編碼為樹型結構。可能採用的編碼方式不是最好的,不過結果表明編譯出來的輸出遠小於其他部分,所以可接受。
5. 將區塊證明編碼為位元組陣列。
6. 將位元組陣列解碼出區塊證明(即那個樹型結構的資料,一些節點來自於狀態,一些則是無關狀態部分的雜湊值)。
7. 再次執行區塊,不過這回不訪問當前狀態,而是直接與區塊證明進行互動。
8. 驗證狀態根和所有儲存根的正確性。

整個流程花了不少時間,但我希望得到的資料足夠精確。如我所料,雖然這個原型已經能處理許多瑣碎的問題(因為 hexary 的默克爾帕特里夏樹包含葉節點、擴充套件節點、嵌入節點等等複雜的結構),還是有一些罕見情況導致我的原型報錯(在區塊 5340939、5361803、5480357、5480507、5480722、5632299、5707052、5769636 ...... );不過考慮到 670 萬個區塊中只出現數十個報錯,我有信心這些小問題不會影響資料分析的結果(當然,我會盡快 debug)。

目前我的資料只採集至第 6757045 個區塊,但我想很快就能超過這個數字。

第一張圖表表示區塊證明的總體量(注:所有圖表都經過視窗 = 1024 的移動平均計算)。

在偽龍硬分叉(區塊 2675000)之前,少量的 gas 消耗也可能產生非常大的區塊證明, 這個缺陷已經在 2016 年秋天被修復。

下面我們只看偽龍硬分叉後至今的資料表現:

為了清除餘額和 nounce 值為零的賬戶狀態,偽龍硬分叉後進行 “狀態清理”,導致圖表最左側 “峰值” 的出現。但 2017 年下半年,以及 2018 年的 “區塊證明” 的規模已經超過了當時的水平。

分解

首先,我們將區塊證明分解為三個部分:1)所有與 “主” 狀態樹有關的部分;2)所有與合約儲存樹有關的部分;3)智慧合約位元組碼。

可能從圖上有點看不清楚,不過可以看到 2016 年發生的垃圾攻擊造成當時的位元組碼(紅線)和 “主” 狀態樹的區塊證明(黃線)激升。有些人還記得,當時對於智慧合約的位元組碼大小並沒有限制(現在限制小於 24k),造成當時能夠部署超大的智慧合約,並透過 EXTCODESIZE 之類的函式進行查詢。

實施偽龍硬分叉後的圖表表明,主默克爾樹的區塊證明在過去大部分時候體量佔比較大,不過合約儲存證明(藍線)和位元組碼隨後開始趕上。

分解狀態樹區塊證明

這裡我們會進一步將狀態樹區塊證明分解為四個部分。前兩部分是交易需要讀取的鍵值對,或者是為了滿足默克爾帕特里夏樹的一些特殊需求所需的資料。

看起來狀態清除操作對於讀取鍵值對還是造成很大的影響。我們會發現鍵值對中值的大小(一般是 80 位元組)通常比鍵要大得多( key 一般小於 64 位元組,而我剛剛才意識到鍵可以被壓縮,因為我把每個十六進位制數都算為一個位元組)。

接下來,我們看看用來建構默克爾證明的雜湊值,以及那些我稱之為 “mask(掩碼)” 的結構化資訊:

可以輕易發現,相比於雜湊值,結構化資訊的大小是可以忽略不計的;也可以進一步說明雜湊值佔區塊證明大小的主要組成部分(對於合約儲存證明來說也是如此)。

分解合約儲存證明

與前面相同,首先是鍵值對:

有意思的是,合約儲存證明中鍵值對的值(最多佔 32 位元組)通常遠小於鍵。

接著是雜湊值和結構化資訊:

可以看到,與狀態樹區塊證明的表現相似。

與狀態費用研究的相關性

我搞這個研究的其中一個目的是想弄清楚使用無狀態客戶端是否能規避某些型別的租金(譯者注:所指應是 “狀態儲存租金”)。因為當前最大的挑戰在於:合約儲存租金的存在,可能會導致許多現有的合約遭受 griefing 攻擊(譯者注:大意為儘管不能讓攻擊者受益,但會讓受害者感覺很苦惱的攻擊形式)。為了保護智慧合約避免遭此類攻擊,“狀態費用” 協議第三版提出了費用預付(押金)的概念,能暫時保護合約免受攻擊,給已部署合約提供一些緩衝時間,能夠升級為不受影響的程式碼。

無狀態客戶端的構想,至少在讓智慧合約儲存免受 griefing 攻擊方面(如上文所示),提出了一種成本雖高但永久有效的預防方法。

不過如果真的引入某種無狀態客戶端,交易傳送方無可避免的必須針對交易產生的區塊證明,按比例支付額外的 gas,這樣一來合約儲存操作(SLOAD、SSTORE)的成本可能比現在高昂。附帶一提,Martin Swende 最近的分析表明 SLOAD 費用似乎被嚴重的低估。

我們可以將無狀態客戶端用於已有的智慧合約,並讓新的智慧合約自主選擇是否使用儲存租金。我估計這個租金會比使用 “區塊證明” 的成本便宜許多,但總有一天這個臨時費用會消失(因為大家都使用無狀態客戶端啦)。我們不會強迫已有的智慧合約在某個時間點必須轉變為相容可選費用的部署形式,相反地我們希望以激勵手段鼓勵大家進行遷移。

後續的建議

顯然,我要增加資料收集至當前區塊,並修復資料分析中存在的 bug 。

即使我們去掉與合約儲存相關的部分,區塊證明的大小仍是個值得重視問題。我認為有兩種方案能降低它的大小:

1. 在無狀態客戶端中引入多一點的 “富狀態性”。如果大多數客戶端都持有最新的 N 個區塊證明(N = 1, 2, 3 ...),現在你(以太坊節點之一)知道你的對等節點保有許多以前的區塊證明,那麼你可能會選擇只傳送後 N 個區塊證明中不包含的部分。因為你知道節點有能力重構完整的狀態證明。這個方案還需要經過更多分析驗證。

2. 使用 SNARK 證明,將現在區塊證明中可變的 “雜湊值” 部分壓縮為固定大小(就我所知,目前 SNARK 產生的證明大小約 60k 左右)。這個方案還需要投入更多的研究和開發工作,比如圍繞 Keccak256 演算法建立 SNARK 證明,要考慮證明需要付出多少成本,是否需要用 CPU 加速證明過程、保證出塊速度足夠快......等等。

或許在即將釋出的第二章裡,會提出其他更新和想法。

作者: Alexey Akhunov 
翻譯&校對: IAN LIU & 阿劍

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;