智能合約設(shè)計模式 - 狀態(tài)機

本文為智能合約設(shè)計模式系列的一部分。

目的

確保合約不同狀態(tài)暴露不同的功能

動機

合約的生命周期從初始狀態(tài)開始蜀变,經(jīng)歷一些中間狀態(tài)宣决,到達(dá)最終狀態(tài)。在不同狀態(tài)下昏苏,合約以不同的方式運行尊沸,并提供不同的功能。拍賣贤惯、賭博洼专、眾籌等許多用例都反應(yīng)了這點。即使Solidity官方文檔也將其列為常見模式之一孵构。狀態(tài)轉(zhuǎn)換有不同方式屁商,有時狀態(tài)在函數(shù)結(jié)尾轉(zhuǎn)換,有時狀態(tài)在指定時間段后轉(zhuǎn)換颈墅。實現(xiàn)部分將做詳細(xì)描述蜡镶。

Gamma等人在1995年定義了類似功能的模式,但它的區(qū)塊鏈版本有些不同恤筛。因為區(qū)塊鏈本身就是一個狀態(tài)轉(zhuǎn)換系統(tǒng)官还,每個輸入交易都會產(chǎn)生一個新狀態(tài)。為了避免與區(qū)塊鏈的狀態(tài)混淆毒坛,我們定義合同的狀態(tài)為階段望伦。

適用性

在以下條件時使用狀態(tài)機模式

  • 合約的生命周期需要經(jīng)歷不同的階段
  • 合約不同階段具有不同的方法
  • 階段轉(zhuǎn)換應(yīng)當(dāng)明確定義并對所有人公開

參與者和協(xié)作

狀態(tài)機模式有兩個參與者。一個是合約本身煎殷,其能夠在不同階段轉(zhuǎn)換并確保每階段中只提供對應(yīng)的方法屯伞。另一個是合約所有者或使用者,他們可以初始化合約階段并直接或間接的切換階段豪直。

實現(xiàn)

狀態(tài)機模式的實現(xiàn)包括三個主要部分:階段的表示劣摇、方法的訪問控制以及階段轉(zhuǎn)換。

在Solidity中弓乙,可以使用枚舉類型定義階段末融。枚舉是用戶定義類型。先定義一個包含所有可能階段的枚舉唆貌,然后聲明該枚舉的變量存儲當(dāng)前階段滑潘,并通過賦予其不同的階段枚舉值表示階段轉(zhuǎn)換。由于枚舉可以顯式地轉(zhuǎn)化為整型锨咙,因此可以通過將階段變量加1轉(zhuǎn)換到下一階段。

不同階段的方法訪問限制可以采用訪問限制模式追逮。在執(zhí)行方法前酪刀,modifier會檢查當(dāng)前階段是否為正確階段粹舵,如果不為有效階段,則使用守衛(wèi)檢查模式恢復(fù)事務(wù)骂倘。

有幾種方式可以轉(zhuǎn)換階段眼滤。一種是在方法中轉(zhuǎn)換,無論是專門的轉(zhuǎn)換方法历涝,還是處理業(yè)務(wù)邏輯的方法诅需,轉(zhuǎn)換本身就是流程的一部分。例如荧库,輪盤賭合約堰塌,調(diào)用一個方法支付贏家收益,最后將階段從GameEnded轉(zhuǎn)換為WinnersPaid分衫。此情況時场刑,階段轉(zhuǎn)換可以是給階段變量直接復(fù)制,或是利用 modifer 執(zhí)行轉(zhuǎn)化蚪战,亦或是調(diào)用helper方法牵现。helper方法是個內(nèi)部方法,每次調(diào)用階段值就加1邀桑。另一個方式自動定時轉(zhuǎn)換瞎疼,合約保存一個階段持續(xù)的時間或需要轉(zhuǎn)換的某個未來時間點,相關(guān)方法的調(diào)用都會觸發(fā)一個 modifer 檢查當(dāng)前時間點如果條件滿足就轉(zhuǎn)換階段值壁畸。需要強調(diào)的是Solidity modifer 的順序丑慎,階段轉(zhuǎn)換的 modifer 一定要在階段檢查的 modifer 之前,確保檢查時階段已經(jīng)轉(zhuǎn)換瓤摧。

開發(fā)完成后竿裂,要進(jìn)行足夠的測試確保惡意調(diào)用不會觸發(fā)意外的階段轉(zhuǎn)換,從而導(dǎo)致其獲利或破壞合約照弥。

