經典:淺談以太坊智能合約的設計模式與升級方法

  • 目錄
    • 1. 最佳實踐
    • 2. 實用設計案例
      • 2.1 控制器合約與數據合約: 1->1
      • 2.2 控制器合約與數據合約: 1->N
      • 2.3 控制器合約與數據合約: N->1
      • 2.4 控制器合約與數據合約: N->N
      • 2.5 總結
    • 3. 升級
      • 3.1 控制器合約升級,數據合約不升級
      • 3.2 控制器合約不升級,數據合約升級
      • 3.3 控制器合約升級,數據合約升級
    • 4. 數據遷移
      • 4.1 硬編碼遷移法
      • 4.2 硬拷貝遷移法
      • 4.3 默克爾樹遷移法

以太坊EVM是當前區塊鏈行業應用最爲普遍的虛擬機。其所支持的智能合約語言是圖靈完備的。該語言支持各類基礎類型(Booleans,Integers,Address,String,Enum,Address等)、複雜類型(Struct,Mapping,Array等)、複雜的表達式和控制結構及接口繼承等面向對象的特性。git

正是因爲強大的智能合約語言,本來在真實世界中的複雜商業邏輯和應用都能在區塊鏈上輕鬆實現。然而須要注意的是,儘管公有鏈能夠實現合理的GAS機制自我保護,聯盟鏈能夠用其餘機制替代GAS的計算及代幣化來保障EVM沙盒安全,但因爲區塊鏈運行機制的緣由,智能合約的運行即便是異常運行都會在全部區塊鏈節點上獨立重複運行。所以,不管是在公有鏈仍是聯盟鏈運行智能合約都是很是昂貴(運算資源、存儲資源)的操做。github

另外,智能合約與傳統應用程序有一個不一樣的地方在於智能合約一經發佈於區塊鏈上就沒法篡改,即便智能合約中有Bug須要修復或者業務邏輯變動,它也不能直接在原有的合約上直接修改再從新發布。所以在設計之初就須要結合業務場景考慮合理的升級機制。安全

總而言之,智能合約實現上要達到的目標是:完備的業務功能、精悍的代碼邏輯、良好的模塊抽象、清晰的合約結構、合理的安全檢查、完備的升級方案。數據結構

智能合約的生命週期主要有設計、開發、部署、運行、升級、銷燬。在下文中主要是基於目標在設計階段、升級階段的一些梳理總結。架構

1. 最佳實踐

從業務視角來看,智能合約只須要作兩件事,其一是如何定義數據的結構和讀寫方式,其二是如何處理數據並對外提供服務接口。app

爲了更好的作好模塊抽象和合約結構分層,將這兩件事分開,既是將業務控制邏輯和數據從合約代碼層面就作好分離,這樣的處理在複雜業務邏輯場景中通過實踐是當前被認爲最佳的模式。模塊化

這個模式簡稱爲CD(Controller-Data)模式。將合約分爲兩類:控制器合約(Controller Contract)與數據合約(Data Contract)。工具

CD模式

控制器合約經過訪問數據合約得到數據,並對數據作邏輯處理,而後寫回數據合約。�它專一於對數據的邏輯處理和對外提供服務。根據處理邏輯的不一樣,常見的有命名空間控制器合約、代理控制器合約、業務控制器合約、工廠控制器合約等。通常狀況下,控制器合約不須要存儲任何數據,它徹底依賴外部的輸入來決定對數據合約的訪問。特殊狀況下,控制器合約能夠存儲某個固定的數據合約的地址或者命名空間(經過命名空間在運行時得到合約地址)。性能

數據合約專一於數據結構定義與所存儲數據的讀寫裸接口。爲了達到數據統一訪問管理和數據訪問權限控制的目的,最好是將數據讀寫接口只暴露給對應的控制器合約。禁止其餘方式的讀寫訪問。區塊鏈

基於這個模式,遵循從上至下的分析方式,從對外提供的服務接口開始設計各種控制器合約,再逐步過渡到服務接口所須要的數據模型和存儲方式,進而設計各種數據合約,能夠較爲快速的完成合約架構的設計。

2. 實用設計案例

在CD模式下,根據控制器合約與數據合約之間的操做關係,從邏輯上歸結爲四類:

  1. 控制器合約與數據合約 1->1
  2. 控制器合約與數據合約 1->N
  3. 控制器合約與數據合約 N->1
  4. 控制器合約與數據合約 N->N

假設一個業務場景:將全國全部銀行的業務和信息上鍊。

2.1 控制器合約與數據合約: 1->1

假設全國只有兩家銀行,A銀行和B銀行。A銀行只有存款業務,B銀行只有取款業務。一種可能的設計是這樣的:

CD模式

