header-langage
简体中文
繁體中文
English
Tiếng Việt
한국어
日本語
ภาษาไทย
Türkçe
掃碼下載APP
Partnered With:

當以太坊系統越來越難用,V神眼中有什麼解決方案?

閱讀本文需 30 分鐘
以太坊協議所面臨的一個最為長久且尚未解決的挑戰,就是由於狀態資料規模不斷增長而帶來的問題。
原文標題:《觀點| 以太坊狀態規模管理諸提議》
原文作者:Vitalik Buterin
原文翻譯:阿劍,以太坊愛好者


以太坊協議所面臨的一個最為長久且尚未解決的挑戰,就是由於狀態資料規模不斷增長而帶來的問題。以太坊區塊鏈上的許多操作(創建帳戶、寫入一個合約儲存槽、發送ETH 到一個新的帳戶…)都會為以太坊添加狀態內容(也即為狀態資料增加資料物件),而所有全節點都必須儲存全量的狀態數據,這樣才能驗證新區塊以及製造新區塊。這些操作只需事務的發送者一次繳交以Gas 用量來計量的手續費,但會對整個網路造成永久的持續性成本,因為節點需要儲存這些新資料(而未來加入的節點也需要在同步過程中下載這些數據)。


這是系統設計中的一個顯著的失衡,可能會讓以太坊系統變得越來越難用,因為狀態中充斥著不再有用處的「垃圾資料」。本文的目的是詳細解釋問題產生的根源,以及一些解決問題的方法。如果我們能實現某個解決方案,這將為安全地大幅提高區塊 Gas 上限 鋪平道路。

本文所論述的研究領域仍在推進中,隨時有可能出現更新、更好的想法和更優雅的權衡。


引言:問題出在哪裡?


「狀態」指的是節點若想處理新產生的區塊和事務就必須存有的資訊。狀態與「歷史」完全不同,後者是關於過去時間的信息,節點可以保存這些資訊以便日後重新廣播或歸檔,但並不是處理區塊鏈所必需的。


在以太坊協議中,狀態資訊包括:


· 帳戶的ETH 餘額  ;和 nonce(流水號)

· 智能合約的代碼

· 智能合約的存儲項(storage)

· 與共識機制相關的資料(近期的區塊雜湊值,叔塊;權益證明的共識資料還包括驗證者的公鑰以及及其記錄在信標鏈上的活動,等等)


歷史資訊則由舊的區塊和收據組成。 EVM 中沒有操作碼可以讓你存取舊區塊、舊事務和內容和收據輸出,所以節點丟棄這些資料也仍然能驗證新區塊,所以這些是歷史資訊。


上述狀態資訊清單中的最後一項-共識機制相關資料-在設計上已經精心限制了其規模,因此我們不太需要為此困擾。但前面三項,就令人頭大了。這三類狀態資訊的規模會隨著時間推移而不斷增大,因為不斷會有新用戶加入網絡,他們會創建新的帳戶、新的合約,還會加入合約、收到 token 什麼的。


難辦的是,許多狀態用過之後就會靜靜地躺在那裡(不會再被觸及);一旦某個用戶停用某個應用程式之後,就會產生一些「垃圾狀態」──不會再派上用場,但會永遠存在在那裡。


理論上,使用者可以做到「垃圾不落地」。用戶可以只發布帶有 SELFDESTRUCT 條件的合約,等他們再也用不上這個合約的時候,就調用這個操作碼移除這個合約、清空其token 餘額;他們還可以使用智能合約錢包,通過一個已有的外部持有帳戶(EOA)來發送交易,而無需產生一個新的EOA(EOA 狀態是無法刪除的)。


但是在實務中,這樣的激勵非常少,而適當的狀態清理的技術複雜性又太大了。在許多合約中,賦予任何人這樣呼叫 SELFDESTRUCT 的權限都是不合適的(人們想要的就是「無法終止」的應用程式!),而且,也會在使用者體驗和程式碼上增加很多複雜性。實際上,由於 SELFDESTRUCT 用處極其有限而副作用極大,我更傾向於永遠移除這個操作碼。如果我們真想控制狀態資料的規模,我們需要的是一個網路中的節點可以 預設 丟棄不再被使用的「垃圾狀態」的方法。


無狀態客戶端


