浅谈以太坊智能合约的设计模式与升级方法
- 淺談以太坊智能合約的設計模式與升級方法
- 1. 最佳實踐
- 2. 實用設計案例
- 2.1 控制器合約與數(shù)據(jù)合約: 1->1
- 2.2 控制器合約與數(shù)據(jù)合約: 1->N
- 2.3 控制器合約與數(shù)據(jù)合約: N->1
- 2.4 控制器合約與數(shù)據(jù)合約: N->N
- 2.5 總結
- 3. 升級
- 3.1 控制器合約升級,數(shù)據(jù)合約不升級
- 3.2 控制器合約不升級,數(shù)據(jù)合約升級
- 3.3 控制器合約升級,數(shù)據(jù)合約升級
- 4. 數(shù)據(jù)遷移
- 4.1 硬編碼遷移法
- 4.2 硬拷貝遷移法
- 4.3 默克爾樹遷移法
淺談以太坊智能合約的設計模式與升級方法
以太坊EVM是當前區(qū)塊鏈行業(yè)應用最為廣泛的虛擬機。其所支持的智能合約語言是圖靈完備的。該語言支持各種基礎類型(Booleans,Integers,Address,String,Enum,Address等)、復雜類型(Struct,Mapping,Array等)、復雜的表達式和控制結構及接口繼承等面向對象的特性。
正是由于強大的智能合約語言,原本在真實世界中的復雜商業(yè)邏輯和應用都能在區(qū)塊鏈上輕松實現(xiàn)。然而需要注意的是,盡管公有鏈可以實現(xiàn)合理的GAS機制自我保護,聯(lián)盟鏈可以用其他機制替代GAS的計算及代幣化來保障EVM沙盒安全,但由于區(qū)塊鏈運行機制的原因,智能合約的運行即使是異常運行都會在所有區(qū)塊鏈節(jié)點上獨立重復運行。因此,無論是在公有鏈還是聯(lián)盟鏈運行智能合約都是非常昂貴(運算資源、存儲資源)的操作。
另外,智能合約與傳統(tǒng)應用程序有一個不同的地方在于智能合約一經(jīng)發(fā)布于區(qū)塊鏈上就無法篡改,即使智能合約中有Bug需要修復或者業(yè)務邏輯變更,它也不能直接在原有的合約上直接修改再重新發(fā)布。因此在設計之初就需要結合業(yè)務場景考慮合理的升級機制。
總而言之,智能合約實現(xiàn)上要達到的目標是:完備的業(yè)務功能、精悍的代碼邏輯、良好的模塊抽象、清晰的合約結構、合理的安全檢查、完備的升級方案。
智能合約的生命周期主要有設計、開發(fā)、部署、運行、升級、銷毀。在下文中主要是基于目標在設計階段、升級階段的一些梳理總結。
1. 最佳實踐
從業(yè)務視角來看,智能合約只需要做兩件事,其一是如何定義數(shù)據(jù)的結構和讀寫方式,其二是如何處理數(shù)據(jù)并對外提供服務接口。
為了更好的做好模塊抽象和合約結構分層,將這兩件事分開,既是將業(yè)務控制邏輯和數(shù)據(jù)從合約代碼層面就做好分離,這樣的處理在復雜業(yè)務邏輯場景中經(jīng)過實踐是當前被認為最佳的模式。
這個模式簡稱為CD(Controller-Data)模式。將合約分為兩類:控制器合約(Controller Contract)與數(shù)據(jù)合約(Data Contract)。
控制器合約通過訪問數(shù)據(jù)合約獲得數(shù)據(jù),并對數(shù)據(jù)做邏輯處理,然后寫回數(shù)據(jù)合約。�它專注于對數(shù)據(jù)的邏輯處理和對外提供服務。根據(jù)處理邏輯的不同,常見的有命名空間控制器合約、代理控制器合約、業(yè)務控制器合約、工廠控制器合約等。一般情況下,控制器合約不需要存儲任何數(shù)據(jù),它完全依賴外部的輸入來決定對數(shù)據(jù)合約的訪問。特殊情況下,控制器合約可以存儲某個固定的數(shù)據(jù)合約的地址或者命名空間(通過命名空間在運行時獲得合約地址)。
數(shù)據(jù)合約專注于數(shù)據(jù)結構定義與所存儲數(shù)據(jù)的讀寫裸接口。為了達到數(shù)據(jù)統(tǒng)一訪問管理和數(shù)據(jù)訪問權限控制的目的,最好是將數(shù)據(jù)讀寫接口只暴露給對應的控制器合約。禁止其他方式的讀寫訪問。
基于這個模式,遵循從上至下的分析方式,從對外提供的服務接口開始設計各類控制器合約,再逐步過渡到服務接口所需要的數(shù)據(jù)模型和存儲方式,進而設計各類數(shù)據(jù)合約,可以較為快速的完成合約架構的設計。
2. 實用設計案例
在CD模式下,根據(jù)控制器合約與數(shù)據(jù)合約之間的操作關系,從邏輯上歸結為四類:
假設一個業(yè)務場景:將全國所有銀行的業(yè)務和信息上鏈。
2.1 控制器合約與數(shù)據(jù)合約: 1->1
假設全國只有兩家銀行,A銀行和B銀行。A銀行只有存款業(yè)務,B銀行只有取款業(yè)務。一種可能的設計是這樣的:
代理控制器合約:面向Dapp,是所有業(yè)務合約的入口,提供命名空間服務,提供了命名空間到合約地址的映射。使得Dapp對鏈上合約升級導致的地址變更無感知。例如,Dapp對A銀行的存款請求只需要(“BankA",deposit,args) 即可。對B銀行的取款請求只需要(”BankB",withdraw,args)即可。代理器控制合約實現(xiàn)上應該是區(qū)塊鏈底層內置的、固化的,或者是業(yè)務上極少變更的。Dapp在業(yè)務運行之前已經(jīng)明確知道代理控制器合約的地址。
命名控制器合約:面向鏈上合約,提供命名空間服務,提供了命名空間到合約地址的映射。使得鏈上合約可以在運行時根據(jù)命名獲得實際的合約地址。例如,A銀行控制器合約向命名控制器合約請求(“BankA-Data"),可以獲得A銀行數(shù)據(jù)合約地址,使得A銀行控制器合約可以在運行時訪問A銀行數(shù)據(jù)合約。它與代理控制器合約的主要不同在于服務對象的不同,代理控制器合約面向Dapp,命名控制器合約面向鏈上合約。另外,命名控制器合約包含有版本控制的設計(下文第3.2節(jié)介紹),可以根據(jù)需要配合灰度策略的實施。
A銀行控制器合約:提供了存款服務接口deposit。部署初始化時已經(jīng)明確知道自己的身份”BankA"。并且可以在運行時通過命名控制合約獲得”BankA“的數(shù)據(jù)合約“BankA-Data"的地址。
A銀行數(shù)據(jù)合約:保存了A銀行的當前余額。提供add和sub接口給A銀行控制器合約來更新余額信息。
B銀行控制器合約:提供了存款服務接口withdraw。部署初始化時已經(jīng)明確知道自己的身份”BankB"。并且可以在運行時通過命名控制合約獲得”BankB“的數(shù)據(jù)合約"BankB-Data"的地址。
B銀行數(shù)據(jù)合約:保存了B銀行的當前余額。提供add和sub接口給B銀行控制器合約來更新余額信息。
對A銀行的存款請求的流程是這樣的:
2.2 控制器合約與數(shù)據(jù)合約: 1->N
假設全國有N家銀行,所有銀行都有存款業(yè)務和取款業(yè)務,并且業(yè)務流程都是一樣的。一種可能的設計是這樣的:
這個設計與上面的2.1不一樣的地方在于,將存款服務接口和取款接口都集中歸結到銀行業(yè)務控制器合約里面了。這意味著任何銀行的存款和取款業(yè)務都由銀行業(yè)務控制器合約來統(tǒng)一處理,處理邏輯上不再區(qū)分是A銀行還是B銀行,只是在數(shù)據(jù)訪問的時候需要根據(jù)入?yún)⒌牟煌瑏頉Q定訪問不同的銀行數(shù)據(jù)合約。
還有,于2.1相比,對于Dapp而言,它發(fā)出請求的時候只需要將請求發(fā)往固定的”Bank"就可以了,不用具體關心某個銀行。
另外,由于銀行有很多個,并且它們的存儲結構都是一樣的,因此可以設計一個銀行數(shù)據(jù)合約的工廠控制器合約,來負責對新的數(shù)據(jù)合約的生成實現(xiàn)模板化。
對A銀行的存款請求的流程是這樣的:
2.3 控制器合約與數(shù)據(jù)合約: N->1
假設全國有N家銀行,所有銀行都有存款業(yè)務和取款業(yè)務,并且業(yè)務流程都是一樣的,但是由于業(yè)務邏輯較為復雜,出于模塊化維護的需要,需要將存款業(yè)務和取款業(yè)務做分拆。一種可能的設計是這樣的:
這個設計與上面的2.2不一樣的地方在于,將存款服務接口和取款接口拆分到了不同的業(yè)務控制器合約里面了。這意味著不同的業(yè)務邏輯從模塊上做了清晰的切分。對于Dapp而言,它發(fā)出請求的時候需要明確指向所對應的業(yè)務接口。
對A銀行的存款請求的流程是這樣的:
2.4 控制器合約與數(shù)據(jù)合約: N->N
此類情況可以拆解為上面三種情況的組合。不再贅述。
2.5 總結
從Dapp視角考慮,可以總結如下:
| 1->1 | 面向業(yè)務對象 |
| 1->N | 面向業(yè)務流程 |
| N->1 | 面向業(yè)務接口 |
| N->N | / |
3. 升級
在CD模式下,在業(yè)務邏輯變更需要升級合約的情況下,根據(jù)控制器合約與數(shù)據(jù)合約的升級關系來劃分,可以歸納為以下三種情況:
| 升級 | 不升級 |
| 不升級 | 升級 |
| 升級 | 升級 |
在升級過程中,還需要考慮是全量升級還是灰度升級?如果是灰度升級,灰度策略是怎么樣的?另外,在多鏈場景和單鏈場景、跨鏈場景,升級過程是否有不同?多鏈場景的灰度策略如何考慮?新舊版本數(shù)據(jù)能否共存?如果需要數(shù)據(jù)遷移,如何做到無縫遷移?
下面以最為常見的1->N 場景來介紹不同的升級情況。
3.1 控制器合約升級,數(shù)據(jù)合約不升級
如上圖所示,銀行業(yè)務控制器合約從V1升級到V2,而其他的合約和接口都是不需要更新的,假設V2版本相對V1版本只是升級withdraw這個接口。
此時,V2版本的銀行業(yè)務控制器合約需要做的事情是:
完成V2版本的合約工作之后,即可發(fā)布一個普通交易,交易中的邏輯是,先部署V2版本的銀行業(yè)務控制器合約,再將其地址更新到代理控制器合約中,使得將“Bank”映射到V2版本的合約地址上。這樣控制器合約即升級完成。
如果需要回退版本,只需要發(fā)布一個普通交易,將代理控制器合約的“Bank”映射到V1版本的合約地址上即可。
以上是單鏈場景的升級方法。如果是多鏈場景,只需根據(jù)業(yè)務的需要來判斷鏈與鏈之間的灰度策略,重復單鏈場景的升級即可。如果是跨鏈場景,需要根據(jù)跨鏈兩端的具體情況來制定升級方法。
而對于業(yè)務發(fā)起端的Dapp而言,它是無任何感知的。它對A銀行的存款請求與2.2中完全一樣。依舊是以(“Bank",deposit,”BankA“,money)來發(fā)出請求。
總結而言,灰度策略定義在新版本的控制器合約中,數(shù)據(jù)無需遷移,業(yè)務無感知,無需停止服務。無縫升級。
3.2 控制器合約不升級,數(shù)據(jù)合約升級
如上圖所示,A銀行數(shù)據(jù)合約從V1升級到V2。而其他的合約和接口都是不需要更新,假設V2版本相對V1版本只是增加新的數(shù)據(jù)字段loan,并假設銀行業(yè)務控制器合約原本就能支持到V2版本的A銀行數(shù)據(jù)合約(如果是銀行業(yè)務控制器合約也需要升級則是3.3節(jié)的場景,這里不做描述)。
此時,V2版本的A銀行數(shù)據(jù)合約需要做的事情是:
需要注意的是,命名控制器合約有如下重要的設計:
因此,完成V2版本的數(shù)據(jù)合約之后,即可發(fā)布一個普通交易,交易中的邏輯是,先部署V2版本的A銀行數(shù)據(jù)合約,并完成V1版本數(shù)據(jù)合約到V2版本數(shù)據(jù)合約的數(shù)據(jù)遷移(數(shù)據(jù)遷移方法第4節(jié)會描述),接著將V2版本數(shù)據(jù)合約地址注冊到命名控制器合約,并更新BankA-Data所映射的當前有效verison=V2。此時已完成了A銀行數(shù)據(jù)合約的V2版本升級。
如果需要回退版本,只需要發(fā)布一個普通交易,將命名控制器合約的BankA-Data所映射的當前有效verison=V1即可。
而對于業(yè)務發(fā)起端的Dapp而言,它是無任何感知的。它對A銀行的存款請求與2.2中完全一樣。依舊是以(“Bank",deposit,”BankA“,money)來發(fā)出請求。
對于B銀行而言,因為B銀行數(shù)據(jù)合約并沒有執(zhí)行升級,所以與它相關的業(yè)務請求依然是訪問的B銀行數(shù)據(jù)合約的V1版本。所以,對于歷史舊版本的數(shù)據(jù)合約,可以根據(jù)業(yè)務的需要來判斷是否需要對歷史舊版本執(zhí)行升級。有些特殊場景下,需要對所有的歷史舊版本數(shù)據(jù)合約進行升級,這時可以利用命名控制器合約的遍歷功能,對所有數(shù)據(jù)合約進行類似的升級。而對于新加入的C銀行,它可以直接使用最新版本V2的數(shù)據(jù)合約,按照正常流程完成部署與注冊,無任何額外操作。
正是由于有了命名控制器合約的版本控制邏輯,可以使得即使存在新老版本數(shù)據(jù)合約并存的情況下,業(yè)務控制器類合約依然能正常運行。而對于由于業(yè)務的發(fā)展和不斷的版本升級,會帶來命名數(shù)據(jù)合約的存儲量膨脹,導致可能出現(xiàn)的性能下降的情況,依然可以套用本節(jié)所述的數(shù)據(jù)遷移與升級的方法來解決。
以上是單鏈場景的升級方法。如果是多鏈場景,只需根據(jù)業(yè)務的需要來判斷鏈與鏈之間的灰度策略,重復單鏈場景的升級即可。如果是跨鏈場景,需要根據(jù)跨鏈兩端的具體情況來制定升級方法。
總結而言,得益于命名控制器合約的版本控制設計,灰度策略可以交給業(yè)務方非常自由地選擇,業(yè)務無感知,無需停止服務。無縫升級。
3.3 控制器合約升級,數(shù)據(jù)合約升級
此種情況下,實質是3.1與3.2 兩種情況的混搭。
因此根據(jù)具體情況,拆解成參考3.1和3.2場景方法來執(zhí)行即可。
4. 數(shù)據(jù)遷移
如3.2節(jié)所描述,在數(shù)據(jù)合約升級的場景,某些情況需要處理歷史數(shù)據(jù)在新舊合約之間的遷移。遷移的方法有如下三種,各有特點。
4.1 硬編碼遷移法
硬編碼遷移法指的是,新版本的數(shù)據(jù)合約中保存一個指向舊版本數(shù)據(jù)合約的合約地址,新版本數(shù)據(jù)合約保存的是增量的數(shù)據(jù)內容。
這樣相當于新版本合約保留了一份舊版本數(shù)據(jù)的指針,當新版本需要使用舊數(shù)據(jù)的時候,直接調用舊數(shù)據(jù)合約地址對應數(shù)據(jù)接口即可。這樣,新舊版本數(shù)據(jù)合約可以并存,即使是在異常情況下,數(shù)據(jù)被誤寫到了舊版本合約上,它依然可以被新版本所訪問到。
這個方法的優(yōu)點是:新舊合約可以同時并存,不增加區(qū)塊鏈存儲壓力,簡單靈活,較強的升級容錯能力。缺點:持續(xù)不斷的版本升級會導致形成較長的鏈式邏輯關系,維護成本較高。
4.2 硬拷貝遷移法
硬拷貝遷移法指的是,新版本和舊版本之間切斷邏輯關系,利用外部遷移工具,將舊版本數(shù)據(jù)逐步拷貝到鏈下,再從鏈下重新存儲到新版本合約的過程。
這個方法的優(yōu)點是:無歷史包袱。缺點是:大幅度增加區(qū)塊鏈存儲壓力;數(shù)據(jù)遷移工具需要適配不同的數(shù)據(jù)合約,開發(fā)成本較高;遷移過程需要停止服務,否則容易出現(xiàn)臟數(shù)據(jù);數(shù)據(jù)量大時,耗時長,操作復雜,容易出錯,基本無法實操。
4.3 默克爾樹遷移法
默克爾數(shù)遷移法要點如下:
這個方法擁有前面兩個方法的所有優(yōu)點,且簡單高效,安全,實操性強。缺點:需要區(qū)塊鏈底層功能特性的支持。
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的意思在