重構智能合約(上):非確定性的幽靈
1. 前言
本文是小蟻的兩位創始人過去兩年中在設計小蟻智能合約時所做的深度思考和技術探索的結果。《重構智能合約》系列文章將分為上、中、下三篇,分別從確定性和資源控制、擴展性和耦合度、通用性和生態兼容三個方面來剖析現有智能合約系統的優缺點,并提出新的智能合約體系的設計思路。
2. 智能合約與區塊鏈
自從比特幣、以太坊的相繼誕生,以及區塊鏈技術的逐步升溫,智能合約一詞便開始頻繁的出現在金融和科技媒體之中。智能合約是1994年由密碼學家尼克薩博(Nick Szabo)***提出的理念,幾乎與互聯網同齡。根據Nick Szabo的定義:智能合約是指能夠自動執行合約條款的計算機程序。
傳統意義上的合約的生命周期一般包含:各方協商、簽名記錄、條款執行三個階段,智能合約也類似。但是在區塊鏈技術出現之前,單臺計算機難以提供安全可靠的簽名記錄和條款執行服務,所以智能合約也一直僅僅停留在理論階段。比特幣及區塊鏈技術出現后,其多方存儲、多方計算、規則透明、不可篡改等特性,恰好為智能合約提供了安全可靠的記錄載體和執行環境。
與普通的計算機程序不同,區塊鏈上的智能合約必須同時具備兩種性質:確定性和可終止性。
區塊鏈是一個通過多方存儲、多方計算的方式來實現數據不可篡改、計算結果可信的分布式系統。智能合約會在區塊鏈網絡的多個節點中運行。如果一個智能合約是非確定性的,那么不同節點運行的結果就可能不一致,從而導致共識無法達成,網絡陷入停滯;如果智能合約是永不停止的,那么節點將耗費無窮多的時間和資源去運行合約,同樣導致網絡進入停滯狀態。這就涉及到了兩個有趣的話題,下面我們分別來探討一下確定性和可終止性問題(停機問題)。
3. 智能合約的確定性
如果一個程序在不同的計算機、或者在同一臺計算機上的不同時刻多次運行,對于相同的輸入能夠保證產生相同的輸出,則稱該程序的行為是確定性的,反之則稱該程序的行為是非確定性的。使程序產生非確定性的因素有很多,總結起來有以下幾種:
1) 調用了非確定性的系統函數
一般在編寫程序的時候,開發者或多或少會調用一些系統提供的函數和功能以減少開發的工作量。這些系統函數中可能會存在一些非確定性的函數,比如生成隨機數、獲取系統時間等。一旦程序調用了另一個非確定性的程序并使用了它們輸出的內容,那么該程序自身的行為也可能會變為非確定性的。
2) 使用了非確定性的數據來源
如果一個程序在運行時獲取數據,而數據源提供的是非確定性的數據,那么該程序也可能會變成非確定性的程序。例如,通過搜索引擎來獲取某個關鍵詞的前10條搜索結果——搜索引擎針對不同的IP地址來源可能會返回不同的排序結果。
3) 動態調用
動態調用是指,一個程序在調用另一個程序時,如果必須在運行時才能確定被調用的目標,則稱該調用為動態調用;反之,如果在運行前即可確定被調用的目標,且在運行時無法變更該目標,則稱該調用為靜態調用。由于動態調用的目標在運行時決定,因此其行為是非確定的。
對于區塊鏈上的智能合約,我們一般要求它的行為必須是確定性的,因為非確定性的合約可能會破壞系統的一致性。區塊鏈的作者必須考慮到這個問題,并在設計智能合約系統的時候,就想辦法把非確定性因素排除在外。那么我們來看看現有的各個區塊鏈是如何解決這個問題的。
1) 比特幣
比特幣內置了一套腳本引擎,用于執行鑒權腳本,它是區塊鏈智能合約的雛形。開發者可以基于這套腳本系統來開發一些簡單的應用,但由于其指令集非常簡單且非圖靈完備,能夠實現功能相當有限。這套系統既沒有提供任何系統函數,也沒有提供任何訪問數據的能力,更沒有動態調用的功能,甚至連靜態調用也沒有提供,因此比特幣的智能合約一定是確定性的。
2) 以太坊
以太坊的主要設計思想,就是提供一個圖靈完備的智能合約平臺,讓用戶可以編寫任意邏輯的程序。它專門開發了一個用于執行合約代碼的虛擬機EVM,并設計了一種類似于Java的高級語言Solidity,以方便用戶進行開發。以太坊智能合約沒有提供任何非確定性的系統函數,可訪問的數據也僅限于鏈內數據,外部數據需要通過交易來發送到合約。但是,以太坊的CALL和CALLCODE指令的目標地址通過棧來傳遞,使得合約可以在運行時動態調用其它的合約代碼,使合約的調用路徑變為非確定性。好在合約可以訪問到的數據都是確定性的,使得所有節點在動態調用目標代碼時一定會獲得相同的目標地址,保證了系統的一致性。但是調用路徑的非確定性,會導致一個可擴展性上的重要性能損失,具體會在《重構智能合約(中):平行宇宙與***擴展》中詳述。
3) Fabric
Fabric是超級賬本中的一個子項目,它的智能合約采用了重量級的Docker作為執行環境。這可能跟大家的印象有點矛盾——“Docker不是一直被認為是一種輕量級的容器技術嗎?”。實際上,Docker的“輕”是相對于模擬物理機架構的重量級虛擬化技術而言。在區塊鏈應用場景下,Docker是一個比較“重”的執行環境,這也是Fabric的性能瓶頸所在,目前只能達到每秒幾百TPS。由于Docker的特性,智能合約幾乎可以使用物理計算機上的所有功能,因此具有極高的非確定性。所以Fabric要求智能合約的開發者在編寫代碼的時候盡量避免使用到具有非確定性的功能,并計劃提供一套專門開發的確定性系統函數庫供開發者使用。 然而,由于無法從底層機制上避免非確定性的產生,寄托于開發者遵守良好的開發規范難免有些一廂情愿。非確定性就像幽靈一般,平時似乎并不存在,在一些邊緣案例(corner case)上就可能會突然冒出來造成難以判斷的故障。
4. 停機問題與資源控制
停機問題(halting problem)是邏輯數學中可計算性理論的一個問題。通俗地說,停機問題就是判斷任意程序是否能在有限的時間之內結束運行的問題。該問題等價于如下的判定問題:是否存在一個程序P,對于任意輸入的程序w,能夠判斷w會在有限時間內結束或者死循環。
艾倫·圖靈在1936年用對角論證法證明了我們無法編寫出程序P——即不存在解決停機問題的通用算法。這個證明的關鍵在于對計算機和程序的數學定義,這被稱為圖靈機。停機問題在圖靈機上是不可判定問題。這是最早提出的決定性問題之一。
區塊鏈上的智能合約必須是可終止的,否則將會消耗***的時間和資源。從上面的論證已經可以看出,停機問題是不可解的,我們無法在不運行一個程序的情況下,提前判定該程序是否會停機。因此,區塊鏈的設計者不得不假設所有的智能合約都可能會進入死循環,并對于可能已經進入死循環的合約采用異常終止的方式來結束它。通常會有以下幾種策略:
1) 非圖靈完備
如果一個區塊鏈的智能合約系統,只提供了有限的指令集,而不提供諸如跳轉、循環等指令,以及可以等效實現類似功能的指令,那么基于這個系統的智能合約就不可能進入死循環,因此它們總是可停機的,比特幣就是其中的一個例子。值得一提的是,比特幣改進計劃BIP12的提案中,有過在比特幣指令集中增加一條OP_EVAL指令的計劃,這條指令可以加載計算棧中的腳本并動態執行,從而解決多重簽名算法的問題。但由于該指令會間接地使得比特幣的腳本系統變為圖靈完備,因此最終被廢棄了。
2) 計價器
既然無法在智能合約運行之前就判定其是否會停機,那么也可以在合約運行中進行計步——每執行一條指令就將計步器加一。當計步器的數值超過一定的限制之后,就認為合約已經進入死循環,從而強行終止它的運行。這種方法需要區塊鏈對智能合約的執行有較大的控制能力,能夠精確的計算出合約執行的步數且在各個節點間保持一致。
計價器的方案和計步器基本上是一致的,但是它采用經濟手段來解決停機問題:對合約執行的每一個指令進行收費,當合約的手續費全部用完之后,如果合約還沒有終止,那么就強行終止退出。以太坊采用的就是這個模式,它通過消耗一種被稱為燃料的代幣來對智能合約進行計價。一旦燃料耗盡,合約就會執行失敗,并且不會退回消耗掉的費用。
3) 計時器
計時器和計價器的方案比較類似,但它使用時間作為標準來衡量一個合約是否已經進入死循環:如果合約在超時時間到達之前還沒有正常終止,那么就認為它已進入死循環并強行終止它。Fabric采用了計時器的方案,原因是Fabric使用Docker作為其智能合約的執行環境,而Docker中運行的程序是無法計步或計價的。雖然計時器可以部分地解決停機問題,但是在分布式系統中,每個節點的執行時長未必能夠保證一致,且每個節點的性能和負載都不一樣,導致對合約運行是否超時這一判斷會出現不一致的情況,從而使得共識算法的失敗率大大的增加,這是計時器方案的重要缺點。
上述的非圖靈完備、計價器、計時器方案,實際上都是一種資源控制手段,即通過對代碼量設定上限,對運算資源計價,或對執行時間設定上限等方法來將智能合約占用的資源控制在合理的范圍。
5. 資源隔離
虛擬化的執行環境除了實現資源控制外這一目的外,更重要的是通過資源隔離來保障系統的安全。在開放的區塊鏈上,任何參與者都可以編寫并上傳智能合約,圖靈完備的智能合約就意味著可以編寫并執行任意的邏輯——包括病毒或故障合約如果智能合約直接在區塊鏈節點的宿主系統上運行,病毒就能夠自我復制,故障合約就可能破壞宿主系統的自身數據。因此智能合約必須放在一個隔離的沙盒環境中運行——虛擬機或者容器。
通過沙盒執行環境,合約和合約之間,合約和宿主系統之間進行了有效的資源隔離,也就控制住了惡意或故障合約的影響范圍。但在資源隔離度上,Docker所依賴的基于命名空間的隔離要弱于虛擬機的隔離。正因為此,在公有云上兩個不同用戶的Docker鏡像一般不會被放在一個宿主操作系統中運行。因此,基于Docker的方案在資源隔離度的安全性上要弱于虛擬機方案。
6. 小結
通過對確定性、可終止性、資源控制、資源隔離的分析,不難發現區塊鏈上的智能合約需要具備強確定性,可終止性、精確的資源控制和資源隔離。在這一維度上,為區塊鏈量身定制虛擬機的方案相比借用現有容器的方案有著較大的優勢。而容器方案的優點在于編寫語言的靈活,不要求合約開發者學習新的編程語言,從而更容易融入現有的開發者生態。那么是否有辦法兼顧二者呢?我們會在《重構智能合約(下):兼容性與生態》中給出方案。
在此之前,我們會先通過《重構智能合約(中):平行宇宙與***擴展》來分析一下現有公有鏈、聯盟鏈的性能為何如此不堪。
關于作者
本文兩位作者均系小蟻區塊鏈(www.antshares.org)的創始人。小蟻區塊鏈是一個探索可編程智能經濟的開源公有鏈。小蟻起源于2014年,是***個區塊鏈項目。2017年,小蟻的前期技術積累將進入兌現期,會在智能合約、跨鏈互操作、共識機制、密碼學等多個方向實現技術突破。