hi 歡迎來到小祕課堂第七期,今天咱們來說講智能合約的一種設計結構的那些事兒,歡迎主講人馮開開html
講師:馮開開git
編輯:Leogithub
前言
智能合約的設計和傳統的應用設計有點不一樣。傳統應用通常爲了快速迭代是在產品以後考慮安全,可是 DApp 則須要在產品出來以前就考慮安全問題,它將會關係到帳戶資產、用戶數據等問題,並且對 DApp 來說,升級是個比較麻煩的事情,所以在智能合約設計時,結構是很是重要的部分。web
目前 DApp 面臨問題
首先,是關於 DApp 和 App。事物發展將會遵循技術爲王、產品爲王、最後到運營爲王三個發展階段。如今,區塊鏈和 DApp 正處於技術爲王階段。整個市場上的 DApp,在性能和用戶友好性上,都不如 App。DApp 的優點顯而易見:去中心化,它是依附區塊鏈的應用。可是咱們認爲不少 DApp 的短板,實際上是由於底層區塊鏈的限制。算法
其次,是關於安全。如今 DApp 爆發的安全漏洞不少,主要緣由是區塊鏈仍處於發展早期。開發 DApp 的基礎設施和相關工具都很不成熟,可是黑客是很成熟的,在互聯網上久經沙場,對 DApp 世界影響很大。因此,在設計 DApp 時,要了解區塊鏈相關知識,這些是出於安全考慮。 數據庫
最後,是關於成本。在以太坊中就是 Gas,部署智能合約將消耗必定 Gas。這是由於 DApp 很消耗 Gas,特別是部署一個大型 DApp(包括後面的維護、升級)。Gas 是什麼?是資金。那麼,有沒有一種結構可以暫時忽略 Gas。這就分紅兩種方向,一是思考節約 gas 到細微處,用一種怪異不太舒服的寫法來節約 Gas;第二種是走向宏觀,整個結構是清晰明瞭的,可是可能會存在浪費 Gas 的行爲。 後端
解決辦法
第一,是優美結構。一個優美的結構會帶來 Gas 的節約,這是我一直相信的。那整個結構是包含哪些方面呢?最宏觀的說指分層。這裏分層和通常的 app 分層是相通的,好比應用層、邏輯層、數據層。安全
第二,是友好。由於如今一個大型的 DApp,若是有很好的模塊化,可能會有十幾個智能合約,他們中間可能還有依賴。那就要求你在部署時,須要格外當心。(友好就是說可以一鍵部署。 )數據結構
第三,是支持升級。這個是在 App 上很常見的,由於咱們在開發一個 App 時,必需要進行版本迭代,新功能的增長,Bug fix...這個就是升級。而咱們知道 DApp 在區塊鏈上,因爲區塊鏈的不可篡改性,部署的合約就在那裏了。這裏面涉及升級的問題就比較複雜。簡而言之,咱們這個結構採起了支持升級的方式,升級比較簡單的部分是算法部分,算法部分是純粹的邏輯,替換能夠作到無感知,只須要修改邏輯合約的地址便可。比較麻煩的是對數據結構的升級,數據結構涉及到實際數據,若是輕易改動就要兼容之前的數據(這種需求很常見)。數據遷移是一個比較麻煩的事情,在設計數據結構之初儘可能肯定數據結構,避免頻繁的變更。數據結構肯定以後,包括 CURD 以及一些 Check 的接口其實也能夠肯定。這些能夠做爲一個最小的數據合約單元。app
第四,是訪問控制。要對整個結構中的數據流轉進行控制,這是出於安全性的考慮。
第五,是模塊化。一方面,是出於安全考慮,全部東西在一個合約裏會增長合約的複雜性,會對安全形成隱患。畢竟複雜是安全的天敵,越簡單越安全,這是軟件開發中的常識。因此咱們要保證合約的簡潔,一眼看過去就知道這個合約是幹什麼。另外一方面,是保證函數的簡單。同樣的道理,函數簡單意味着組成的合約也是簡單的。
整個分層就是應用層、邏輯層和數據層,這個結構裏面的三層,整個 DApp 是偏向後端的,這裏三層是智能合約裏面的三層。
結構設計
根據以前 hackathon 上作的項目,叫作 summerWar(區塊鏈沙盒遊戲),這個項目裏的結構基本上遵循這種設計。
summer War GitHub 倉庫地址:
https://github.com/CryptapeHackathon/SummerWars
an Arch : layer
這個是咱們整個遊戲的結構圖,最左邊的是 off-chain,是關於鏈下用戶操做。register 是應用層、Permission 是訪問控制、後面是邏輯層和數據層,後面是數據流轉和調用的圖 。
分層:register 是應用層,operator 是單純邏輯 ,和數據沒有關係。後面是數據層,整個數據單獨和邏輯拆分。
權限剛纔已經闡述了。在整個結構流轉中,若是 operator 是操做層的話,用戶須要先訪問操做層合約,而後由操做層訪問底層數據合約。這裏限制訪問控制,一是指控制各個層之間的調用關係,好比數據類合約嚴格控制只能讓操做類合約來控制,不多是隨便的合約就能訪問的。這裏數據不只是指數據,還包括數據的簡單存取接口,至關於一個數據庫的概念,包含存儲和查詢。 二是能夠控制具體用戶的操做。
An arch: auth
auth 模塊實現了上述說的兩種訪問控制,在結構上是散佈在各個層級之間及整個結構與外部用戶之間。
對用戶進行訪問控制是指:假設有 id,就會先檢查哪些地址能夠操做,這裏 id 是指整個 DApp 的身份,和區塊鏈身份不影響,由於區塊鏈因爲去中心化緣由是沒有身份的。可是開發的 DApp 裏面能夠定義一個用戶名,這個在 DApp 裏是可行的,和區塊鏈沒有關係。因此這裏能夠對這個地址進行檢查,容許哪些地址進行操做。至於層之間的訪問控制,和用戶訪問控制相似,用戶 id 是Dapp 通行的身份,各層合約能夠在 register 查詢到也能夠把這些合約地址做爲整個結構中通行的身份。
具體在底層實現要用到兩種特性,modifier 和 函數的可見性。經過這兩種特性結合可以達到以上效果:某個合約只能某些合約調用,某些合約只能由某些 sender 調用,這樣的控制。
modifier:
https://solidity.readthedocs.io/en/latest/contracts.html#function-modifiers
函數的可見性:
https://solidity.readthedocs.io/en/latest/contracts.html#visibility-and-getters
整個下來,代碼的組織結構可能就以下圖所示:
App: register
整個結構的分層,咱們先從上往下咱們開始講。首先是 App 層的 register。regitser 是什麼角色?他是註冊中心,是一個 hub,咱們指望它可以保存全部 DApp 智能合約的相關信息(地址等),這也是一個用戶接入的入口。在這裏,除 operator 後續升級和註冊外,它至關於一個交互樞紐,能夠進行部署、初始化、註冊和升級。這裏可以實如今 regiter 部署以後,整個 DApp 的智能合約部分也就部署上去了。咱們看一下 register 在整個結構中的位置,是在 off-chain 與 後面操做、數據層之間。
爲何要這樣作呢?不能直接邏輯層+數據層?這樣一箇中心化的東西會不會影響整個 DApp 的去中心化?
DApp 去中心化屬性是依靠後面區塊鏈實現的,有一箇中心化的東西並不影響。它的一個優勢是簡單。經過一個智能合約可以管理全部模塊,這個 register 是不變的,至關於一個不變的點,用來連接各個模塊,保證穩定,至關於 DApp 在區塊鏈上一直會有一個穩定的地址長期進行服務。若是,須要支持升級那麼不少模塊均可改變。然而,若是全部東西能夠改變,則會變得很難維護,因此使用 register,可以隨時經過這個東西進行查詢和操做。還有一個優勢是可以節約 Gas。只部署 register,而後就完成了整個 DApp 的部署。是怎麼實現的?這裏合約能夠用 new 來生成其餘智能合約。new 並不節約 Gas,節約的是交易相關的消耗。
Register 包含三類接口
第一類接口:初始化
也就是 Solidity 裏面的 constructor,合約的構造函數。它的功能是在部署智能合約時,一次性執行而後銷燬。因此初始化時,要存入什麼?剛說 register 是一個穩定的東西,那就能把整個結構中,一些相對穩定的東西放在這裏初始化。好比多個用戶的操做層合約是固定的,而數據層的合約隨着用戶的註冊註銷而變化,那麼就能夠把操做層合約在這裏初始化,隨着 register 的部署由其進行建立。
第二類接口:註冊
註冊是 web 調用,由外部用戶來使用的。在初始完以後,至關於整個 DApp 上線了,在用戶使用時,可能就有些功能上的註冊操做。好比 identity,用戶須要註冊一個用戶名之類。在 register 部署以後,你可以完成初始化的其餘操做,類比咱們如今的應用運行以後提供的功能。
第三類接口:查詢
這類接口的使用者分爲兩類:
-
外部用戶能夠查詢一些 register 保存的各類模塊信息。
-
當一個合約與其餘模塊通訊時,它只知道 register 地址,而更多模塊合約地址多是經過 register 來生成的隨機地址,這個時候就能夠經過 register 得到其餘模塊的地址進行之間的交互。
Logic:Operator
接着往下看是操做層的合約。這裏能夠作一些模塊化的東西,支持升級也是在這裏作的,是由於簡單。咱們一般講的支持升級包括兩個方面,一個是函數或者接口的升級。另一個是數據的升級或者說遷移。接口的升級比較容易,在區塊鏈上數據升級比較困難,由於數據複製的操做很貴。存數據一字大概 2w gas。咱們這裏優先考慮 operator 的升級。
升級有兩種方式,對應 evm 的智能合約裏面進行交互兩種指令,分別有兩種升級方式
-
一個是 call,是消息調用的一種。調用 call 時,至關於把主動權交給另外一個合約了,這個合約在一個新的 evm 執行以後返回一個結果給我。利用這個指令能夠完成一個支持升級的方式,就是在 register 作一個相似 router 的東西,記錄每個操做類合約的版本號,而後用戶就在訪問操做類合約的時候選擇版本進行下一次的調用,或者 register 幫你轉。
-
第二個是用 delegate call,相對於 call,用 delegate 時主動權並無交出去,整個智能合約代碼仍是在我如今的執行環境中執行,這是智能合約庫使用的基本指令,不少庫的實現都是基於 delegate call 的方式來作的。支持升級就是用一個代理類的合約,用戶調用時幫你進行轉發。這裏會有一個反作用,必須把操做的數據留在 proxy 裏面,這是 delegate 的屬性。由於這個屬性就須要支持升級的合約。有兩個要求,一個是純邏輯的,沒有對狀態的改變,第二個是沒有在對數據留存在外面的要求,沒有對數據進行分開的要求,全部版本的數據在 proxy 保存 。
我更推薦前者。後者把數據都存在 proxy 裏面,前者是把數據也分開了,更模塊化。我我的以爲是比較清晰的用法,這裏用的也是這個。
DATA: data
關於數據,這方面的升級其實比較麻煩,會有一些問題。因此咱們設計數據結構時儘可能穩定一點,變更小一些,提早預留好之後要用的字段,避免之後的升級。要升級的話也有兩種方法
-
一種方法是相似於插件的東西,舊的好比是 map 結構,是地址結構體,後面要多加一個字段。那麼涉及舊數據怎麼辦?我能夠定義一個插件類的合約,定義一個多餘字段加一個指針指回原來的地方,至關於數據分開存。,可是保存一個指針指向舊數據而且可以找到他,可以作一些操做,這樣的好處是不會變更數據,可是會增長操做的邏輯,比較複雜,並且不是全部的數據結構都能作的。
-
第二個是遷移,若是頗有錢的話,能夠直接拷貝過來,若是不在意錢這是最簡單的方式。
整個結構大概是這樣。
對於升級的一點建議:升級時 copy 數據很貴,因此咱們儘可能避免這樣的消耗,前期 gas 消耗也是注意的一個點。第二個是使用庫來封裝這些邏輯,就是說模塊化。儘量邏輯都能成庫,能夠找比較好的庫來用。就是說不少模塊交互須要用接口,讓合約不依賴模塊自己實現而依賴接口,這樣保持接口不變的前提下就能升級合約。
關於講師
馮開開
祕猿科技高級區塊鏈工程師
Github:https://github.com/kaikai1024
祕猿科技 repo:https://github.com/cryptape
鏈接開發者與運營方的合做平臺 CITAHub:https://www.citahub.com/
有任何技術問題能夠在論壇討論:https://talk.nervos.org