這個問題的一類解決方案是基於「無狀態客戶端」的觀念(此文是論述這個觀念的出處,此處是演講影片)。基本原理是,讓區塊驗證不再以持有全域狀態為前提。相反,區塊會自備證據(或稱為「見證資料(witness)」),證明其所訪問狀態的數值。就跟現在的設計一樣,區塊內會包含一個「狀態根(state root)」,所訪問的值可以對應著狀態根得到證明(譯者註:默克爾證明即是一種常見的證明技術)。以太坊現在的狀態樹方案(梅克爾帕特里夏樹)支持這樣的證明技術,像二進制樹或 Verkle Trie 這樣更有效率的方案也可以。見證資料也會證明處理完該區塊後新狀態根的正確性。


無狀態性有兩種形式:


· 弱無狀態性:出塊者仍需要完整的狀態,以為(自己製造的)區塊產生見證資料;但驗證區塊的階段可以是無狀態的;


· 強無狀態性:沒有任何節點需要完整的轉台。反過來,是交易發送者需要提供見證數據,而出塊者可以聚合這些數據。交易發送者自己負責儲存為所關切的帳戶產生見證資料所需的部分狀態樹。


強無狀態性是一個非常「優雅」的解決方案,因為它把責任完全轉移給了用戶,雖然為了保證實踐中的良好用戶體驗,我們需要創造某些類型的協定來幫助不運行個人節點的使用者維護狀態、並處理使用者需要與意料之外的帳戶互動的情況。打造這樣的協議非常困難。


此外,所有類型的無狀態性都提高了網路所需的資料頻寬;而強無狀態性還需要交易聲明其所交互的帳戶及儲存項目的鍵(概念上這個叫做「存取清單」)。


一個較溫和的解:狀態過期


較溫和的解可以歸結為不同形式的「狀態過期」方案。必須持續被訪問的狀態才能維持「啟動狀態」;而長期無人問津的狀態會變成「失活」(或叫「過期的」)。具體用什麼機制來更新狀態,有很多選擇(例如預付“租金”,或只需訪問那個狀態),但一般原則是,除非某個狀態對像被顯式地更新,否則就以某種形式處於失活狀態。因此,任何建立新狀態物件(以及更新已有狀態物件)的活動,都只能成為節點在一段時間內的負擔,而不像現在這樣變成永久負擔。


失活狀態,故名思義,就不是「狀態」的一部分;想要處理區塊或創建區塊的節點無需儲存失活狀態。不過,失活狀態並不是完全刪除了!在所有類型的狀態過期提案中,都預設了某種方法可以「復活」已經失活的狀態。


一般原則是,啟動狀態的使用與目前相同,而失活狀態則需透過上述無狀態客戶端的機制來使用。復活一個過期狀態物件的事務需要提供一個證據(見證資料),來證明該物件是失活狀態的一部分。為了能夠產生這樣的證據,使用者自己需要儲存和維護至少一部分失活狀態(對應於其所關切的失活狀態物件的那部分)。


何時過期


決定過期條件的設計也有很多種。最常見的幾種是:


· 直接租金:逐塊逐塊收取“租金”,直接以每個帳戶(或其他狀態對象)的餘額來支付;狀態對象的餘額降到了零,該帳戶就過期了。

· 剩餘存活時間值:每個狀態物件儲存一個「剩餘存活時間」值,這個值可以透過支付費用來增加。

· 觸達即刷新:每個狀態物件都儲存一個「剩餘存活時間」值,並且每逢讀取或寫入該帳戶都會增加該值。

· 所有狀態物件定期過期(例如每 6 個月一次):也就是 ReGenesis 提案。


我自己越來越喜歡「觸達即刷新」方案,因為(1)它避免了應用需要創造複雜的經濟模型來讓使用者承擔狀態租金;以及(2)它保證了激活狀態的規模有一個清晰的上限(區塊Gas 上限/ 觸達狀態對象的Gas 消耗量× 狀態存活的時長)。讓大量狀態按照規律的時間間隔過期的方案(也就是ReGenesis)也有同樣的好處,但也有一些有趣的權衡:關鍵好處是,過期方案更簡單(無需遍歷整棵狀態樹而逐個逐個地滅活狀態物件),但關鍵不足是,跨過一個過期時點後,你再啟動自己的狀態物件時,需要多少見證資料會跟你觸達狀態物件的時間點有關。


帳戶層面的過期vs. 儲存槽層面的過期