代理控制器合約:面向Dapp,是全部業務合約的入口,提供命名空間服務,提供了命名空間到合約地址的映射。使得Dapp對鏈上合約升級致使的地址變動無感知。例如,Dapp對A銀行的存款請求只須要(「BankA",deposit,args) 便可。對B銀行的取款請求只須要(」BankB",withdraw,args)便可。代理器控制合約實現上應該是區塊鏈底層內置的、固化的,或者是業務上極少變動的。Dapp在業務運行以前已經明確知道代理控制器合約的地址。

命名控制器合約:面向鏈上合約,提供命名空間服務,提供了命名空間到合約地址的映射。使得鏈上合約能夠在運行時根據命名得到實際的合約地址。例如,A銀行控制器合約向命名控制器合約請求(「BankA-Data"),能夠得到A銀行數據合約地址,使得A銀行控制器合約能夠在運行時訪問A銀行數據合約。它與代理控制器合約的主要不一樣在於服務對象的不一樣,代理控制器合約面向Dapp,命名控制器合約面向鏈上合約。另外,命名控制器合約包含有版本控制的設計(下文第3.2節介紹),能夠根據須要配合灰度策略的實施。

A銀行控制器合約:提供了存款服務接口deposit。部署初始化時已經明確知道本身的身份」BankA"。而且能夠在運行時經過命名控制合約得到」BankA「的數據合約「BankA-Data"的地址。

A銀行數據合約:保存了A銀行的當前餘額。提供add和sub接口給A銀行控制器合約來更新餘額信息。

B銀行控制器合約:提供了存款服務接口withdraw。部署初始化時已經明確知道本身的身份」BankB"。而且能夠在運行時經過命名控制合約得到」BankB「的數據合約"BankB-Data"的地址。

B銀行數據合約:保存了B銀行的當前餘額。提供add和sub接口給B銀行控制器合約來更新餘額信息。

對A銀行的存款請求的流程是這樣的:

  1. Dapp指定代理控制器合約地址,請求存款交易(「BankA",deposit,money)
  2. 代理控制器合約,運行時獲得」BankA"對應的A銀行控制器合約地址,並向A銀行控制器合約請求存款交易(deposit,money)
  3. A銀行控制器合約的deposit接口向命名控制器合約請求A銀行的數據合約「BankA-Data"的地址,並訪問到A銀行數據合約的數據,而後執行一些存款業務邏輯。返回結果。
  4. 依次返回結果到Dapp。

2.2 控制器合約與數據合約: 1->N

假設全國有N家銀行,全部銀行都有存款業務和取款業務,而且業務流程都是同樣的。一種可能的設計是這樣的:

CD模式

這個設計與上面的2.1不同的地方在於,將存款服務接口和取款接口都集中歸結到銀行業務控制器合約裏面了。這意味着任何銀行的存款和取款業務都由銀行業務控制器合約來統一處理,處理邏輯上再也不區分是A銀行仍是B銀行,只是在數據訪問的時候須要根據入參的不一樣來決定訪問不一樣的銀行數據合約。

還有,於2.1相比,對於Dapp而言,它發出請求的時候只須要將請求發往固定的」Bank"就能夠了,不用具體關心某個銀行。

另外,因爲銀行有不少個,而且它們的存儲結構都是同樣的,所以能夠設計一個銀行數據合約的工廠控制器合約,來負責對新的數據合約的生成實現模板化。

對A銀行的存款請求的流程是這樣的:

  1. Dapp指定代理控制器合約地址,請求存款交易(「Bank",deposit,」BankA「,money)
  2. 代理控制器合約,運行時獲得」Bank"對應的銀行業務控制器合約地址,向銀行業務控制器合約請求存款交易(deposit,」BankA「,money)
  3. 銀行業務控制器合約的deposit接口向命名控制器合約請求A銀行的數據合約「BankA-Data"的地址,並訪問到A銀行數據合約的數據,而後執行一些存款業務邏輯。返回結果。
  4. 依次返回結果到Dapp。

2.3 控制器合約與數據合約: N->1

假設全國有N家銀行,全部銀行都有存款業務和取款業務,而且業務流程都是同樣的,可是因爲業務邏輯較爲複雜,出於模塊化維護的須要,須要將存款業務和取款業務作分拆。一種可能的設計是這樣的:

CD模式

這個設計與上面的2.2不同的地方在於,將存款服務接口和取款接口拆分到了不一樣的業務控制器合約裏面了。這意味着不一樣的業務邏輯從模塊上作了清晰的切分。對於Dapp而言,它發出請求的時候須要明確指向所對應的業務接口。

對A銀行的存款請求的流程是這樣的:

  1. Dapp指定代理控制器合約地址,請求存款交易(「deposit",」BankA「,money)
  2. 代理控制器合約,運行時獲得」deposit"對應的存款業務控制器合約地址,向存款業務控制器合約請求存款交易(」BankA「,money)
  3. 存款業務控制器合約的deposit接口向命名控制器合約請求A銀行的數據合約「BankA-Data"的地址,並訪問到A銀行數據合約的數據,而後執行一些存款業務邏輯。返回結果。
  4. 依次返回結果到Dapp。

2.4 控制器合約與數據合約: N->N

此類狀況能夠拆解爲上面三種狀況的組合。再也不贅述。

2.5 總結

從Dapp視角考慮,能夠總結以下:

CD模式 特色
1->1 面向業務對象
1->N 面向業務流程
N->1 面向業務接口
N->N /

3. 升級

在CD模式下,在業務邏輯變動須要升級合約的狀況下,根據控制器合約與數據合約的升級關係來劃分,能夠概括爲如下三種狀況:

控制器合約 數據合約
升級 不升級
不升級 升級
升級 升級

在升級過程當中,還須要考慮是全量升級仍是灰度升級?若是是灰度升級,灰度策略是怎麼樣的?另外,在多鏈場景和單鏈場景、跨鏈場景,升級過程是否有不一樣?多鏈場景的灰度策略如何考慮?新舊版本數據可否共存?若是須要數據遷移,如何作到無縫遷移?

下面以最爲常見的1->N 場景來介紹不一樣的升級狀況。

3.1 控制器合約升級,數據合約不升級

CD模式

如上圖所示,銀行業務控制器合約從V1升級到V2,而其餘的合約和接口都是不須要更新的,假設V2版本相對V1版本只是升級withdraw這個接口。

此時,V2版本的銀行業務控制器合約須要作的事情是:

  1. 繼承V1版本的銀行業務控制器合約。
  2. 增長一個指向V1版本的鏈上合約地址的成員變量。
  3. 增長一個withdraw開關接口,容許外部帳戶經過普通交易來操做V2版本合約的啓停灰度策略。
  4. 重載withdraw接口。升級對應的接口邏輯。而且在業務邏輯真正開始執行以前,自定義實現灰度策略(譬如灰度特定用戶,或者必定比例用戶或者其餘策略)。而且須要注意的是在打開灰度開關的狀況下,若是請求沒有命中灰度策略,則直接透傳參數調用V1版本的合約接口,V2版本的withdraw接口不作任何額外工做。

完成V2版本的合約工做以後,便可發佈一個普通交易,交易中的邏輯是,先部署V2版本的銀行業務控制器合約,再將其地址更新到代理控制器合約中,使得將「Bank」映射到V2版本的合約地址上。這樣控制器合約即升級完成。

若是須要回退版本,只須要發佈一個普通交易,將代理控制器合約的「Bank」映射到V1版本的合約地址上便可。

以上是單鏈場景的升級方法。若是是多鏈場景,只需根據業務的須要來判斷鏈與鏈之間的灰度策略,重複單鏈場景的升級便可。若是是跨鏈場景,須要根據跨鏈兩端的具體狀況來制定升級方法。

而對於業務發起端的Dapp而言,它是無任何感知的。它對A銀行的存款請求與2.2中徹底同樣。依舊是以(「Bank",deposit,」BankA「,money)來發出請求。

總結而言,灰度策略定義在新版本的控制器合約中,數據無需遷移,業務無感知,無需中止服務。無縫升級。

3.2 控制器合約不升級,數據合約升級

CD模式

如上圖所示,A銀行數據合約從V1升級到V2。而其餘的合約和接口都是不須要更新,假設V2版本相對V1版本只是增長新的數據字段loan,並假設銀行業務控制器合約本來就能支持到V2版本的A銀行數據合約(若是是銀行業務控制器合約也須要升級則是3.3節的場景,這裏不作描述)。

此時,V2版本的A銀行數據合約須要作的事情是:

  1. 繼承V1版本的銀行數據合約。
  2. 增長一個新字段loan。並實現loan相關的數據接口。

須要注意的是,命名控制器合約有以下重要的設計:

  1. 命名控制器合約是經過訪問命名數據合約來存儲和訪問數據的(爲了方便描述,圖中並無畫出來),所以命令控制器合約是能夠參考3.1節的方法來升級的。
  2. 命名數據合約保存了name=>mapping(version=>address)的映射表。
  3. 命名數據合約保存了name=>當前有效的version的映射表。
  4. 命名控制器合約提供了對命名數據合約的name進行遍歷的接口。
  5. 命名控制器合約提供了對命名數據合約的映射表的變動接口。

所以,完成V2版本的數據合約以後,便可發佈一個普通交易,交易中的邏輯是,先部署V2版本的A銀行數據合約,並完成V1版本數據合約到V2版本數據合約的數據遷移(數據遷移方法第4節會描述),接着將V2版本數據合約地址註冊到命名控制器合約,並更新BankA-Data所映射的當前有效verison=V2。此時已完成了A銀行數據合約的V2版本升級。

若是須要回退版本,只須要發佈一個普通交易,將命名控制器合約的BankA-Data所映射的當前有效verison=V1便可。

而對於業務發起端的Dapp而言,它是無任何感知的。它對A銀行的存款請求與2.2中徹底同樣。依舊是以(「Bank",deposit,」BankA「,money)來發出請求。

對於B銀行而言,由於B銀行數據合約並無執行升級,因此與它相關的業務請求依然是訪問的B銀行數據合約的V1版本。因此,對於歷史舊版本的數據合約,能夠根據業務的須要來判斷是否須要對歷史舊版本執行升級。有些特殊場景下,須要對全部的歷史舊版本數據合約進行升級,這時能夠利用命名控制器合約的遍歷功能,對全部數據合約進行相似的升級。而對於新加入的C銀行,它能夠直接使用最新版本V2的數據合約,按照正常流程完成部署與註冊,無任何額外操做。

正是因爲有了命名控制器合約的版本控制邏輯,可使得即便存在新老版本數據合約並存的狀況下,業務控制器類合約依然能正常運行。而對於因爲業務的發展和不斷的版本升級,會帶來命名數據合約的存儲量膨脹,致使可能出現的性能降低的狀況,依然能夠套用本節所述的數據遷移與升級的方法來解決。

以上是單鏈場景的升級方法。若是是多鏈場景,只需根據業務的須要來判斷鏈與鏈之間的灰度策略,重複單鏈場景的升級便可。若是是跨鏈場景,須要根據跨鏈兩端的具體狀況來制定升級方法。

總結而言,得益於命名控制器合約的版本控制設計,灰度策略能夠交給業務方很是自由地選擇,業務無感知,無需中止服務。無縫升級。

3.3 控制器合約升級,數據合約升級

此種狀況下,實質是3.1與3.2 兩種狀況的混搭。

所以根據具體狀況,拆解成參考3.1和3.2場景方法來執行便可。

4. 數據遷移

如3.2節所描述,在數據合約升級的場景,某些狀況須要處理歷史數據在新舊合約之間的遷移。遷移的方法有以下三種,各有特色。

4.1 硬編碼遷移法

硬編碼遷移法指的是,新版本的數據合約中保存一個指向舊版本數據合約的合約地址,新版本數據合約保存的是增量的數據內容。

這樣至關於新版本合約保留了一份舊版本數據的指針,當新版本須要使用舊數據的時候,直接調用舊數據合約地址對應數據接口便可。這樣,新舊版本數據合約能夠並存,即便是在異常狀況下,數據被誤寫到了舊版本合約上,它依然能夠被新版本所訪問到。

這個方法的優勢是:新舊合約能夠同時並存,不增長區塊鏈存儲壓力,簡單靈活,較強的升級容錯能力。缺點:持續不斷的版本升級會致使造成較長的鏈式邏輯關係,維護成本較高。

4.2 硬拷貝遷移法

硬拷貝遷移法指的是,新版本和舊版本之間切斷邏輯關係,利用外部遷移工具,將舊版本數據逐步拷貝到鏈下,再從鏈下從新存儲到新版本合約的過程。

CD模式

這個方法的優勢是:無歷史包袱。缺點是:大幅度增長區塊鏈存儲壓力;數據遷移工具須要適配不一樣的數據合約,開發成本較高;遷移過程須要中止服務,不然容易出現髒數據;數據量大時,耗時長,操做複雜,容易出錯,基本沒法實操。

4.3 默克爾樹遷移法

默克爾數遷移法要點以下:

  1. 利用智能合約語言的面向對象的繼承特性,使得新版本合約存儲結構徹底兼容舊版本合約存儲結構。
  2. 利用智能合約在區塊鏈上的storage樹原理,使得新版本合約的storeage樹直接從舊版本合約上衍生。無需顯式的遷移過程。
  3. 利用區塊鏈交易的原子性,使得新版本合約的部署、數據遷移、升級,原子完成。

這個方法擁有前面兩個方法的全部優勢,且簡單高效,安全,實操性強。缺點:須要區塊鏈底層功能特性的支持。

做者:fisco-dev  https://github.com/FISCO-BCOS/Wiki

注:若是看本文的乾貨頭大,那我就夾帶個私貨推薦一個適合區塊鏈新手的以太坊DApp開發教程:http://t.cn/RnmDmaD

相關文章
相關標籤/搜索