區塊鏈發展至今已有近 10 年曆史,隨着公鏈的百花齊放以及越來越多新銳技術方案的提出,我們正在面臨一個越來越複雜混亂的行業狀況。當我們解決了 scalability 問題,區塊鏈真正獲得 mass adoption,DApp 和用戶數量都爆炸式增長的時候,區塊鏈歷史和狀態數據會以什麼速度累積呢?

原文標題:《區塊鏈的狀態爆炸困境》
文章來源:鏈捕手
作者:謝晗劍,Nervos 創始人

當用戶越多,歷史越長,如何應對越積越多的區塊鏈數據?janx,謝晗劍,Nervos 創始人

1 Layer 1 應該關注狀態

如果我們認爲區塊鏈分層是未來的發展方向,我們應該從一開始就考慮到上層協議和分層網絡的需求,在分層的大框架下去設計區塊鏈協議。

換句話說,從分層的角度來看,現有的區塊鏈設計方式都是過時的。現有的區塊鏈在設計時考慮的是特定的功能(例如支付,或者是運行 DApp),並希望在運行一段時間後讓上層協議來適應自己。然而如果我們閱讀互聯網的歷史就知道,今天互聯網的協議分層不是這樣打補丁打出來的,相反是在吸收過去經驗之後。這是爲什麼區塊鏈並不天然是 Layer 1,Layer 1 是需要設計的。

要弄清 Layer 1 應該關注什麼,先要弄清楚它和上層協議的區別。Layer 2 起源與我們發現公有鏈(這裏指 permissionless blockchain)的性能不足,很難擴容到滿足整個加密經濟體需求的水平,同時我們又非常迷戀公有鏈提供的可用性和極大的服務範圍,因此慢慢演化出了一系列可以由區塊鏈來保證安全的 Layer 2 協議,例如支付通道 (payment channel)、 plasma,etc.

這些協議的共同特點是犧牲共識範圍來換取性能。公有鏈最讓人驚豔的地方是通過開放網絡提供不間斷的覆蓋全球的服務,這意味着全球共識,也意味着性能底下。

解決這個問題的最好方式是 將大部分交易轉移到共識範圍更小但是性能更好的上層協議中,並且保證上層協議的參與者總是可以在不滿意的時候退回到區塊鏈上來解決問題,代價僅僅是一些時間成本。

因此作爲 Layer 1 的區塊鏈,關注點顯然不應該是性能,因爲 Layer 2 會承擔這個職責。Layer 1 是保障上層協議參與者的最後防線,它的關注點應該是安全和去中心化。如果我們觀察 Layer 2 協議與 Layer 1 交互的模式,我們還會發現,Layer 1 負責的是狀態共識(存儲),Layer 2 負責的是狀態生成(計算)。

2 狀態是什麼

如果 Layer 1 的關注點應該是狀態而不是計算,在設計 Layer1 區塊鏈的時候,我們就需要先理解什麼是區塊鏈的狀態。理解了狀態是什麼,我們才能理解狀態爆炸是什麼。

區塊鏈網絡中的每一個全節點,在網絡中運行一段時間之後都會在本地存儲上留下一些數據,我們可以按照歷史和現在把它們分爲兩類:

歷史:區塊數據和交易數據都是歷史,歷史是從 Genesis 到達當前狀態的路徑。
狀態(即現在):節點在處理完從 Genesis 到當前高度的所有區塊和交易後形成的最終結果。狀態隨着區塊的增加一直處於變化之中,交易是造成變化的原因。

共識協議的作用是通過一系列的消息交換,保證每一個節點看到的當前狀態是相同的,而實現這個目標的方式是保證每一個節點看到的歷史是相同的。只要歷史相同(即所有交易的排序相同),處理交易的方式相同(把交易放在相同的確定性虛擬機裏面執行),最後看到的當前狀態就是相同的。當我們說「區塊鏈具有不可篡改性」的時候,指的是區塊鏈歷史不可篡改,相反狀態是一直在變化的。

有趣的是,不同的區塊鏈保存歷史和狀態的方式不同的,其中的差異使得不同的區塊鍊形成了各自的特點。由於這篇文章討論的話題是狀態,而影響狀態的歷史數據主要是交易(而不是區塊頭),接下來的討論歷史的時候會側重交易,忽略區塊頭。