狀態過期的邏輯既可以運作到帳戶層面,也可以運用到單一儲存槽層面。目前,我強烈偏向在儲存槽層級實現狀態過期方案。因為許多合約帳戶的儲存槽數量是不受限制的,任意用戶都能加入合約並增加合約名下的儲存槽的數量(例如,空投就是一個已經出現過的案例)。不管使用什麼樣的帳戶層過期方案,想要實際限制狀態的規模,租金的數量都必須與合約內儲存槽的數量成比例(或存活時間與之成反比)。結果是,用戶還是能夠僅支付一次性的費用就給合約及其用戶施加 永久的持續性成本。

要解決這個問題,合約要么加入複雜的內部邏輯,將存儲操的租金“轉嫁”給用戶,要么重新設計自己合約的模式,轉向使用CREATE2 操作碼創建新的合約並使用這些合約來充當儲存槽。不管是哪一種辦法,最後都會變成等價於儲存槽層級的過期方案。因此,我個人認為,我們應該僅在合約儲存槽層級實現狀態過期方案。


但是,儲存槽層面的過期方案也有自己的缺點:每個儲存槽都要增加一個元數據,指明它何時過期(或者說是否已經失去活性),這也意味著「復活衝突問題」(詳見下文)不僅會影響帳戶,也會影響儲存槽。


從狀態樹上移除vs. 給狀態樹安排一個「退休」部分


另一個區分不同狀態過期提議的技術角度是「一樹流」和「二樹流」。也就是說,我們到底是像現在這樣,只有一棵狀態樹,只不過把某些狀態標記為過期;還是直接把失活的狀態從主狀態樹上移除,轉移到另一棵專門的(只包含過期狀態的)樹(或其他資料)上?


一樹流


观点 | 以太坊状态规模管理诸提议(下)

活化節點以白色標記,失活節點以灰色標記


注意,即使是樹上的中間節點,也會被標記為激活或失火(或者,更現實一點的方案,每個節點都會帶有失活日期的標記,所以能夠輕鬆檢查其活性);標記工作可以在狀態樹上的每個節點(葉子節點和中間節點)處完成。


二樹流


观点 | 以太坊状态规模管理诸提议(下)

白色的樹包含激活狀態;灰色的樹存儲失活狀態


一樹流的好處是,最起碼,其工作方式看起來會跟目前的狀態樹相似,失活和復活的流程也比較簡單:復活流程只需刷新樹上相關節點的「過期日期」參數,而失活則是自動化的。但它的缺點在於:它需要一種能夠在節點中以此種方式儲存過渡資訊(intermediate information)的樹狀結構,而且不能很好地擴展到 Verkle 樹。此外,它還需要額外的梅克爾證明元件,不僅要能夠下沉到葉子節點,還要能夠(在需要證明某部分狀態已經過期時)停在中間節點處。


二樹流的好處是:目前的、形式純粹的狀態累加器就能支援這類方案,而無需為每個節點增加元資料。缺點是,它需要對整個協議做一些更深層的變更,而且需要一個明確的流程來滅活狀態(所以過期不再是自動化的了)。另外,它也沒有為復活衝突兩難(見下一節)提供內建的解決方案,所以需要在兩種辦法中做出選擇。


注意,在二樹流中,儲存失活狀態的資料結構並非非樹不可。事實上,完全有可能出現這樣一種設計:需要復活一個狀態對象時,只需提供一個指向該對象失活時候收據的默克爾樹,再附上一些密碼學證據,證明此前該對象未被復活過(或最近又重新過期),即可。


復活衝突


然後我們就到了狀態過期方案的一個關鍵難題上:「復活衝突」。復活衝突的概念如下。假設某個帳戶由地址A 產生;這個帳戶過期了;然後,地址A 又創建了一個新的帳戶(例如,使用CREATE2 操作碼保證兩次生成的帳戶的地址是同一個);最後,地址A 再試著復活那個最開始的帳戶。這時候會出現什麼狀況?


這裡有幾個可能的解決方案:


1. 明確的「帳戶合併」流程:類似於規定「除了兩個帳戶的ETH 餘額相累加以外,以舊帳戶的狀態為準」或「除了累加ETH 之外,以新帳戶的狀態為準」;甚至於,可以由舊帳戶的合約代碼來規定特殊的合併流程


