智能合约设计模式 - 状态机

本文为智能合约设计模式系列的一部分。

目的

确保合约不同状态暴露不同的功能

动机

合约的生命周期从初始状态开始,经历一些中间状态,到达最终状态。在不同状态下,合约以不同的方式运行,并提供不同的功能。拍卖、赌博、众筹等许多用例都反应了这点。即使Solidity官方文档也将其列为常见模式之一。状态转换有不同方式,有时状态在函数结尾转换,有时状态在指定时间段后转换。实现部分将做详细描述。

Gamma等人在1995年定义了类似功能的模式,但它的区块链版本有些不同。因为区块链本身就是一个状态转换系统,每个输入交易都会产生一个新状态。为了避免与区块链的状态混淆,我们定义合同的状态为阶段。

适用性

在以下条件时使用状态机模式

  • 合约的生命周期需要经历不同的阶段
  • 合约不同阶段具有不同的方法
  • 阶段转换应当明确定义并对所有人公开

参与者和协作

状态机模式有两个参与者。一个是合约本身,其能够在不同阶段转换并确保每阶段中只提供对应的方法。另一个是合约所有者或使用者,他们可以初始化合约阶段并直接或间接的切换阶段。

实现

状态机模式的实现包括三个主要部分:阶段的表示、方法的访问控制以及阶段转换。

在Solidity中,可以使用枚举类型定义阶段。枚举是用户定义类型。先定义一个包含所有可能阶段的枚举,然后声明该枚举的变量存储当前阶段,并通过赋予其不同的阶段枚举值表示阶段转换。由于枚举可以显式地转化为整型,因此可以通过将阶段变量加1转换到下一阶段。

不同阶段的方法访问限制可以采用访问限制模式。在执行方法前,modifier会检查当前阶段是否为正确阶段,如果不为有效阶段,则使用守卫检查模式恢复事务。

有几种方式可以转换阶段。一种是在方法中转换,无论是专门的转换方法,还是处理业务逻辑的方法,转换本身就是流程的一部分。例如,轮盘赌合约,调用一个方法支付赢家收益,最后将阶段从GameEnded转换为WinnersPaid。此情况时,阶段转换可以是给阶段变量直接复制,或是利用 modifer 执行转化,亦或是调用helper方法。helper方法是个内部方法,每次调用阶段值就加1。另一个方式自动定时转换,合约保存一个阶段持续的时间或需要转换的某个未来时间点,相关方法的调用都会触发一个 modifer 检查当前时间点如果条件满足就转换阶段值。需要强调的是Solidity modifer 的顺序,阶段转换的 modifer 一定要在阶段检查的 modifer 之前,确保检查时阶段已经转换。

开发完成后,要进行足够的测试确保恶意调用不会触发意外的阶段转换,从而导致其获利或破坏合约。

代码示例

这个示例展示了盲拍合约的状态机,来源于Solidity官方文档示例代码。它包含了方法调用中转换和定时转换。由于完整合约比较复杂,此处只展示状态机相关代码,省略其它部分。

// 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行定义了拍卖经历的四个阶段值。第12行定义阶段变量并赋予初始值。第14行定义了合约创建时间,定时转换会用到它。第16行定义了阶段检查 modifier ,合约必须处于入参的阶段,方法才可以执行。包含了transitionAfter modifier 的方法,其结尾会调用内部方法nextStage,转换到下一阶段。第26行定义定时转换 modifier ,比较当前时间点和预期的时间点以及当前阶段值,来决定是否转换阶段。

从第36行开始的4个外部方法只能在相应的阶段调用,这是由atStage modifier 及其入参实现。前两个阶段是定时转换。注意,前3个方法包含了timedTransitions modifier ,而不是前2个。这是因为真正的转换发生在方法调用时。例如,合约创建8天后调用bid方法将转换到下一阶段,之后的atStage modifier 将检测到阶段不匹配,并恢复整个事务包括阶段转换,这种情况中,timedTransitions modifier 的作用是保证6天后无法调用bid方法。阶段转换被持久化是发生在第一次调用下一阶段方法是,本例是调用reveal方法,这时阶段检查通过,事务被执行。这种复杂的行为就是在实现部分提到的注意 modifier 顺序的原因。

第三阶段到第四阶段的转换由第44行包含的transitionAfter modifier 完成。方法执行后,合约进入最后一个阶段,只允许调用cleanup方法。

后果

应用此模式的一个后果是合约方法被划分到不同阶段,方法只能在指定阶段调用。此外,它还提供几种阶段转换的方式。

转换方式有一些需要注意的地方。定时转换给每个参与者带来明确策略的益处,但使用块编号或时间戳并不是完全没有风险。矿工可以在一定程度上控制块时间戳。因此,对于时间非常敏感的情况,应避免使用自动转换。如果考虑绝对安全,合约应在块时间戳偏离真是时间900秒的情况下保持健壮。此外,人工阶段转换也容易被合约所有人控制,他可以容易的转换合约状态,或放弃合约冻结所有资金。

已知应用

多个合约以某种形式应用此模式。一个例子是Ethorse合约,它可以对加密货币价格走势投注,阶段变量为结构体中的bool类型,当前阶段为true,自动转换阶段。另一个手动转换的例子是Pocketinns拍卖合约,它是一个社区驱动的市场生态系统。合约所有者有权自行改变阶段,尽管这被描述为紧急措施,但也为操控开启方便之门,此类合约的交易应谨慎进行。

推而广之

大部分智能合约天然的具有不同状态,去中心化应用处理的业务模型很多也有状态,例如一份订单、一只宠物、一个专利等。这也正是很多智能合约或去中心化应用可以采用此模式的原因。此模式类似传统设计模式中的状态模式,通过将合约生命周期划分为几个状态来简化合约的管理。

和状态模式不同的是,智能合约状态的主要作用是确保安全而非利于扩展。使用访问限制模式,实现状态检查方法,并在合约方法中(一般是开始)调用,同时在合约方法中(一般是最后)调用切换方法转换状态。

上述的自动定时转换在状态机中经常涉及。由于区块链合约确定性的特点,无法获取实时时间,而是当前区块时间(一般只有很少的差别)。EOS提供了now()和current_time()获得当前区块时间。HyperLedger Fabric则有ChaincodeStubInterface.GetTxTimestamp()方法。联盟链还可利用预言机模式定时触发智能合约切换状态。

完整内容请查看智能合约设计模式系列

你可能感兴趣的:(智能合约设计模式 - 状态机)