若是 Layer 1 的關注點應該是狀態而不是計算,在設計 Layer 1 區塊鏈時,咱們就須要先理解什麼是區塊鏈的狀態。理解了狀態是什麼,咱們才能理解狀態爆炸是什麼。git
區塊鏈網絡中的每個全節點,在網絡中運行一段時間以後都會在本地存儲上留下一些數據,咱們能夠按照歷史和如今把它們分爲兩類:github
共識協議的做用是經過一系列的消息交換,保證每個節點看到的當前狀態是相同的,而實現這個目標的方式是保證每個節點看到的歷史是相同的。只要歷史相同(即全部交易的排序相同),處理交易的方式相同(把交易放在相同的肯定性虛擬機裏面執行),最後看到的當前狀態就是相同的。當咱們說「區塊鏈具備不可篡改性」時,是指區塊鏈歷史不可篡改,相反,狀態是一直在變化的。web
有趣的是,不一樣的區塊鏈保存歷史和狀態的方式不一樣,其中的差別使得不一樣的區塊鏈造成了各自的特色。因爲這篇文章討論的話題是狀態,而影響狀態的歷史數據主要是交易(而不是區塊頭),接下來的討論歷史的時候會側重交易,忽略區塊頭。segmentfault
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 的歷史只包含交易。post
在 Bitcoin 網絡中,每個區塊,每個 UTXO 都要持續佔用節點的存儲空間。目前 Bitcoin 整個歷史的大小(全部區塊加起來的大小)大約是200G,而狀態的大小隻有大約 3G(由約 5000萬個UTXO組成)。Bitcoin 經過對區塊大小的限制很好的管理了歷史的增加速度,因爲其歷史和狀態之間的子集關係,狀態數據大小必然遠小於歷史數據大小,所以狀態增加也間接的受到區塊大小的管理。性能
Ethereum 的狀態,也叫作「世界狀態」,指的是 Ethereum 帳本當前的樣子。Ethereum 的狀態是由帳戶構成的一棵 Merkle 樹(帳戶是葉子),帳戶裏面不只記錄了餘額(表明必定數量的 ether),還記錄了合約的數據(例如每一隻加密貓的數據)。Ethereum 的狀態能夠看做是一個大帳本,帳本的第一列是名字,第二列是餘額,第三列是合約數據。區塊鏈
Ethereum 的歷史一樣由交易構成,交易內部的主要結構是:google
交易更改狀態的方法是,EVM 找到交易發送的目標帳戶:
1.根據交易的 value 計算目標帳戶的新餘額;
2.將交易攜帶的 data 做爲參數傳遞給目標帳戶的智能合約,運行智能合約的邏輯,在運行中可能會修改任意帳戶的內部狀態生成新的狀態;
3.構造新的葉子存放新的狀態,更新狀態 Merkle 樹。
能夠看出,Ethereum 的歷史和交易結構與 Bitcoin 相比有很是大的不一樣。Ethereum 的狀態是由帳戶構成的,而交易是由觸發帳戶變更的信息構成,狀態和交易中記錄的是徹底不一樣類型的數據,兩者之間沒有超集和子集的關係,歷史和狀態所包含的數據類型是兩個維度的,交易歷史大小與狀態大小之間沒有必然的聯繫。交易修改狀態後,不只會產生新的狀態(圖中實線框的葉子),並且會留下舊的狀態(圖中虛線框的葉子)成爲歷史狀態,所以 Ethereum 的歷史不只僅包含交易,還包含歷史狀態。由於歷史和狀態屬於不一樣的維度,Ethereum 區塊頭中不只僅包含交易的 merkle root,也須要顯式包含狀態的 merkle root。(思考題:EOS 使用了相似 Ethereum 的帳戶模型,卻沒有在區塊頭中包含狀態的 Merkle Tree Root,這是好仍是很差?)
Ethereum 中每個區塊,每個帳戶都會持續佔用節點的存儲空間。Ethereum 節點在同步的時候有多種模式,在 Archive 模式下全部的歷史和狀態都會保存下來,其中歷史包括歷史交易和歷史狀態,全部數據加起來的大小超過了 2TB;在 Default 模式下,歷史狀態會被裁剪掉,本地只保留歷史交易和當前狀態,全部數據加起來大約是 170G,其中交易歷史大小是 150G,當前狀態大小是 10G。Ethereum 中全部的開銷管理都被統一到 gas 計費模型之下,交易的大小須要消耗對應的 gas,而每一條 EVM 指令消耗的 gas,不只考慮了計算開銷,也將存儲開銷考慮在內。經過每一個區塊的 gaslimit,間接限制了歷史和狀態的增加速度。
ps. 常見的一個誤解是:Ethereum 的「區塊鏈大小」已經超過 1T 了。從上面的分析咱們能夠看到,「區塊鏈大小」是一個很是模糊的定義,若是把歷史狀態算進去,它確實超過了,可是對於全節點來講,把歷史狀態刪掉沒有任何問題,由於只要有 Genesis 和交易歷史,任意時刻的歷史狀態均可以從新被計算出來(不考慮計算須要的時間)。真正有意義的數據,是全節點必須的數據的大小,Bitcoin 是 200G,Ethereum 是 170G,二者是基本相同的,並且在平均配置的雲主機上都能裝下,所以人們觀察到的 Ethereum 全節點減小 並非因爲存儲增長致使的(根本緣由是同步時的計算開銷,這裏不展開了)。考慮到 Ethereum 的歷史長度(當前區塊的 timestamp 減去 genesis 的 timestamp)不到 Bitcoin 的一半,能夠看出 Ethereum 的歷史和狀態大小增加更快。
The Tragedy of (Storage) Commons:區塊鏈版本的公地悲劇
公地悲劇所指的是這樣一種狀況,有限的共享資源在不受任何使用限制的狀況下會被人們過分消耗。區塊鏈節點爲保存歷史和狀態付出的存儲,正是這樣一種共享資源。
區塊鏈節點爲處理交易所花費的資源有三種,CPU、存儲和網絡帶寬。CPU 和帶寬都是每一個區塊會刷新的資源,咱們能夠認爲每一個區塊間隔內都有一樣多的 CPU 和帶寬可供使用,上個區塊消耗掉的 CPU 和帶寬不會讓下個區塊可用的 CPU 和帶寬變少。對於可刷新的資源,咱們能夠經過一次性支付的交易手續費來補償節點。
與 CPU 和帶寬不一樣,存儲是一種佔用資源,在一個區塊中被佔用了的存儲,除非使用者主動釋放,不然沒法在後面的區塊中被其它使用者使用。節點須要爲存儲持續的付出成本,而使用者卻不須要爲存儲持續的支付手續費(記住交易手續費只須要支付一次)。使用者只須要在往區塊鏈寫數據的時候支付一點點手續費,就能夠永久使用一個可用性超過 Amazon S3 的存儲,其無限大的永久存儲成本須要區塊鏈網絡中的全部全節點來承擔。
Ethereum 上因爲各類 DApp 的存在,The Tragedy of (Storage) Commons 相對更加嚴重。例如,在區塊 5700001(May 30, 2018)的時候,使用狀態最多的 5 個合約是:
1.EtherDelta, 5.09%
2.IDEX, 4.17%
3.CryptoKitties, 3.05%
4.ENS, 1.92%
5.EOS Sale, 1.73%
比較有趣的是最後一個,EOS Sale。雖然 EOS 的衆籌已經完成,EOS 代幣已經在 EOS 鏈上流轉,EOS 衆籌的記錄卻永遠留在了Ethereum 的節點上,消耗 Ethereum 全節點的存儲資源。
能夠看到,在缺少管理的狀況下,區塊鏈的存儲資源會被有意或者無心的濫用。在一個設計合理的經濟模型中,使用者必須承擔存儲佔用的成本,這個成本不只僅與佔用存儲空間的大小成正比,還與佔用時間的長度成正比。
不管是歷史仍是狀態數據都會佔用存儲資源。經過上面對 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。以 ERC20 Token 爲例,全部用戶的資產記錄都存放在單個 ERC20 合約的存儲裏面,在這種狀況下,應該由誰來支付租金?
解決狀態爆炸問題也是 Nervos CKB 的設計目標之一,爲此 CKB 走了一條徹底不一樣的、更爲完全的變革之路。