[BLOCKCHAIN]

Solidity 패턴 분석 - State Machine

바보코딩러 2022. 6. 9. 12:58

State Machine

state machine 이란?

컨트랙트가 서로 다른 여러 단계를 거치도록 하는 것

 

Motivation

초기 상태에서 여러 중간 상태를 거쳐 최종 상태로 전환해야 하는 계약을 고려해봅시다. 각 상태에서 컨트랙트는 다른 방식으로 작동하고 사용자에게 다른 기능을 제공해야 합니다. 설명된 동작은 경매, 도박, 크라우드 펀딩 등 다양한 사용 사례에서 관찰할 수 있습니다. Solidity 문서 조차도 일반적인 패턴 중 하나로 나열하여 그 관습을 인정합니다. 하나의 상태가 다른 상태로 전환될 수 있는 다양한 방법이 있습니다. 상태가 함수의 끝으로 끝나는 경우도 있고 지정된 시간 후에 상태가 전환되어야 하는 경우도 있습니다. Implementation 섹션에서 다양한 가능성에 대해 자세히 설명합니다.

 

위에서 설명한 기능을 가진 패턴은 이미 Gamma et al.(1995) 에 의해 공식화되었습니다. 그러나 블록체인에서의 구현은 특히 흥미롭습니다. 블록체인 자체가 트랜잭션과 결합된 초기 상태가 새로운 상태를 출력으로 갖는 상태 전이 시스템이기 때문이다. 블록체인의 상태(즉, 트랜잭션 전후의 조건)와 컨트랙트가 진행 중인 상태 간의 혼동을 피하기 위해 명시적으로 정의된 계약 단계의 상태부터 호출합니다.

 

Applicability

다음과 같은 경우 state machine을 사용합니다.

  • 스마트 컨트랙트가 수명 주기 동안 여러 단계를 전환해야 할 때
  • 스마트 컨트랙트의 기능은 특정 단계에서만 액세스할 수 있어야 할 때
  • 단계 전환은 명확하게 정의되어야 하며 모든 참가자에 대해 예방할 순 없습니다.

 

Participants & Collaborations

State Machine 패턴에는 두 명의 참가자가 있습니다. 첫 번째 참여자는 미리 정의된 단계를 거치며 각 단계에서 의도한 기능만 호출될 수 있도록 보장하는 구현하는 컨트랙트입니다. 다른 참가자는 컨트랙트 소유자 또는 상호 작용하는 사용자로, 시간 경과에 따라 직접 또는 간접적으로 단계 전환을 시작할 수 있습니다.

 

 

Implementation

State Machine 패턴의 구현은 세 가지 주요 구성 요소를 다룹니다 . 단계 표현 , 기능에 대한 상호 작용 제어  단계 전환 .

 

Solidity의 여러 단계를 모델링하기 위해 열거형(enums)을 사용할 수 있습니다.
열거형(enums)은 사용자 정의 데이터 유형입니다. 가능한 모든 단계를 포함하는 하나의 열거형이 선언된 후 해당 열거형의 인스턴스를 사용하여 현재 단계를 저장하고 새 단계를 할당하여 다음 단계로 전환할 수 있습니다. 열거형은 모든 정수 유형과 명시적으로 변환할 수 있으므로 단계 인스턴스에 정수 1을 추가하여 다음 단계로의 전환을 수행할 수 있습니다.

 

Access Restriction 패턴 을 사용하여 특정 단계에 대한 기능 액세스를 제한할 수 있습니다. 함수 제어자는 호출된 함수를 실행하기 전에 컨트랙트 단계가 필요한 단계와 같은지 확인합니다. 함수가 부적절한 단계에서 호출되는 경우 Guard Check 패턴 을 사용하여 트랜잭션을 되돌 립니다.

 

