이벤트로 블라인드 경매 시스템 개선하기

KimCookieYa·2022년 8월 10일
0

블록체인 인 액션

목록 보기
4/5
post-thumbnail

* Blockchain in Action(블록체인 인 액션) 책의 실습을 바탕으로 작성하였음.

온체인과 오프체인

Dapp에서 사용하는 데이터의 일부분은 블록체인 인프라(온체인)에 저장하고, 다른 것은 전통적인 데이터베이스와 파일(오프체인)에 저장한다. 일반적으로 블록체인상에 저장하는 모든 데이터는 온체인데이터이고, 그 이외의 것은 오프체인데이터다.

전통적인 시스템에서는 애플리케이션 안에서 함수를 실행시켜 얻은 겨로가를 로컬 파일 시스템 또는 중앙화 데이터베이스에 저장한다. 블록체인 애플리케이션은 다음과 같은 데이터를 블록체인 노드상에(온체인에) 저장한다.

  • 실행하고 컨펌받은 트랜잭션
  • 스마트 컨트랙트 함수의 실행 결과
  • 상태 변화(storage 변숫값에 일어난 변화)
  • 발생시킨 이벤트 로그

블록체인 노드의 데이터 구조에 이러한 데이터를 저장하고, 블록체인 프로토콜에서 설정한대로 다른 참여 노드로 전파한다.

비즈니스 시스템에서 Dapp을 개발할 때 전통적인 시스템을 전부 솔리디티 언어로 포팅하는 것이 아니다. 더 큰 시스템의 일부로서 오직 블록체인의 지원이 필요한 부분만 코딩하는 것이다. 주어진 탈중앙화 시나리오에서 Dapp을 설계할 때 가장 중요한 업무는 다음의 것을 식별하는 것이다.

  • 통합 시스템에서 전통적인 파트가 담당해야 할 행동
  • 블록체인 애플리케이션이 담당해야 할 행동

블록체인 애플리케이션 설계와 개발자는 "온체인에 저장할 데이터"와 "오프체인에 저장할 데이터"를 결정해야 한다.

온체인 데이터

이더리움 프로토콜은 각각 특정한 목적을 가진 다음과 같은 여러 요소들로 구성되어 있다.

  • 블록체인 헤더 : 블록의 속성(Attributes)을 저장한다.
  • 트랜잭션(Txs) : 블록에 저장된 Tx의 내용을 저장한다.
  • 리시트 : 블록에 기록된 Tx의 실행 결과를 저장한다. 모든 트랜잭션은 리시트를 가지고 있고, 일대일 관계를 가진다.
  • 총합 글로벌 스테이트 : 모든 블록체인상의 스마트 컨트랙트와 다른 일반 어카운트의 데이터값 또는 현재 스테이트를 저장하고, 이것을 이용하는 Tx를 컨펌할 때 갱신한다.

블록 헤더, Txs, 리시트, 스테이트 스토리지는 온체인 데이터의 대부분을 구성한다. 이 데이터는 블록체인의 견고성과 보안성을 확보하는 데 중요한 역할을 하며, 트랜잭션과 이벤트를 위한 존재 증명(Proof of Existence)을 제공한다. 애플리케이션 레벨에서의 오퍼레이션을 지원하기 위해 블록체인에 저장된 이러한 정보를 사용할 수 있다.

여러 온체인 데이터는 블록체인 프로토콜이 업격하게 통제하며, 실제 데이터값은 애플리케이션이 호출한 Txs가 결정한다. 블록체인 기반 애플리케이션을 설계할 때 이러한 제약을 잘 인지하고 있어야 한다. 블라인드 경매 유스 케이스에서는 그중 리시트 트리에 있는 이벤트 로그를 사용해 시스템을 개선해보려 한다. 다음 내용을 검토해보자.

온체인 이벤트 데이터

이벤트(Events)는 스마트 컨트랙트 함수의 실행에서 어떤 조건이나 플래그가 발생했다는 것을 알리기 위해 함수에서 발생시키는 알림이다. 이벤트는 온체인의 리시트 트리에 로그인되는데, 그것의 이름으로 액세스할 수 있다.

스마트 컨트랙트에 미리 이벤트를 정의하고, emit을 사용해 호출함으로써 이벤트를 발생시킬 수 있다.

event BiddingStarted(); // 이벤트 정의
event RevealStarted();

emit BiddingStarted(); // 이벤트 호출
emit RevealStarted();

솔리디티는 이벤트를 정의하고 발생시키는 기능을 제공하는데, 이벤트를 파라미터를 가질 수도 아닐 수도 있다.


설계

블라인드 경매는 네 개의 단계, Init, Bidding, Reveal, Done(경매 종료)를 가진다. 이때, Bidding과 Reveal 단계에 접어들 때 사용자에게 이를 알려서 기회를 놓치지 않도록 해준다면 더 좋을 것이다. 이제 세 개의 이벤트, 즉 AuctionEnded, BiddingStarted, RevealStarted를 추가한다.