2. 透過消除同一地址重複部署的功能來確保復活衝突不會發生:也就是調整CREATE2 的功能,例如在最終哈希成位址的資料原像中包含當前時間,因此即使未來使用相同的資料來生成,也無法得到相同的位址


3. 為狀態物件增加一個「存根」,以防止在同一位置產生新帳戶(上述一樹流方法自動實現了此功能)


4. 要求產生新帳戶時都必須附帶該帳戶此前未過期的證明:某種意義上等價於存根方案,只不過這種辦法是把存根放在狀態的一個單獨部分中,所以任何想要創建合約帳戶的使用者都必須追蹤這部分狀態


(注意,如果我們使用儲存槽過期方案,則上述任一解決方案都必須延伸到單一存儲槽層面,而不能止步於帳戶層)


主要的擔憂有:(1)會為應用增加很多複雜性,他們需要加入合併的邏輯; (2)這樣做了之後,除非在鏈上「註冊」一個地址,否則用戶就沒法再輕易獲得可以與之交互、可以累積資產(例如ERC20 token)的地址了。未註冊的地址是很重要的:任何第一次收到 ETH 的用戶都是在使用尚未註冊的地址。這第(2)的擔憂的根源是:未註冊的地址實際上有了時間限制,如果用戶生成了一個地址、收到了資金,但在接下來一年裡忘了發送交易(也就是忘了“註冊”),那他的資金就會被鎖住。


注意,EOA 也不能倖免。雖然看起來能夠,因為 EOA 的合併流程比較簡單(只要把舊的 ETH 餘額加到新的里,對 nonce 則有 EIP 169)這樣的方案。不過,這裡也有兩個問題。首先,帳戶抽象的目標是用合約來取代 EOA,而帳戶抽象化的合約的合併流程可能並不簡單。其次,會受過期和復活事件影響的不僅有 EOA 本身,還有該 EOA 所參與的應用中的相關儲存鍵(例如 ERC20 token 餘額),所以還是需要複雜的合併邏輯。


因此,從我的角度來看,破壞性最小的是某種形式的存根方案。不過,存根方案裡存在著一個資訊理論問題,會導致一些奇怪的結果。為了防止新的狀態物件在 N 個已經過期的狀態物件位置處創建,一個覆蓋(cover)了這 N 個位址(以及 / 或 儲存鍵)的集合必須是狀態的一部分。如果這個集合是資訊最小化的(即,只包含了這些位址),那麼這個集合的大小會是O(N),因此其狀態規模也是O(N);那麼,活化狀態的規模就會與失活狀態的規模成比例,所以實際上我們並沒有解決這個問題。


Tree rot


解決這個問題的唯一辦法就是覆蓋超過那N 個帳戶的資訊;實際上,我們將不得不讓整棵樹都變得不可訪問(再次提醒,這就是一樹流解決方案的實質:如果兩個帳戶過期了,它們之間的所有空間都會隱式過期( if two accounts get expired, all the space in between them also implicitly gets expired))。


而這裡還有一個問題:這產生了一種形式的「樹發黴(tree rot)」,隨著時間推移,對於新帳戶的創建來說,狀態樹的所有部分都是不可訪問的,至少對那些沒有追蹤該區域過期狀態的用戶來說是這樣的。


而樹發黴導致的次生問題也必須解決。舉個例子:如果一個合約要創建子合約,它必須能夠在要么未發黴,要么用戶具有見證數據的狀態區域創建合約(也許需要用戶提供的「提示」)。樹發黴問題的解決方案請參閱此處:持續地開放狀態的新區域以供帳戶建立。另一個想法是每個使用者都選擇狀態的某些區域(例如狀態的 1/256),追蹤該區域的變化(包括過期狀態)以便能建立見證訊息,並且只在該區域建立帳戶。


樹發霉的另一個問題是,它需要一個明確的資料結構來儲存和檢查範圍。如果一棵樹有能夠放在節點中、指明該節點以下的哪些部分已經過期的資料(就像一樹流解決方案所用的那樣),那是最好的,但一個鍵值對儲存要做到這一點還是相當有難度的。


回頭再看強無狀態性


在狀態過期方案中使用樹狀結構所產生的許多問題,都可以追溯到這樣一個事實:我們需要對哪些狀態是活躍的、哪些狀態是失活的,達成共識。在二樹流模式中,這一點更加明顯;但即使是在一樹流模式中,狀態樹上也需要有明確的標記,以便近期使用快速同步下載了狀態的以太坊節點能夠確定一筆嘗試存取某個帳戶、但又沒有提供見證訊息的交易,應該成功還是失敗。那我們能不能做到不需要明確這個差異呢?