한 단계에서 다음 단계로 전환하여 다양한 상황과 요구에 대응할 수 있는 몇 가지 방법이 있습니다. 한 가지 방법은 함수 호출하여 전환하는 것입니다. 함수가 단계 전환 전용이거나 비즈니스 로직을 실행하며 이루어지는 단계 전환은 프로세스의 자연스러운 부분입니다. 예를 들어, 룰렛 계약에서 하우스는 모든 당첨금을 지불하는 기능을 호출할 수 있으며, 이는 GameEnded(게임종료)에서 WinnersPaid(승자에게 모든상금지불)로 단계 전환하여 종료됩니다. 이와 같은 경우 상태 변수에 새 단계를 할당하거나 함수 끝에서 전환을 시작하는 제어자를 사용하거나 헬퍼 함수를 사용하여 단계 변경을 직접 구현합니다. 헬퍼 함수는 호출될 때마다 단계를 1씩 증가시키는 내부 함수입니다. 직접 함수 호출에 의존하지 않는 또 다른 옵션은 자동 시간 전환입니다. 단계가 지속되어야 하는 기간 또는 단계 전환이 실행되어야 하는 미래의 시점이 컨트랙트에 저장됩니다. 관련된 모든 함수 호출과 함께 호출되는 제어자는 현재 타임스탬프를 확인하고 해당 시점에 이미 도달한 경우 다음 단계로 전환합니다. Solidity에서 제어자의 순서를 언급하는 것이 중요합니다. 이러한 지식을 염두에 두고, 현재 단계를 확인하는 제어자 전에 시간 경과에 대한 제어자를 언급해야 하며, 단계 점검에서 시간 경과 단계 변경 가능성이 이미 고려되었는지 확인해야 합니다.

 

구현이 완료된 후에는 의도하지 않은 동작을 이용하거나 계약을 위반할 수 있는 악의적인 엔티티에 의한 예기치 않은 단계 변경 가능성을 배제하기 위해 엄격한 테스트가 필요합니다.

 

 

Sample Code

이 샘플 코드는 블라인드 경매를 위한 State Machine을 보여주며 Solidity 설명서에 제공된 예제 코드에서 영감을 얻었습니다. 이 기능은 단계 전환뿐만 아니라 시간 전환 기능도 갖추고 있습니다. 이는 광범위한 사용 사례이므로 State Machine과 관련된 코드만 제시됩니다. 입찰 저장을 포함한 경매와 관련된 모든 논리는 생략됩니다.