3 舉個例子:Bitcoin 的歷史和狀態

Bitcoin 的狀態,指的是 Bitcoin 賬本當前的樣子。Bitcoin 的狀態是由一個個 UTXO (尚未花費的交易輸出)構成的,每個 UTXO 代表了一定數量的 Bitcoin,每個 UTXO 上面寫了一個名字(scriptPubkey),記錄這個 UTXO 的所有者是誰。如果要做一個比喻的話,Bitcoin 的當前狀態是一個裝滿了金幣的袋子,每個金幣上刻着所有者的名字。

Bitcoin 的歷史由一連串的交易構成,交易內部的主要結構是輸入和輸出。交易更改狀態的方法是,把當前狀態中包含的一些 UTXO (交易輸入引用的那些)標記爲已花費,從 UTXO 集合中移出,然後把一些新的 UTXO (這個交易的輸出)添加到 UTXO 集合裏面去。

當用戶越多,歷史越長,如何應對越積越多的區塊鏈數據?

可以看出,Bitcoin 交易的輸出(TXO,Transaction Output)正是上面說的 UTXO,UTXO 只不過是一種處於特殊階段(尚未花費)的 TXO。因爲構成 Bitcoin 狀態的組件 (UTXO),同時也是構成交易的組件 (TXO)。

由此,Bitcoin 有一個奇妙的性質:任意時刻的狀態都是歷史的一個子集,歷史和狀態包含的數據類型是同一維度的。交易的歷史(所有被打包的交易的集合,即所有產生過的 TXO 的集合)即狀態的歷史(每個區塊對應的 UTXO 集合的集合,也是所有產生過的 TXO 的集合),Bitcoin 的歷史只包含交易。

在 Bitcoin 網絡中,每一個區塊,每一個 UTXO 都要持續佔用節點的存儲空間。目前 Bitcoin 整個歷史的大小(所有區塊加起來的大小)大約是 200G,而狀態的大小隻有大約 3G(由大約 5000 萬個 UTXO 組成)。Bitcoin 通過對區塊大小的限制很好的管理了歷史的增長速度,由於其歷史和狀態之間的子集關係,狀態數據大小必然遠小於歷史數據大小,因此狀態增長也間接的受到區塊大小的管理。

4 再舉個例子:Ethereum 的歷史和狀態

Ethereum 的狀態,也叫做「世界狀態」,指的是 Ethereum 賬本當前的樣子。Ethereum 的狀態是由賬戶構成的一棵 Merkle 樹(賬戶是葉子),賬戶裏面不僅記錄了餘額(代表一定數量的 ether),還有合約的數據(例如每一隻加密貓的數據)。Ethereum 的狀態可以看作一個大賬本,賬本的第一列是名字,第二列是餘額,第三列是合約數據。

Ethereum 的歷史同樣由交易構成,交易內部的主要結構是:

  • to:另一個賬戶,代表交易的發送對象
  • value:交易攜帶的 ether 數量
  • data:交易攜帶的任意信息

交易更改狀態的方法是,EVM 找到交易發送的目標賬戶,

1、根據交易的 value 計算目標賬戶的新餘額;
2、將交易攜帶的 data 作爲參數傳遞給目標賬戶的智能合約,運行智能合約的邏輯,在運行中可能會修改任意賬戶的內部狀態生成新的狀態;
3、構造新的葉子存放新的狀態,更新狀態 Merkle 樹 。

當用戶越多,歷史越長,如何應對越積越多的區塊鏈數據?

可以看出,Ethereum 的歷史和交易結構與 Bitcoin 相比有非常大的不同。Ethereum 的狀態是由賬戶構成的,而交易是由觸發賬戶變動的信息構成,狀態和交易中記錄的是完全不同類型的數據,二者之間沒有超集和子集的關係,歷史和狀態所包含的數據類型是兩個維度的,交易歷史大小與狀態大小之間沒有必然的聯繫。

交易修改狀態後,不僅會產生新的狀態(圖中實線框的葉子),而且會留下舊的狀態(圖中虛線框的葉子)成爲歷史狀態,因此 Ethereum 的歷史不僅僅包含交易,還包含歷史狀態。