如果我們實現了完全的無狀態性,然後能幫助交易發送者和區塊生產者可靠地獲得見證訊息產生所需的狀態,不就解決這個問題了嗎?那什麼辦法能幫助交易發送者和區塊生產者做到這些呢?


一種自然而然的辦法是:網路中的節點都只保存狀態樹的一部分,例如,在過去一年中訪問到的那部分。只需在客戶端設定中加入一個自願的設定即可。如果我們想要更可靠一些,我們可以透過引入一種 proof of custody 方案,強制至少礦工(後面就是 PoS 的驗證者)儲存一些資料。


有一點要注意:如果共識層不能感知哪些狀態是活躍的、哪些狀態是失活的,那麼訪問近期狀態和老舊狀態的Gas開銷就是一樣的。這會導致兩個結果:


1. 訪問近期狀態的Gas 開銷也需要進一步提高


2. 包含了見證訊息的區塊大小上限可能非常之大,如果一個區塊裡滿是訪問老舊狀態的事務的話(大概是 800 bytes * 12.5 m gas / 2400 gas per access ~ = 4.1 MB,已假設實行了EIP-2929,轉成了二進位樹)


如果我們想避免這些不利因素,就需要在共識中追蹤哪些狀態物件(包括尚未填滿的位址空間區域)是活躍狀態,這又會讓我們回到接近狀態過期方案的屬性。這再一次地說明了,「無狀態性 vs. 狀態過期(狀態租金)」是一條光譜,是一個複雜的權衡空間,而不是一個非此即彼的選擇。


Rollup 也需要,也可以,使用同樣的解決方案


以太坊的一種重要的中期可擴展性解決方案是rollups。不過,rollup 本身並非不再需要擔憂狀態資料規模問題;事實上,rollup 系統的狀態規模問題,與以太坊鏈本身的,性質完全相同。


幸運的是,如果我們能推出解決方案,則至少EVM rollup(嘗試最大程度複製以太坊運行環境的rollup 方案)能夠使用同樣的解決方案,來解決其內部狀態的規模問題。因此,狀態規模管理方案,與 rollup 和 分片等可擴展性方案是互補的(state size management is complementary to rollups, sharding and other scaling strategies)。


(譯者註:個人認為此處的「互補」一詞有嚴重誤導性。)


結論


狀態規模是一個日益惡化的問題,而狀態規模的解決方案也能為大幅提高區塊Gas 上限鋪平道路。我們應該對某種形式的狀態過期方案達成共識並加以實現。不過,不同的解決方案之間存在著重大技術權衡,尤其如果我們還想要保持當前設計的一些重要屬性的話。


一些我們可能需要犧牲的屬性包括:


· 使用者可以離線產生帳戶並以該位址接收資金、並且在使該位址在鏈上顯明之前可以靜默任意時長的屬性

· 位址保持20 位元組的長度(rolling state expansion 方案需要更大的位址空間,雖然位址的長度可能本來就需要為抗碰撞的緣故很快改變)

· 狀態可以被視為「純粹的」鍵值對儲存的屬性,以及無需在狀態樹上每個節點內儲存元資料的屬性

· 現有的應用需要程度不等的重寫,以確保使用者無需儲存全部失活狀態就能產生見證資料

· Gas 消耗量;或建立新合約、寫入新儲存槽的難度


我們如果已經準備好作出犧牲,有些方案可以很快開始著手實現。另一方面,也許假以時日,我們能修補或更好地匯總這些觀念,減少問題,尤其是使它們在技術上更容易實現(例如,允許使用“純粹的”鍵值對存儲)。我們應該更深入地理解我們 更願意 / 更不願意 接受哪些方面的犧牲,並繼續積極研究改進提案。


原文連結


歡迎加入律動 BlockBeats 官方社群:

Telegram 訂閱群:https://t.me/theblockbeats

Telegram 交流群:https://t.me/BlockBeats_App

Twitter 官方帳號:https://twitter.com/BlockBeatsAsia

举报 糾錯/舉報
選擇文庫
新增文庫
取消
完成
新增文庫
僅自己可見
公開
保存
糾錯/舉報
提交