代碼示例

這個示例展示了盲拍合約的狀態(tài)機腻异,來源于Solidity官方文檔示例代碼。它包含了方法調(diào)用中轉(zhuǎn)換和定時轉(zhuǎn)換这揣。由于完整合約比較復(fù)雜悔常,此處只展示狀態(tài)機相關(guān)代碼,省略其它部分给赞。

// This code has not been professionally audited, therefore I cannot make any promises about
// safety or correctness. Use at own risk.
contract StateMachine {
    
    enum Stages {
        AcceptingBlindBids,
        RevealBids,
        WinnerDetermined,
        Finished
    }

    Stages public stage = Stages.AcceptingBlindBids;

    uint public creationTime = now;

    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }
    
    modifier transitionAfter() {
        _;
        nextStage();
    }
    
    modifier timedTransitions() {
        if (stage == Stages.AcceptingBlindBids && now >= creationTime + 6 days) {
            nextStage();
        }
        if (stage == Stages.RevealBids && now >= creationTime + 10 days) {
            nextStage();
        }
        _;
    }

    function bid() public payable timedTransitions atStage(Stages.AcceptingBlindBids) {
        // Implement biding here
    }

    function reveal() public timedTransitions atStage(Stages.RevealBids) {
        // Implement reveal of bids here
    }

    function claimGoods() public timedTransitions atStage(Stages.WinnerDetermined) transitionAfter {
        // Implement handling of goods here
    }

    function cleanup() public atStage(Stages.Finished) {
        // Implement cleanup of auction here
    }
    
    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
    }
}

第5行定義了拍賣經(jīng)歷的四個階段值机打。第12行定義階段變量并賦予初始值。第14行定義了合約創(chuàng)建時間片迅,定時轉(zhuǎn)換會用到它残邀。第16行定義了階段檢查 modifier ,合約必須處于入?yún)⒌碾A段,方法才可以執(zhí)行芥挣。包含了transitionAfter modifier 的方法驱闷,其結(jié)尾會調(diào)用內(nèi)部方法nextStage,轉(zhuǎn)換到下一階段空免。第26行定義定時轉(zhuǎn)換 modifier 空另,比較當(dāng)前時間點和預(yù)期的時間點以及當(dāng)前階段值,來決定是否轉(zhuǎn)換階段蹋砚。

從第36行開始的4個外部方法只能在相應(yīng)的階段調(diào)用扼菠,這是由atStage modifier 及其入?yún)崿F(xiàn)。前兩個階段是定時轉(zhuǎn)換坝咐。注意循榆,前3個方法包含了timedTransitions modifier ,而不是前2個畅厢。這是因為真正的轉(zhuǎn)換發(fā)生在方法調(diào)用時冯痢。例如,合約創(chuàng)建8天后調(diào)用bid方法將轉(zhuǎn)換到下一階段框杜,之后的atStage modifier 將檢測到階段不匹配浦楣,并恢復(fù)整個事務(wù)包括階段轉(zhuǎn)換,這種情況中咪辱,timedTransitions modifier 的作用是保證6天后無法調(diào)用bid方法振劳。階段轉(zhuǎn)換被持久化是發(fā)生在第一次調(diào)用下一階段方法是,本例是調(diào)用reveal方法油狂,這時階段檢查通過历恐,事務(wù)被執(zhí)行。這種復(fù)雜的行為就是在實現(xiàn)部分提到的注意 modifier 順序的原因专筷。

第三階段到第四階段的轉(zhuǎn)換由第44行包含的transitionAfter modifier 完成弱贼。方法執(zhí)行后,合約進(jìn)入最后一個階段磷蛹,只允許調(diào)用cleanup方法吮旅。

后果

應(yīng)用此模式的一個后果是合約方法被劃分到不同階段,方法只能在指定階段調(diào)用味咳。此外庇勃,它還提供幾種階段轉(zhuǎn)換的方式。

轉(zhuǎn)換方式有一些需要注意的地方槽驶。定時轉(zhuǎn)換給每個參與者帶來明確策略的益處责嚷,但使用塊編號或時間戳并不是完全沒有風(fēng)險。礦工可以在一定程度上控制塊時間戳掂铐。因此罕拂,對于時間非常敏感的情況揍异,應(yīng)避免使用自動轉(zhuǎn)換。如果考慮絕對安全聂受,合約應(yīng)在塊時間戳偏離真是時間900秒的情況下保持健壯蒿秦。此外烤镐,人工階段轉(zhuǎn)換也容易被合約所有人控制蛋济,他可以容易的轉(zhuǎn)換合約狀態(tài),或放棄合約凍結(jié)所有資金炮叶。

