重构智能合约(中):平行宇宙与无限扩展
1、前言
本文是小蟻的兩位創始人過去兩年中在設計小蟻智能合約時所做的深度思考和技術探索的結果。《重構智能合約》系列文章將分為上、中、下三篇,分別從確定性和資源控制、擴展性和耦合度、通用性和生態兼容三個方面來剖析現有智能合約系統的優缺點,并提出新的智能合約體系的設計思路。
在上一篇《重構智能合約(上):非確定性的幽靈》中我們分析了智能合約對確定性、資源控制和隔離的需求,得出了虛擬機作為智能合約的執行環境在上述三點都相對與容器技術具備相當的優勢。本篇中,我們將從擴展性的角度繼續比較幾種智能合約系統的優劣,并提出改進的思路。
2、執行環境的性能
智能合約的執行環境會對合約的性能起到非常重要的作用。目前主流的區塊鏈架構對智能合約執行環境的設計主要分為兩種:虛擬機和容器(Docker)。無論是虛擬機還是容器,它們的作用都是在一個沙盒中執行合約代碼,并對合約所使用的資源進行隔離和限制。
1) 虛擬機
虛擬機通常是指能夠像真實機器一樣執行程序的計算機的軟件實現。有些虛擬機會模擬出一個完整的物理計算機,比如VMware、Hyper-V等,可以在這些虛擬機上安裝操作系統和應用程序;另一些虛擬機則只提供了硬件的抽象層,而與具體的底層硬件無關,例如Java虛擬機。
區塊鏈智能合約系統的設計中,很少會采用模擬完整物理計算機的模式,因為這種方式會消耗大量的資源并嚴重影響性能,且很難兼容不同的硬件架構。所以絕大多數的區塊鏈會采用更加輕量級的虛擬機架構,例如以太坊開發了EVM,R3 Corda則直接采用了JVM,還有一些區塊鏈采用了V8引擎——Google的JavaScript引擎(虛擬機)。
當我們分析執行環境的性能時,有兩個指標是非常關鍵的:(1)指令的執行速度(2)執行環境本身的啟動速度。對于智能合約而言,執行環境的啟動速度往往要比指令的執行速度更為重要。智能合約中較多是一些甚少涉及IO操作的邏輯判斷指令,這些指令的執行速度很容易得到優化。上一篇《重構智能合約(上):非確定性的幽靈》中,我們提到了出于安全性考慮,智能合約必須在相互隔離的沙盒執行環境中運行。每個智能合約每次被調用,都必須啟動一個新的虛擬機/容器。因此執行環境本身的啟動速度(啟動一個虛擬機/容器)對智能合約系統的性能影響更大。
上述的EVM、JVM、V8引擎這些輕量級的虛擬機架構對智能合約的性能提升有顯著的優勢。它們的啟動速度非常快,占用資源也很小,適合像智能合約這樣短小的程序。缺點是,這類虛擬機的執行效率會相對略低,好在智能合約一般都比較短小,會更加注重環境加載的速度而非代碼執行的速度。另外,通過JIT(即時編譯器)技術對熱點智能合約進行靜態編譯和緩存可以顯著提升虛擬機的執行效率。
2) 容器(Docker)
與其它主流區塊鏈設計不同的是,超級帳本中的子項目Fabric獨樹一幟地采用Docker作為其智能合約的執行環境。與虛擬機的作用一樣,Docker也進行了資源的隔離,但不如虛擬機那么隔離充分。Docker本身沒有采用虛擬化技術,而是讓程序直接運行在底層操作系統上,因此代碼執行的效率很高。但由于其相對于輕量級虛擬機而言過于龐大的體型,部署和啟動Docker本身需要消耗大量的時間和資源。當使用在智能合約系統時,Docker的啟動時間成為了制約整體效率的瓶頸。Fabric在性能測試時,即便用上了IBM的大型機LinuxONE這樣的強悍硬件,性能依然不高。
執行環境的代碼執行速度就好比汽車的最高時速,而執行環境的啟動速度則好比汽車的0-100km/h加速度。和一般程序相比,智能合約短小精悍,總是處在“啟動—停止—啟動—停止”的狀態,很少能夠跑到極速。執行環境的啟動速度才是影響智能合約性能的關鍵因素。
3、并發、分片與無限擴展
當談及一個系統的擴展性時,總會涉及到兩個詞Scale Up(垂直擴展)和Scale Out(水平擴展)。最典型的垂直擴展案例是單核時代的CPU——主要靠提高主頻達到性能的提升。垂直擴展很容易就碰上天花板,當CPU制程工藝的提升越來越困難后,通過多核實現水平擴展,對指令進行并行處理,成為了提升CPU性能的重要手段。
正因為垂直擴展會很快觸及造價、技術的極限,一個不可拆分業務的串行系統的擴展性(或曰性能提升能力)就會很弱——它取決于單臺設備的最大處理能力。當我們需要對系統進行擴展時,如果有辦法將串行系統改造成并行系統,那么理論上我們將可以獲得近乎無限的擴展性。我們在考慮對區塊鏈系統進行擴展時,是否有無限擴展的可能?換言之,區塊鏈能否并行地對業務進行處理?
區塊鏈是一個分布式的大賬本,里面記錄了各式各樣的狀態數據,同時也記錄了這些狀態如何變化的規則,智能合約正是用來記錄這些規則的載體。區塊鏈能否并行地對業務進行處理,就取決于多個智能合約能否并發執行——即合約的執行是否是順序無關的。
舉個例子,某賬戶中有10元的余額,現有兩個合約對該賬戶進行修改,第一個合約在賬戶中增加5元,第二個合約在賬戶中扣除11元。如果先執行前者,則最終賬戶的余額為4元;如果先執行后者,由于余額不足,第二個合約將會執行失敗,而第一筆合約會執行成功,最終賬戶余額為15元。像這樣的兩個合約由于執行的順序不同而導致不同的結果,那么它們是不可以并發執行的,只能串行處理。
反過來,如果兩個合約分別對兩個不同的賬戶進行修改,那么它們無論哪一個先執行,結果都不會不同,所以它們是可以并發執行的。
從上面的例子可以看出,兩個合約是否可以并行處理,取決于這兩個合約是否是順序無關的;而是否順序無關,則取決于他們是否能夠對同一條狀態記錄進行修改。
基于上面的分析,我們可以很容易設計出一個具備“無限擴展”能力的智能合約系統。只需要簡單地規定:(1)一個智能合約只能修改屬于該合約自己的狀態記錄;(2)同一個事務批次(區塊)中,一個合約只能被運行一次。這樣一來,所有的智能合約之間都是順序無關可以平行處理了。干的漂亮!
但是,等等……如果“一個智能合約只能修改屬于該合約自己的狀態記錄”,就意味著合約間無法相互調用,每個合約都是一個孤島;如果“一個區塊中,一個合約只能被運行一次”,就意味著用智能合約發行的某種數字資產在一個區塊里只能處理一筆交易。這顯然和“智能”二字的設計初衷大相徑庭。畢竟合約間的相互調用,同一區塊中多次調用同一個合約,都是我們想要的設計目標。
這樣一來情況就變得復雜多了,特別是像以太坊這種支持動態調用(通過CALL指令)的智能合約系統,不可能在運行前就判斷出合約的行為和調用路徑,也就無法判斷合約會修改哪些狀態記錄。因此,以太坊的擴展性一直是其設計上的一大弊病,其目前的架構設計難以支撐以太坊成為“全球計算平臺”的遠大愿景。為了解決擴展性問題,以太坊提出了分片(Sharding)方案:
打個比方,分片就類似于戶籍制度。計算一個合約的散列值再對256取模,就可以把合約分配到256個片區中去,這相當于給每個合約分配了一個該片區的“戶口”。江蘇戶口的合約只能調用江蘇的合約,上海戶口的合約就只能調用上海的,不能直接彼此調用。
這樣一來,江蘇、上海等256個片區的合約就可以按片區進行并行處理了,看起來執行效率可以得到256倍的提升。但是在這種設計下,想要跨片區調用,就必須向一個全局賬本(區塊鏈)寫入調用請求,另一片區的合約收到請求后再執行操作,并再次寫入全局賬本來返回調用結果。這導致了跨片區調用無法在同一個業務批次(區塊)中完成,效率顯著降低。在真實的應用場景中,分片的結果很可能是大家都擠到一個“繁華片區”中去,因為這樣才能最高效的進行相互調用,避免跨區操作。在城市郊區修建再多的干道,也無法解決市中心的擁堵問題。
另外,智能合約代碼的加載方式也會影響到擴展性。目前主流的區塊鏈智能合約系統都會要求將智能合約代碼發布到鏈上,然后再從鏈上加載代碼執行。有些合約代碼可能只被使用一次就廢棄了,但在區塊鏈中永久性地存在,占用節點的存儲資源,久而久之這些廢棄代碼會成為區塊鏈的巨大負擔,影響擴展性。
另一種方案,是將智能合約的散列值記錄在鏈上,用IPFS等以散列值為索引的新型分布式存儲網絡來存儲完整合約代碼。在執行合約的時候,再從鏈外加載代碼。由于合約的散列值已經在鏈上記錄,即使從鏈外加載代碼也不用擔心合約的內容被篡改,這樣可以為節點節省大量的存儲空間。同時也能對智能合約的內容進行一定程度的隱私保護。
4、耦合度
耦合是指兩個或兩個以上的實體相互依賴于對方的一個量度。在區塊鏈與智能合約系統的設計中,對于耦合度的控制有兩個非常極端的例子:
1) 以太坊
以太坊在智能合約系統的設計中是高耦合的典型,區塊鏈與EVM之間到處充斥著相互依賴的關系,例如:
費用的計算混雜在虛擬機的實現邏輯中;
虛擬機指令集中包含大量用于訪問賬本數據的指令;
虛擬機直接提供以區塊鏈賬本作為載體的持久化存儲指令。
將區塊鏈的業務邏輯與虛擬機混在一起,并不是一個良好的設計。這會造成一系列的問題,一旦區塊鏈的功能需要改進或者升級,勢必就要對EVM也進行相應的修改,這種修改多數情況都會體現在增加新的指令上;而EVM幾乎沒有辦法移植到其它區塊鏈系統中,除非另一個鏈的底層架構與以太坊高度一致,或者專門針對EVM開發一個對接層。這種模式會對以太坊的生態應用造成很大的局限性,關于這一點我們將會在《重構智能合約(下):兼容性與生態》中詳述。
2) Fabric
與以太坊的設計模式相反,Fabric的智能合約系統采用了低耦合的設計,區塊鏈賬本與Docker之間幾乎沒有任何依賴關系,因為Docker本身就被廣泛應用于區塊鏈以外的大量場景之中。在Docker中運行的智能合約程序只能通過gRPC協議與節點進行通信,協議中包含了訪問賬本和持久化存儲的功能。當區塊鏈的功能需要改進或者升級時,只需要對gRPC協議進行改動即可。這種超低耦合度的設計模式值得其它區塊鏈的開發者學習參考。
高內聚、低耦合是設計系統架構時所常常追求的目標。Fabric的設計目標是打造通用的許可型區塊鏈的技術框架,因此一開始就采用了高度模塊化的設計思想;而以太坊最初的設計目標是一個具體的公有鏈實例,而非技術框架。因此以太坊中存在著系統耦合度過高的問題。這將會妨礙以太坊作為一種通用技術被使用在聯盟鏈、私有鏈上。
5、小結
在本篇中,我們分析了智能合約系統理想的執行環境性能、并發處理能力、耦合度和代碼加載方式,發現了以太坊的一些高度抽象,無灰度的設計帶來的擴展性問題,提出了可以做到理論上“無限擴展”的高度并行化智能合約系統的設計思路。我們認為一個可以良好進行并發執行的智能合約系統,應該具有以下特征:
輕量級的執行環境:快速的啟動時間和較高的執行效率。
可插拔的執行環境架構:默認的執行環境應該不提供持久化存儲,從而讓合約默認是一種類似于微服務的無狀態函數,從而可以直接并發處理。僅在需要存儲狀態時,才提供可插拔的持久化存儲模塊。這樣的虛擬機默認只有一個CPU和棧,僅在需要時才提供“硬盤”和其他IO設備。
明示化的調用關系:即只提供靜態調用的功能,從而使得程序的調用關系可以在運行它之前就整理清楚。一旦調用路徑明確了,那么合約可能會修改到的狀態數據也就明確了,依據這些明示的調用路徑就可以進行即時的動態分片提高合約執行的并行能力。
可鏈外存儲的合約代碼:通過鏈上存儲散列值,鏈外存儲合約代碼實現存儲空間的擴展性。
低耦合度的設計:合約語言、執行環境、區塊鏈之間的低耦合度,提高智能合約系統的通用性。
在本系列最后一篇《重構智能合約(下):兼容性與生態》中,我們將會分析現有智能合約系統對編程語言的選擇,并提出一種讓非區塊鏈開發者也能立即編寫智能合約的新模式。
參考閱讀:《重構智能合約(上):非確定性的幽靈》
https://36kr.com/p/5067556.html
總結
以上是生活随笔為你收集整理的重构智能合约(中):平行宇宙与无限扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重构智能合约(上):非确定性的幽灵
- 下一篇: 第一篇 - 手把手教你理清EOS各种开发