• <option id="cacee"><noscript id="cacee"></noscript></option>
  • <table id="cacee"><noscript id="cacee"></noscript></table>
  • <td id="cacee"></td>
  • <option id="cacee"></option>
  • <table id="cacee"></table>
  • <option id="cacee"><option id="cacee"></option></option>
  • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
    <option id="cacee"><option id="cacee"></option></option>
     找回密碼
     立即注冊

    掃一掃,登錄網站

    首頁 自媒體 查看內容
    • 2355
    • 0
    • 分享到

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

    2018-8-6 21:34

    來源: Uni-times

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

    正是由于強大的智能合約語言,原本在真實世界中的復雜商業邏輯和應用都能在區塊鏈上輕松實現。然而需要注意的是,盡管公有鏈可以實現合理的GAS機制自我保護,聯盟鏈可以用其他機制替代GAS的計算及代幣化來保障EVM沙盒安全,但由于區塊鏈運行機制的原因,智能合約的運行即使是異常運行都會在所有區塊鏈節點上獨立重復運行。因此,無論是在公有鏈還是聯盟鏈運行智能合約都是非常昂貴(運算資源、存儲資源)的操作。

    另外,智能合約與傳統應用程序有一個不同的地方在于智能合約一經發布于區塊鏈上就無法篡改,即使智能合約中有Bug需要修復或者業務邏輯變更,它也不能直接在原有的合約上直接修改再重新發布。因此在設計之初就需要結合業務場景考慮合理的升級機制。

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

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


    1. 最佳實踐

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

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

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


    控制器合約通過訪問數據合約獲得數據,并對數據做邏輯處理,然后寫回數據合約。它專注于對數據的邏輯處理和對外提供服務。根據處理邏輯的不同,常見的有命名空間控制器合約、代理控制器合約、業務控制器合約、工廠控制器合約等。一般情況下,控制器合約不需要存儲任何數據,它完全依賴外部的輸入來決定對數據合約的訪問。特殊情況下,控制器合約可以存儲某個固定的數據合約的地址或者命名空間(通過命名空間在運行時獲得合約地址)。

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

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


    2. 實用設計案例

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

    1. 控制器合約與數據合約 1->1

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

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

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

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


    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-Data“的合約地址。

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

    B銀行控制器合約:提供了存款服務接口withdraw。部署初始化時已經明確知道自己的身份”BankB"。運行時通過命名控制合約獲得”BankB“的合約地址。

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

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

    1. Dapp指定代理控制器合約地址,請求存款交易(“BankA",deposit,money)

    2. 代理控制器合約,運行時得到”BankA"對應的A銀行控制器合約地址,并向A銀行控制器合約請求存款交易(deposit,money)

    3. A銀行控制器合約的deposit接口向命名控制器合約請求“BankA-Data"獲得A銀行數據數據合約地址,并訪問到A銀行數據合約的數據,然后執行一些存款業務邏輯。返回結果。

    4. 依次返回結果到Dapp。


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

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


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

    還有,于2.1相比,對于Dapp而言,它發出請求的時候只需要將請求發往固定的”Bank"就可以了,不用具體關心某個銀行。

    另外,由于銀行有很多個,并且它們的存儲結構都是一樣的,因此可以設計一個銀行數據合約的工廠控制器合約,來負責對新的數據合約的生成模塊化。

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

    1. Dapp指定代理控制器合約地址,請求存款交易(“Bank",deposit,”BankA“,money)

    2. 代理控制器合約,運行時得到”Bank"對應的銀行業務控制器合約地址,向銀行業務控制器合約請求存款交易(deposit,”BankA“,money)

    3. 銀行業務控制器合約的deposit接口向命名控制器合約請求“BankA-Data"獲得A銀行數據數據合約地址,并訪問到A銀行數據合約的數據,然后執行一些存款業務邏輯。返回結果。

    4. 依次返回結果到Dapp。


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

    假設全國有N家銀行,所有銀行都有存款業務和取款業務,并且業務流程都是一樣的,但是由于業務邏輯較為復雜,出于模塊化維護的需要,需要將存款業務和取款業務做分拆。一種可能的設計是這樣的:


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

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

    1. Dapp指定代理控制器合約地址,請求存款交易(“deposit",”BankA“,money)

    2. 代理控制器合約,運行時得到”deposit"對應的存款業務控制器合約地址,向存款業務控制器合約請求存款交易(”BankA“,money)

    3. 存款業務控制器合約的deposit接口向命名控制器合約請求“BankA-Data"獲得A銀行數據數據合約地址,并訪問到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 控制器合約升級,數據合約不升級


    如上圖所示,銀行業務控制器合約從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 控制器合約不升級,數據合約升級


    如上圖所示,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 硬拷貝遷移法

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


    這個方法的優點是:無歷史包袱。缺點是:大幅度增加區塊鏈存儲壓力;數據遷移工具需要適配不同的數據合約,開發成本較高;遷移過程需要停止服務,否則容易出現臟數據;數據量大時,耗時長,操作復雜,容易出錯,基本無法實操。


    4.3 默克爾樹遷移法

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

    1. 利用智能合約語言的面向對象的繼承特性,使得新版本合約存儲結構完全兼容舊版本合約存儲結構。

    2. 利用智能合約在區塊鏈上的storage樹原理,使得新版本合約的storeage樹直接從舊版本合約上衍生。無需顯式的遷移過程。

    3. 利用區塊鏈交易的原子性,使得新版本合約的部署、數據遷移、升級,原子完成。


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


    作者:郭世清

    來源:以太坊愛好者社區

    原文鏈接:

    https://github.com/toxotguo/thinking/blob/master/淺談以太坊智能合約的設計模式與升級方法.md

    著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

    版權申明:本內容來自于互聯網,屬第三方匯集推薦平臺。本文的版權歸原作者所有,文章言論不代表鏈門戶的觀點,鏈門戶不承擔任何法律責任。如有侵權請聯系QQ:3341927519進行反饋。
    相關新聞
    發表評論

    請先 注冊/登錄 后參與評論

      回頂部
    • <option id="cacee"><noscript id="cacee"></noscript></option>
    • <table id="cacee"><noscript id="cacee"></noscript></table>
    • <td id="cacee"></td>
    • <option id="cacee"></option>
    • <table id="cacee"></table>
    • <option id="cacee"><option id="cacee"></option></option>
    • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
      <option id="cacee"><option id="cacee"></option></option>
      妖精视频