已知應(yīng)用

多個合約以某種形式應(yīng)用此模式碗旅。一個例子是Ethorse合約,它可以對加密貨幣價格走勢投注镜悉,階段變量為結(jié)構(gòu)體中的bool類型祟辟,當(dāng)前階段為true,自動轉(zhuǎn)換階段侣肄。另一個手動轉(zhuǎn)換的例子是Pocketinns拍賣合約旧困,它是一個社區(qū)驅(qū)動的市場生態(tài)系統(tǒng)。合約所有者有權(quán)自行改變階段稼锅,盡管這被描述為緊急措施吼具,但也為操控開啟方便之門,此類合約的交易應(yīng)謹(jǐn)慎進(jìn)行矩距。

推而廣之

大部分智能合約天然的具有不同狀態(tài)拗盒,去中心化應(yīng)用處理的業(yè)務(wù)模型很多也有狀態(tài),例如一份訂單锥债、一只寵物陡蝇、一個專利等。這也正是很多智能合約或去中心化應(yīng)用可以采用此模式的原因哮肚。此模式類似傳統(tǒng)設(shè)計模式中的狀態(tài)模式登夫,通過將合約生命周期劃分為幾個狀態(tài)來簡化合約的管理。

和狀態(tài)模式不同的是允趟,智能合約狀態(tài)的主要作用是確保安全而非利于擴展恼策。使用訪問限制模式,實現(xiàn)狀態(tài)檢查方法拼窥,并在合約方法中(一般是開始)調(diào)用戏蔑,同時在合約方法中(一般是最后)調(diào)用切換方法轉(zhuǎn)換狀態(tài)。

上述的自動定時轉(zhuǎn)換在狀態(tài)機中經(jīng)常涉及鲁纠。由于區(qū)塊鏈合約確定性的特點总棵,無法獲取實時時間,而是當(dāng)前區(qū)塊時間(一般只有很少的差別)改含。EOS提供了now()和current_time()獲得當(dāng)前區(qū)塊時間情龄。HyperLedger Fabric則有ChaincodeStubInterface.GetTxTimestamp()方法。聯(lián)盟鏈還可利用預(yù)言機模式定時觸發(fā)智能合約切換狀態(tài)。

完整內(nèi)容請查看智能合約設(shè)計模式系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骤视,一起剝皮案震驚了整個濱河市鞍爱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌专酗,老刑警劉巖睹逃,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祷肯,居然都是意外死亡沉填,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門佑笋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翼闹,“玉大人,你說我怎么就攤上這事蒋纬×攒” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵蜀备,是天一觀的道長关摇。 經(jīng)常有香客問我,道長琼掠,這世上最難降的妖魔是什么拒垃? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮瓷蛙,結(jié)果婚禮上悼瓮,老公的妹妹穿的比我還像新娘。我一直安慰自己艰猬,他們只是感情好横堡,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冠桃,像睡著了一般命贴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上食听,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天胸蛛,我揣著相機與錄音,去河邊找鬼樱报。 笑死葬项,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迹蛤。 我是一名探鬼主播民珍,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼襟士,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嚷量?” 一聲冷哼從身側(cè)響起陋桂,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝶溶,沒想到半個月后嗜历,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡身坐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年秸脱,在試婚紗的時候發(fā)現(xiàn)自己被綠了落包。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片部蛇。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咐蝇,靈堂內(nèi)的尸體忽然破棺而出涯鲁,到底是詐尸還是另有隱情,我是刑警寧澤有序,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布抹腿,位于F島的核電站,受9級特大地震影響旭寿,放射性物質(zhì)發(fā)生泄漏警绩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一盅称、第九天 我趴在偏房一處隱蔽的房頂上張望肩祥。 院中可真熱鬧,春花似錦缩膝、人聲如沸混狠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽将饺。三九已至,卻和暖如春痛黎,著一層夾襖步出監(jiān)牢的瞬間予弧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工湖饱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掖蛤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓琉历,卻偏偏與公主長得像坠七,于是被迫代替她去往敵國和親水醋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容