因爲歷史和狀態屬於不同的維度,Ethereum 區塊頭中不僅僅包含交易的 merkle root, 也需要顯式包含狀態的 merkle root。

Ethereum 中每一個區塊,每一個賬戶都會持續佔用節點的存儲空間。Ethereum 節點在同步的時候有多種模式,在 Archive 模式下所有的歷史和狀態都會保存下來,其中歷史包括歷史交易和歷史狀態,所有數據加起來大小超過了 2TB;在 Default 模式下,歷史狀態會被裁剪掉,本地只保留歷史交易和當前狀態,所有數據加起來大約是 170G,其中交易歷史大小是 150G,當前狀態大小是 10G。

Ethereum 中所有的開銷管理都被統一到 gas 計費模型之下,交易的大小需要消耗對應的 gas,而每一條 EVM 指令消耗的 gas,不僅考慮了計算開銷,也將存儲開銷考慮在內。通過每個區塊的 gaslimit,間接限制了歷史和狀態的增長速度。

常見的一個誤解是,Ethereum 的「區塊鏈大小」已經超過 1T 了。從上面的分析我們可以看到,「區塊鏈大小」是一個非常模糊的定義,如果把歷史狀態算進去,確實超過了,但對於全節點來說,把歷史狀態刪掉沒有任何問題,因爲只要有 Genesis 和交易歷史,任意時刻的歷史狀態都可以重新被計算出來(不考慮計算需要的時間)。

真正有意義的數據,是全節點必須的數據的大小,Bitcoin 是 200G,Ethereum 是 170G,兩者是基本相同的,而且在平均配置的雲主機都能裝下,因此人們觀察到的 Ethereum 全節點減少並不是由於存儲增加導致的(根本原因是同步時的計算開銷)。

考慮到 Ethereum 的歷史長度(當前區塊的 timestamp 減去 genesis 的 timestamp)不到 Bitcoin 的一半,可以看出 Ethereum 的歷史和狀態大小增長更快。

5 The Tragedy of (Storage) Commons: 區塊鏈版本的公地悲劇

公地悲劇所指指的是這樣一種情況,有限的共享資源在不受任何限制的使用下被人們過度消耗。區塊鏈節點爲保存歷史和狀態付出的存儲,正是這樣一種共享資源。

區塊鏈節點爲處理交易所花費的資源有三種,CPU,存儲和網絡帶寬。CPU 和帶寬都是每個區塊會刷新的資源,我們可以認爲每個區塊間隔內都用同樣多的 CPU 和帶寬可供使用,上個區塊消耗掉的 CPU 和帶寬不會讓下個區塊可用的 CPU 和帶寬變少。對於可刷新的資源,我們可以通過一次性支付的交易手續費來補償節點(手續費與計算複雜度和交易大小的相關性可參考 RFC0015Appendix)。

與 CPU 和帶寬不同,存儲是一種佔用資源,在一個區塊中被佔用了的存儲,除非使用者主動釋放,否則無法在後面的區塊中被其它使用者使用。節點需要爲存儲持續的付出成本,而使用者卻不需要爲存儲持續的支付手續費(記住交易手續費只需要支付一次)。

使用者只需要在往區塊鏈寫數據的時候支付一點點手續費,就可以永久使用一個可用性超過 Amazon S3 的存儲,其無限大的永久存儲成本需要區塊鏈網絡中的所有全節點來承擔。

Ethereum 上由於各種 DApp 的存在,The Tragedy of (Storage) Commons 相對更加嚴重。例如,在區塊 5700001 (May 30, 2018)的時候,使用狀態最多的 5 個合約是:

  • EtherDelta, 5.09%
  • IDEX, 4.17%
  • CryptoKitties, 3.05%
  • ENS, 1.92%
  • EOS Sale, 1.73%

比較有趣的是最後一個,EOS Sale。雖然 EOS 的衆籌已經完成,EOS 代幣已經在 EOS 鏈上流轉,EOS 衆籌的記錄卻永遠留在了 Ethereum 的節點上,消耗 Ethereum 全節點的存儲資源。