이벤트는 블록의 리시트 트리에 기록하는 전형적인 온체인 데이터이기 때문이다. 이벤트 로그는 블라인드 경매 유스 케이스처럼 거의 리얼타임 수준의 응답에도 사용할 수 있지만, 오프라인에서 인덱싱해서 조회하거나 온체인 데이터를 분석할 때도 사용한다. 이벤트를 호출하면 로그를 발생시키는데, 이 로그에 온체인 인데스를 걸고 토픽(topic)으로 조회할 수 있다. 이 경우 이벤트 이름 자체와 파라미터들을 토픽으로 사용할 수 있다. 이러한 세분화한 액세스는 블록체인의 과거 데이터에 대한 분석에 유용하다.

발생한 이벤트에 접근하기 위한 방법에는 여러 가지가 있는데, 리스너(listener)(push 방법)를 이용할 수도 있고, 리시트 로그(pull 방법)를 사용할 수도 있다. 또 하나의 온체인 데이터인 트랜잭션 리시트를 사용하는 기법을 소개한다. 새로운 요소를 포함하도록 블라인드 경매 컨트랙트 다이어그램을 수정한다.

수정된 컨트랙트 다이어그램

블라인드 경매 스마트 컨트랙트는 세 개의 이벤트를 가지고 있다.

  • 경매의 종료를 알리는 AuctionEnded
  • 경매의 Bidding 단계를 알리는 BiddingStarted
  • 경매의 Reveal 단계를 알리는 RevealStarted

AdvancedBlindAuction.sol 코딩

pragma solidity >=0.4.22 <=0.6.0;

contract AdvancedBlindAuction {

    struct Bid {
        bytes32 blindedBid;
        uint deposit;
    }

    enum Phase {
        Init, Bidding, Reveal, Done
    }
    Phase public currentPhase = Phase.Init;

    address payable beneficiary;
    mapping(address=>Bid) bids;

    address public highestBidder;
    uint public highestBid = 0;

    mapping(address=>uint) depositReturns;

    // Events
    event AuctionEnded(address winner, uint highestBid);
    event BiddingStarted();
    event RevealStarted();

    modifier validPhase(Phase reqPhase) {
        require(currentPhase == reqPhase);
        _;
    }

    modifier onlyBeneficiary() {
        require(msg.sender == beneficiary);
        _;
    }

    constructor() public {    
        beneficiary = msg.sender;
    }

    function advancePhase() public onlyBeneficiary {
        if (currentPhase == Phase.Done) {
            currentPhase = Phase.Init;
        } else {
            uint nextPhase = uint(currentPhase) + 1;
            currentPhase = Phase(nextPhase);
        }
        
        if (currentPhase == Phase.Reveal) emit RevealStarted();
        if (currentPhase == Phase.Bidding) emit BiddingStarted();
    }
    
    function bid(bytes32 blindBid) public payable validPhase(Phase.Bidding) {  
        bids[msg.sender] = Bid({
            blindedBid: blindBid,
            deposit: msg.value
        });
    }
    
    function reveal(uint value, bytes32 secret) public validPhase(Phase.Reveal) {
        uint refund = 0;
            Bid storage bidToCheck = bids[msg.sender];
            if (bidToCheck.blindedBid == keccak256(abi.encodePacked(value, secret))) {
                refund += bidToCheck.deposit;
                if (bidToCheck.deposit >= value) {
                    if (placeBid(msg.sender, value))
                        refund -= value;
                }
            }
            
        msg.sender.transfer(refund);
    }

    function placeBid(address bidder, uint value) internal returns (bool success) {
        if (value <= highestBid) {
            return false;
        }
        if (highestBidder != address(0)) {
            depositReturns[highestBidder] += highestBid;
        }
        highestBid = value;
        highestBidder = bidder;
        return true;
    }

    function withdraw() public {   
        uint amount = depositReturns[msg.sender];
        require (amount > 0);
        depositReturns[msg.sender] = 0;
        msg.sender.transfer(amount);
    }
    
    function auctionEnd() public validPhase(Phase.Done) {
        beneficiary.transfer(highestBid);
        emit AuctionEnded(highestBidder, highestBid);
    }
}

이벤트 로그 온체인 데이터

이제 스마트 컨트랙트를 리믹스 IDE에 로딩하고 작동 중에 발생하는 이벤트를 체크해 보자.

다음은 수혜자가 advancePhase() 함수를 실행했을 때 생성되는 로그의 내용이다. 이 함수의 실행은 경매 단계를 Init에서 Bidding 단계로 바꾼 후, Bidding 단계를 알리는 이벤트, 즉 BiddingStarted 이벤트를 발생시키고, 그 결과를 Remix IDE의 콘솔에 출력한다. 이 Tx의 컨펌이 끝나면 이벤트는 온체인 데이터로서 블록 헤더에 로그인된다.

  • from : 스마트 컨트랙트를 배포한 주소
  • topic : 호출 함수(advancePhase()) 시그니처(헤더)의 16진수 값
  • event : 발생한 이벤트명(BiddingStarted)
  • args : 발생한 이벤트의 파라미터값을 보여준다. 이 경우에는 파라미터가 없으므로 length가 0이다.

profile
무엇이 나를 살아있게 만드는가

0개의 댓글