浅谈以太坊智能合约的设计模式与升级方法
- 淺談以太坊智能合約的設計模式與升級方法
- 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等)、復雜的表達式和控制結構及接口繼承等面向對象的特性。
正是由于強大的智能合約語言,原本在真實世界中的復雜商業邏輯和應用都能在區塊鏈上輕松實現。然而需要注意的是,盡管公有鏈可以實現合理的GAS機制自我保護,聯盟鏈可以用其他機制替代GAS的計算及代幣化來保障EVM沙盒安全,但由于區塊鏈運行機制的原因,智能合約的運行即使是異常運行都會在所有區塊鏈節點上獨立重復運行。因此,無論是在公有鏈還是聯盟鏈運行智能合約都是非常昂貴(運算資源、存儲資源)的操作。
另外,智能合約與傳統應用程序有一個不同的地方在于智能合約一經發布于區塊鏈上就無法篡改,即使智能合約中有Bug需要修復或者業務邏輯變更,它也不能直接在原有的合約上直接修改再重新發布。因此在設計之初就需要結合業務場景考慮合理的升級機制。
總而言之,智能合約實現上要達到的目標是:完備的業務功能、精悍的代碼邏輯、良好的模塊抽象、清晰的合約結構、合理的安全檢查、完備的升級方案。
智能合約的生命周期主要有設計、開發、部署、運行、升級、銷毀。在下文中主要是基于目標在設計階段、升級階段的一些梳理總結。
1. 最佳實踐
從業務視角來看,智能合約只需要做兩件事,其一是如何定義數據的結構和讀寫方式,其二是如何處理數據并對外提供服務接口。
為了更好的做好模塊抽象和合約結構分層,將這兩件事分開,既是將業務控制邏輯和數據從合約代碼層面就做好分離,這樣的處理在復雜業務邏輯場景中經過實踐是當前被認為最佳的模式。
這個模式簡稱為CD(Controller-Data)模式。將合約分為兩類:控制器合約(Controller Contract)與數據合約(Data Contract)。
控制器合約通過訪問數據合約獲得數據,并對數據做邏輯處理,然后寫回數據合約。�它專注于對數據的邏輯處理和對外提供服務。根據處理邏輯的不同,常見的有命名空間控制器合約、代理控制器合約、業務控制器合約、工廠控制器合約等。一般情況下,控制器合約不需要存儲任何數據,它完全依賴外部的輸入來決定對數據合約的訪問。特殊情況下,控制器合約可以存儲某個固定的數據合約的地址或者命名空間(通過命名空間在運行時獲得合約地址)。
數據合約專注于數據結構定義與所存儲數據的讀寫裸接口。為了達到數據統一訪問管理和數據訪問權限控制的目的,最好是將數據讀寫接口只暴露給對應的控制器合約。禁止其他方式的讀寫訪問。
基于這個模式,遵循從上至下的分析方式,從對外提供的服務接口開始設計各類控制器合約,再逐步過渡到服務接口所需要的數據模型和存儲方式,進而設計各類數據合約,可以較為快速的完成合約架構的設計。
2. 實用設計案例
在CD模式下,根據控制器合約與數據合約之間的操作關系,從邏輯上歸結為四類:
假設一個業務場景:將全國所有銀行的業務和信息上鏈。
2.1 控制器合約與數據合約: 1->1
假設全國只有兩家銀行,A銀行和B銀行。A銀行只有存款業務,B銀行只有取款業務。一種可能的設計是這樣的:
代理控制器合約:面向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銀行的存款請求的流程是這樣的:
2.2 控制器合約與數據合約: 1->N
假設全國有N家銀行,所有銀行都有存款業務和取款業務,并且業務流程都是一樣的。一種可能的設計是這樣的:
這個設計與上面的2.1不一樣的地方在于,將存款服務接口和取款接口都集中歸結到銀行業務控制器合約里面了。這意味著任何銀行的存款和取款業務都由銀行業務控制器合約來統一處理,處理邏輯上不再區分是A銀行還是B銀行,只是在數據訪問的時候需要根據入參的不同來決定訪問不同的銀行數據合約。
還有,于2.1相比,對于Dapp而言,它發出請求的時候只需要將請求發往固定的”Bank"就可以了,不用具體關心某個銀行。
另外,由于銀行有很多個,并且它們的存儲結構都是一樣的,因此可以設計一個銀行數據合約的工廠控制器合約,來負責對新的數據合約的生成實現模板化。
對A銀行的存款請求的流程是這樣的:
2.3 控制器合約與數據合約: N->1
假設全國有N家銀行,所有銀行都有存款業務和取款業務,并且業務流程都是一樣的,但是由于業務邏輯較為復雜,出于模塊化維護的需要,需要將存款業務和取款業務做分拆。一種可能的設計是這樣的:
這個設計與上面的2.2不一樣的地方在于,將存款服務接口和取款接口拆分到了不同的業務控制器合約里面了。這意味著不同的業務邏輯從模塊上做了清晰的切分。對于Dapp而言,它發出請求的時候需要明確指向所對應的業務接口。
對A銀行的存款請求的流程是這樣的:
2.4 控制器合約與數據合約: N->N
此類情況可以拆解為上面三種情況的組合。不再贅述。
2.5 總結
從Dapp視角考慮,可以總結如下:
| 1->1 | 面向業務對象 |
| 1->N | 面向業務流程 |
| N->1 | 面向業務接口 |
| N->N | / |
3. 升級
在CD模式下,在業務邏輯變更需要升級合約的情況下,根據控制器合約與數據合約的升級關系來劃分,可以歸納為以下三種情況:
| 升級 | 不升級 |
| 不升級 | 升級 |
| 升級 | 升級 |
在升級過程中,還需要考慮是全量升級還是灰度升級?如果是灰度升級,灰度策略是怎么樣的?另外,在多鏈場景和單鏈場景、跨鏈場景,升級過程是否有不同?多鏈場景的灰度策略如何考慮?新舊版本數據能否共存?如果需要數據遷移,如何做到無縫遷移?
下面以最為常見的1->N 場景來介紹不同的升級情況。
3.1 控制器合約升級,數據合約不升級
如上圖所示,銀行業務控制器合約從V1升級到V2,而其他的合約和接口都是不需要更新的,假設V2版本相對V1版本只是升級withdraw這個接口。
此時,V2版本的銀行業務控制器合約需要做的事情是:
完成V2版本的合約工作之后,即可發布一個普通交易,交易中的邏輯是,先部署V2版本的銀行業務控制器合約,再將其地址更新到代理控制器合約中,使得將“Bank”映射到V2版本的合約地址上。這樣控制器合約即升級完成。
如果需要回退版本,只需要發布一個普通交易,將代理控制器合約的“Bank”映射到V1版本的合約地址上即可。
以上是單鏈場景的升級方法。如果是多鏈場景,只需根據業務的需要來判斷鏈與鏈之間的灰度策略,重復單鏈場景的升級即可。如果是跨鏈場景,需要根據跨鏈兩端的具體情況來制定升級方法。
而對于業務發起端的Dapp而言,它是無任何感知的。它對A銀行的存款請求與2.2中完全一樣。依舊是以(“Bank",deposit,”BankA“,money)來發出請求。
總結而言,灰度策略定義在新版本的控制器合約中,數據無需遷移,業務無感知,無需停止服務。無縫升級。
3.2 控制器合約不升級,數據合約升級
如上圖所示,A銀行數據合約從V1升級到V2。而其他的合約和接口都是不需要更新,假設V2版本相對V1版本只是增加新的數據字段loan,并假設銀行業務控制器合約原本就能支持到V2版本的A銀行數據合約(如果是銀行業務控制器合約也需要升級則是3.3節的場景,這里不做描述)。
此時,V2版本的A銀行數據合約需要做的事情是:
需要注意的是,命名控制器合約有如下重要的設計:
因此,完成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 硬拷貝遷移法
硬拷貝遷移法指的是,新版本和舊版本之間切斷邏輯關系,利用外部遷移工具,將舊版本數據逐步拷貝到鏈下,再從鏈下重新存儲到新版本合約的過程。
這個方法的優點是:無歷史包袱。缺點是:大幅度增加區塊鏈存儲壓力;數據遷移工具需要適配不同的數據合約,開發成本較高;遷移過程需要停止服務,否則容易出現臟數據;數據量大時,耗時長,操作復雜,容易出錯,基本無法實操。
4.3 默克爾樹遷移法
默克爾數遷移法要點如下:
這個方法擁有前面兩個方法的所有優點,且簡單高效,安全,實操性強。缺點:需要區塊鏈底層功能特性的支持。
https://github.com/toxotguo/thinking/blob/master/%E6%B5%85%E8%B0%88%E4%BB%A5%E5%A4%AA%E5%9D%8A%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%8D%87%E7%BA%A7%E6%96%B9%E6%B3%95.md
總結
以上是生活随笔為你收集整理的浅谈以太坊智能合约的设计模式与升级方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 区块链技术:智能合约入门
- 下一篇: fetch用英语解释_fetch的意思在