可以看到,在缺乏管理的情況下,區塊鏈的存儲資源會被有意或者無意的濫用。在一個設計合理的經濟模型中,使用者必須承擔存儲佔用的成本,這個成本不僅僅與佔用存儲空間的大小成正比,還與佔用時間的長度成正比

6 狀態爆炸

公地悲劇所指的無論是歷史還是狀態數據都會佔用存儲資源。通過上面對 Bitcoin 和 Ethereum 的分析(其他區塊鏈的狀態模型基本都可以歸納爲二者之一)可以看到,雖然它們對歷史和狀態的增長進行了管理,但是對歷史和狀態的總大小卻沒有任何控制,這些數據會持續的無休止的累積下去,使得運行全節點需要的存儲資源越來越大,提高全節點的運行門檻,使網絡的去中心化程度越來越低,這是我們不願意看到的。

你也許會說,有沒有可能硬件平均水平的提高會超過歷史和狀態的積累速度?我的回答是可能性很低:

當用戶越多,歷史越長,如何應對越積越多的區塊鏈數據?

從這張圖中我們可以看到,隨着 Ethereum 網絡的發展,狀態數據累積的數量呈指數式的增長。Bitcoin 的狀態數據從 0 積累到 3G,用了 10 年;Ethereum 的狀態數據從 0 積累到 10G,用了 4 年;而這是在我們還沒有解決 Scalability 問題,區塊鏈仍然是小衆技術的情況下的增長速度。

當我們解決了 scalability 問題,區塊鏈真正獲得 mass adoption,DApp 和用戶數量都爆炸式增長的時候,區塊鏈歷史和狀態數據會以什麼速度累積呢?

這就是狀態爆炸問題,我們把它歸類爲 post-scalability problem,因爲它在解決 scalability 問題之後會非常明顯。我們最早是在做許可鏈場景落地時注意到了這個問題,因爲許可鏈的性能遠高於公有鏈,剛好處於 post-scalability 的階段。

歷史數據的累積相對容易處理,未來可以通過去中心化的 Checkpoint 或是零知識證明等技術來壓縮,在那之前全節點甚至可以把歷史直接丟掉,依然可以正常運行。狀態數據的累積則麻煩許多,因爲它是全節點運行必須的數據。

不少區塊鏈項目已經看到了這個問題,並提出了一些解決方案。EOS RAM 是解決狀態爆炸問題的一個有益嘗試:RAM 代表了超級節點服務器可用的內存資源,無論是賬戶、合約狀態還是代碼,都需要佔用一定的 RAM 才能運行。

RAM 的設計也有很多問題,它需要通過內置的交易市場購買,不可轉讓,無法租用,將合約執行過程中的短期內存需求和合約狀態的長期存儲需求混在了一起,而且 RAM 的總量的設定沒有確定的規則,更多取決於超級節點可以承受的硬件配置,而非共識空間的成本。

Ethereum 社區也看到了這個問題並提出了 Storage Rent 的方案:要求使用者爲存儲資源的使用預支付一筆租金,佔用存儲資源會持續消耗這筆租金,佔用時間越長,使用者需要支付的租金越多。Storage Rent 方案存在兩個問題:

1、預支付的租金終有一天會用完,這時候如何處理佔用的狀態?正是爲解決這個問題,Storage Rent 需要諸如 resurrection 的機制來補充,增加了設計的複雜度,使智能合約的 immutability 大打折扣,也爲使用體驗帶來了麻煩;
2、Ethereum 的狀態模型是一種共享狀態的模型,而不是 First-class State。以 ERC20Token 爲例,所有用戶的資產記錄都存放在單個 ERC20 合約的存儲裏面,在這種情況下,應該由誰來支付租金?

解決狀態爆炸問題也是 Nervos CKB 的設計目標之一,爲此我們走了一條完全不同、更爲徹底的變革之路。簡而言之,我們在 Nervos CKB 一個 token 代表一個單位的存儲空間,通過經濟模型的設計,限制世界狀態的大小,利用市場手段調節狀態存儲的供需,並通過增發的設計持續向佔用狀態的用戶收費,來協調生態中各方的利益,達到長期的安全和持久的穩定。