// 이 코드는 전문적인 감사를 받지 않았기 때문에 안전이나 정확성에 대해
// 어떠한 약속도 할 수 없습니다. 참고만하세요
contract StateMachine {
    
    enum Stages { //경매가 진행되는 4단계를 정의
        AcceptingBlindBids, //블라인드입찰시작
        RevealBids, //입찰공개
        WinnerDetermined, //낙찰자결정
        Finished //경매종료
    }

    Stages public stage = Stages.AcceptingBlindBids; //stage 초기단계를 상태변수로 설정

    uint public creationTime = now; //생성시간(시간경과에 따라 단계 전환하기 위함)

    modifier atStage(Stages _stage) { //함수제어자를 통해 현재 단계 확인
        require(stage == _stage);
        _;
    }
    
    modifier transitionAfter() {
        _;
        nextStage(); //내부메소드 nextStage() 설정
    }
    
    modifier timedTransitions() { //현재단계와 전환이 발생해야할 시간을 비교하여 확인
        if (stage == Stages.AcceptingBlindBids && now >= creationTime + 6 days) {
            nextStage(); //조건이 맞다면 다음단계로 전환
        }
        if (stage == Stages.RevealBids && now >= creationTime + 10 days) {
            nextStage();
        }
        _;
    }
	//4개의 퍼블릭 메소드는 각각의 단계에서만 호출 가능
    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 { //transitionAfter modifier를 사용하여 낙찰자가 공개되면 자동으로 다음단계로 넘어가 cleanup() 메소드만 호출 할 수 있습니다.
        // Implement handling of goods here
    }

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

Stages 3행에서는 경매가 진행되는 4단계를 포함하는 enum 이 정의됩니다. 상태 변수는 10행의 초기 단계에서 초기화됩니다. 생성 시간은 12행에 저장되며 시간 경과 전환에 중요합니다. 14행에 정의된 함수 제어자는 현재 단계 : 함수의 허용 단계를 확인합니다. 파라미터로 제공되는 스테이지는 컨트랙트가 있어야 기능 로직을 실행할 수 있는 스테이지입니다. 함수가 transitionAfter()라는 제어자를 구현하는 경우 nextStage()라는 내부 메서드가 함수의 끝에서 호출되고 다음 단계으로 스테이지가 전환됩니다. 시간 지정된 전환은 24행에 지정된 제어자를 사용하여 처리합니다. 개별 if 절은 현재 시간을 전환이 발생해야 하는 시간(예: creationTime + 6 days)과 비교하여 계약이 이미 다음 단계에 있어야 하는지 확인하는 동시에 현재 단계를 고려합니다.

 

34행에서 시작하는 4개의 공개 메소드는 각각의 단계에서 호출할 수 있으며, 이는 atStage() 구체적인 단계와 함께 제공된 제어자에 의해 달성됩니다. 처음 두 단계의 전환은 시기적절합니다. timedTransitions 제어자는 처음 두 함수뿐만 아니라 세 가지 기능을 가지고 있습니다. 다음 단계의 함수를 호출할 때 실제 전환이 일어나기 때문입니다. 예를 들어, 컨트랙트 생성 후 8일 후에 bid() 함수를 호출하면 timedTransitions 제어자가 트리거되기 때문에 먼저 다음 단계로 전환 됩니다. 그러나 이후 atStage() 제어자는 스테이지가 더 이상 일치하지 않음을 감지하고 스테이지 전환을 포함하여 전체 트랜잭션을 되돌립니다. 이 경우 timedTransitions 수정자는 다음을 확인합니다.bid()6일이 지나면 호출할 수 없습니다. 지속 단계 전환은 다음 단계의 함수가 처음 호출될 때 발생합니다. reveal() 함수를 호출한 경우, 프로세스는 이전과 동일하지만 이번에는 단계가 올바른 것으로 인식되고 트랜잭션이 되돌리지 않습니다. 이 복잡한 동작은 Implementation 섹션에서 언급한 수정자의 순서가 중요한 이유입니다.

 

3단계에서 4단계로의 전환은 transitionAfter 42행의 함수에서 사용된 수정자를 사용하여 수행됩니다. 함수 실행 후 컨트랙트는 자동으로 다음 단계(마지막 단계)로 이동하여 cleanup()메서드만 호출할 수 있습니다.

 

Consequences

State Machine 패턴을 적용한 결과 중 하나는 컨트랙트 동작을 개별 단계로 분할하는 것입니다. 함수를 의도한 시간에만 호출할 수 있습니다. 또한 패턴은 컨텍스트에 따라 사용할 수 있는 단계 전환을 시작하기 위한 여러 옵션을 제공하여 다양한 단계를 통해 컨트랙트를 안내합니다.

 

명심해야 할 몇 가지 결과는 단계 전환을 위한 다양한 옵션에서 나옵니다. 시간 초과 전환은 모든 참가자에게 명확한 정책의 이점이 있지만 블록 번호 또는 타임스탬프를 사용하는 것이 완전히 무해한 것은 아닙니다. 채굴자는 타임스탬프에 어느 정도 영향을 미칠 수 있는 잠재적 능력이 있습니다. 따라서 매우 시간에 민감한 경우에는 시간 전환을 사용하는 것을 피해야 합니다. 완벽하게 안전하려면 컨트랙트는 실제 시간에서 최대 900초까지 벗어나는 타임스탬프에 대해 생각해야 합니다. 그러나 계약 소유자가 단계를 변경하거나 계약을 포기하고 모든 투자 자금을 동결하기로 결정할 수 있기 때문에 컨트랙트 소유자가 수동으로 단계를 전환하는 것은 조작되기 쉽습니다.

 

Known Uses

어떤 형태로든 이 패턴을 적용하는 여러 컨트랙트를 관찰될 수 있습니다. 암호화폐 가격 책정에 베팅할 수 있는  Ethorse 베팅 계약이 대표적입니다. 이 예제에서 스테이지는 booleans 형식의 구조체에 저장되며 현재 스테이지는 true로 설정됩니다. 전환은 타임스탬프를 통해 적시에 이루어집니다. 대부분 수동 전환에 의존하는 다른 예는 커뮤니티 주도 마켓플레이스 생태계 인 Pocketinns 의 경매 컨트랙트입니다. 이 컨트랙트에서 소유자는 자신의 의지에 따라 단계를 변경할 수 있습니다. 계약서에는 긴급조치로 명시되어 있지만, 심한 조작의 여지가 있는 만큼 그러한 컨트랙트를 통한 거래는 신중을 기해야 합니다.