본문 바로가기

Blockchain/Blockchain In Action

[Blockchain In Action] 2장 스마트 컨트랙트란?

Blockchain In Action 2장.


블록체인 인 액션 책을 읽고 공부한 내용을 기록한 글 입니다.
글에 나와있는 내용과 사진은 모두 블록체인 인 액션에 포함된 내용 혹은 이를 정리한 것 입니다.
문제가 될 시 삭제하겠습니다.

 

스마트 컨트랙트

스마트 컨트랙트는 애플리케이션(Dapp?)의 규칙과 규정들을 디지털로 정의하고, 검증하고, 검사하며 강제하기 위한 블록체인에서 작동시킬 수 있는 실행 가능한 코드다. 스마트 컨트랙트는 제 3자 없이도 신뢰할 수 있는 트랜잭션의 수행을 지원한다. 이러한 트랜잭션은 추적하고 되돌릴 수 없다. 

스마트 컨트랙트와 블록체인 프로그래밍의 설계와 구현을 위해서는 세 가지가 필요하다.

1. 블록체인 플랫폼
2. 스마트 컨트랙트를 코딩하기 위한 언어
3. 개발, 컴파일, 배포, 테스트하기 적합한 환경

블록체인 인 액션에서는 이더리움, 솔리디티, 리믹스를 이용해서 개발한다. 


스마트 컨트랙트의 개념

스마트 컨트랙트는 비트코인 블록체인 프로토콜이 제공했던 기본적인 신뢰를 확장시키는 코드다. 

스마트 컨트랙트는 암호 화폐 이외의 디지털 자산을 거래하기 위한 트랜잭션을 지원할 수 있는 프로그래밍을 가능하게 해준다. 

또한, 스마트 컨트랙트는 각 블록체인 애플리케이션이 필요로 하는 특정한 확인과 검증을 가능하게 해준다. 

범용적인 애플리케이션을 위한 신뢰 레이어를 구축해준다고 볼 수 있다. 


비트코인 트랜잭션  VS  스마트 컨트랙트 트랜잭션

비트코인의 경우, 모든 트랜잭션은 가치(Tx)를 전송하기 위한 것이다. 스마트 컨트랙트를 지원하는 블록체인의 경우, 트랜잭션은 스마트 컨트랙트가 구현한 기능을 포함한다. 

Blockchain In Action 그림 2.2

책에서 나온 투표 스마트 컨트랙트의 예시이다. Validate, Vote, Count, Declare 등 함수를 호출하면 블록체인에 기록될 트랜잭션을 생성한다. 블록체인에 이러한 임의적인 코드를 올리고 실행시킬 수 있는 기능은 단순한 암호 화폐 전송을 넘어서서 블록체인의 활용도를 크게 향상시킨다. 


스마트 컨트랙트의 역할

스마트 컨트랙트는 블록체인 애플리케이션의 두뇌 역할을 한다. 자세한 내용은 다음과 같다.

  • 각 애플리케이션의 특수한 조건에 맞는 확인과 검증을 할 수 있는 비즈니스 로직 레이어를 표현한다.
  • 블록체인에서 작동할 수 있는 규칙의 명세를 설정할 수 있게 한다.
  • 탈중앙화 네트워크에서 디지털 자산의 전송을 구현하기 쉽게 도와준다.
  • 메세지나 다른 함수의 호출에 의해 실행되는 함수를 내장하는데, 개인 어카운트나 다른 스마트 컨트랙트가 이런 호출을 하게 된다. 이 메세지는 블록체인 분산 장부에 트랜잭션 일부로 기록되는데, 여기에는 입력 parameter, 송신자의 주소, 타임 스탬프 등과 같은 추가적인 메타 데이터를 포함한다.
  • 탈중앙화 블록체인 기반 애플리케이션을 위한 소프트웨어 기반 중개자로서 기능한다.

스마트 컨트랙트는 여지 없이 탈중앙화 블록체인 애플리케이션의 중심 컴포넌트라고 할 수 있다. 


스마트 컨트랙트의 설계

책에 나온 예시를 통해 스마트 컨트랙트의 설계를 배워보자. 책에서 나온 첫 예시는 탈중앙화 카운터다.

카운터는 단순하지만, 스마트 컨트랙트 개발의 다양한 유스 케이스를 보여줄 수 있는 좋은 예시라고 한다. 

설계과정의 목표는 스마트 컨트랙트의 내용을 정의하는 것인데, 구체적으로는 다음과 같다.

  1. 데이터
  2. 데이터를 처리할 함수. 
  3. 처리를 위한 규칙.

책에서는 클래스 다이어그램을 보여주기 위해 UML(통합 모델링 언어)를 사용한다. UML 유스케이스 다이어그램은 해결해야 할 문제와 스마트 컨트랙트를, 정확히는 그것의 함수들을 어떻게 사용해야 할 지에 대해서 숙고할 수 있도록 해준다. 

Blockchain In Action 그림 2.3

카운터의 함수들은 increment, decrement, initialize, get이 있다. 


데이터 Asset, Peer 참여자, 역할, 규칙, 그리고 트랜잭션

마찬가지로 문제 해결을 위한 블록체인 기반 컴포넌트들의 여러 가지 속성에 대해서 정의한다면 다음과 같다.

  • 추적해야 할 데이터 Asset : 카운터 값
  • Peer 참여자 : 카운터값을 업데이트할 애플리케이션
  • 참여자의 역할 : 카운터값을 업데이트하고 그 값에 액세스 하는 것
  • 데이터와 함수에 검증, 검사해야 할 규칙 : *이 케이스에서는 없다.
  • 디지털 장부에 기록해야하는 트랜잭션 : initialize(), increment(). decrement()

카운터 값을 바꾸는 함수나 트랜잭션만을 기록하면 된다는 점에 주목해야 한다. 스마트 컨트랙트에 포함된 모든 함수가 블록체인의 분산장부에 기록되는 트랜잭션을 생성하는 것은 아니다. get()함수는 카운터의 내용을 조회하기만 할 뿐이므로 이 함수의 호출을 블록체인에 기록할 필요는 없다. 이런 특성을 read-only 함수로 정의할 수 있다. 


컨트랙트 다이어그램

위에서 클래스 다이어그램을 간단하게 표현한 것을 볼 수 있는데, 이제 그것을 컨트랙트 다이어그램으로 변경하는 과정이 필요하다. 

전형적인 클래스 다이어그램은 "클래스의 이름, 데이터의 정의, 함수 정의" 세 가지를 포함한다.

그림 2.4의 오른쪽에 보이는 컨트랙트 다이어그램은 추가적인 컴포넌트를 포함한다. 

Blockchain In Action 그림 2.4

바로 함수와 데이터에 대한 접근 규칙이다. 간단한 유스 케이스에서는 조건이나 규칙을 사용하지 않는다.

그림 2.5는 카운터를 위한 컨트랙트 다이어그램이다.

Blockchain In Action 그림 2.5

다이어그램을 살펴보면, 클래스 이름은 Counter이다. value라고 명명한 유일한 데이터인 정수값이 있다.

그리고 위에서 정의한 함수들이 들어가 있다. constructor 함수가 추가된 것을 볼 수 있는데,

이는 스마트 컨트랙트가 블록체인 인프라에서 작동하는 VM 샌드박스에 처음 배포되었을 때 한 번만 작동한다.

constructor 에서는 컨트랙트의 초깃값을 설정할 수 있는데, 이때 이 함수에 주어진 parameter 값들을 이용할 수도 있다.  


솔리디티

책에서는 솔리디티를 사용한다. 솔리디티는 스마트 컨트랙트를 코딩하기 위한 고수준 객체지향 언어이다. 또한 정적 타입의 언어이고, 상속, 라이브러리, 사용자 정의 타입 등을 지원한다. 

다음은 책에 나온 예제 코드이다.

pragma solidity ^0.8.1;

contract    Counter {
    uint    value;

    function initialize (uint x) public {
        value = x;
    }

    function get() view public returns (uint) {
        return value;
    }

    function increment (uint n) public {
        value = value + n;
        // return (optional)
    }

    function decrement (uint n ) public {
        value = value - n;
    }
}

첫 번째 라인은 이 코드를 작성하는데 사용한 언어의 버전을 지정한다. 버전 번호를 지정할 때 pragma 지시문을 사용한다. 

pragma 지시문 이후, contract 키워드와 컨트랙트의 이름으로 컨트랙트 코드 정의를 시작한다. 

다음으로 uint value를 볼 수 있는데, 다른 언어에서와 유사하게 변수를 선언하는 것을 볼 수 있다. 

다만 uint 타입은 다른 주류 컴퓨팅에서 사용하는 정수 데이터타입과 매우 다르다. 

범용 언어에서는 64bit 인데 반해 솔리디티에서는 256bit 값이다. (uint == int == int256 == uint256)

모든 함수는 public으로 선언되어 있는데, 이것을 블록체인 상에 있는 어떠한 외부 참여자 혹은 어카운트도 이 함수를 호출할 수 있다는 것을 의미한다.

initialize, increment, decrement 함수를 호출하게 되면 파라미터를 받아서 value를 갱신하고, 자동적으로 각각 분산 장부상의 트랜잭션으로 기록된다. 변수 value의 상태변화 또한 기록된다.


블록체인 컨트랙트는 왜 스마트한가?

스마트 컨트랙트는 다음과 같은 속성을 지니고 있다.

  • 이름
  • 주소
  • 암호화폐(책에서는 ether) 잔액
  • 암호화폐를 송금하고 수신할 수 있는 내장기능
  • 데이터와 함수
  • 메시지를 수신하고 함수를 호출할 수 있는 내장기능
  • 함수 실행을 계산할 수 있는 능력

전통적인 컴퓨팅 시스템에서는 사용자 이름과 암호로 참여자를 식별한다.

그러나 이러한 <id, pw>조합은 탈중앙화 시스템에서는 Peer가 통상적인 신뢰 범위 밖에 있기 때문에 사용하지 않는다.

이에 대한 해결책은 각 참여자에게 암호학적 알고리즘에 기반한 고유한 식별자를 제공하는 것이다. 

블록체인과 상호작용을 하는 모든 참여자는 고유하게 식별 가능한 어카운트 번호, 즉 주소를 가진다. 

이더리움은 두 가지 종류의 어카운트가 있는데, 하나는 외부 소유 어카운트(EOA), 다른 하나는 스마트 컨트랙트 어카운트(SCA)이다.

두 어카운트 모두 160비트 또는 20바이트 크기의 주소로 식별한다. Remix IDE에서 이 숫자를 볼 수 있다. 

EOA와 SCA모두 이더 밸런스를 가질 수 있다. 따라서 모든 어카운트는 address와 balance라는 묵시적 속성을 가진다. 

스마트 컨트랙트상에서 명시적으로 이 속성을 발견할 수 없음에도 address(this).balance와 같이 스마트 컨트랙트가 가진 밸런스를 조회해볼 수 있다. 

EOA와 SCA는 스마트 컨트랙트에 메세지를 보냄으로서 함수를 호출할 수 있다. 이러한 메세지는 두 가지 속성을 지니고 있는데,

바로 msg.sender와 msg.value 이다. 메세지는 가치를 전송할 수 있는데, 호출한 스마트 컨트랙트의 함수를 실행할 때 그 금액은 호출한 스마트 컨트랙트의 밸런스에 더해진다.

이렇게 금액을 전송받기 위해서는 해당 함수에 payable 수정자를 포함해 선언해야 한다. 

pragma solidity ^0.8.1;

contract Account {

    address public whoDeposited;
    uint public depositAmt;
    uint public accountBalance;

    function deposit() public payable {
        whoDeposited = msg.sender;
        depositAmt = msg.value;
        accountBalance = address(this).balance;
    }
}

탈중앙화 항공사 시스템 유스 케이스(ASK)

이번 예제에서는 블록체인 프로그래밍의 또 다른 주의 사항, 블록체인에 저장할 정보량을 최소화해야 한다는 점을 배운다.

ASK는 서로 코드를 공유하지 않는 항공사들이 좌석을 P2P로 거래할 수 있는 시장이라고 보면 된다. 

다음과 같은 전제로 예제를 진행한다.

  • 항공사는 언제든지 원하는 바에 따라 시스템에 합류하거나 탈퇴할 수 있다.
  • 각 항공사는 ASK 트랜잭션에서 좌석의 정산에 사용될, 미리 설정된 최소 에스크로(escrow) 금액을 예치함으로써 ASK에 합류할 수 있다.
  • 컨소시엄은 항공사 간 좌석을 트레이딩 할 수 있게 허용하는데, 이를 관장하는 조건과 상황을 제시한다. 
  • 트레이딩을 위한 규칙은 시스템 안에 코드로 포함하고 있으므로 애매모호한 경우가 없고, 모든 결과가 결정론적이다.

ASK 문제 설정, 이슈, 블록체인 기반 솔루션, 결과물 등을 그림 2.9의 사분면 차트로 요약할 수 있다.

Blockchain In Action 그림 2.9

이 유스 케이스는 기본적인 항공사 간의 P2P 좌석 거래로 허용 범위를 한정한다.

블록체인은 합의한 규칙을 강제하거나 원활한 지급 시스템을 가능하게 함으로써 경쟁 항공사 간의 통상적인 비즈니스 우려도 덜어 준다. 

참여 항공사는 항공편 좌석의 가용성을 조회해 볼 수 있는 안전하고 간단한 표준 API를 제공한다. 

이 조회는 소프트웨어 애플리케이션에 의해 프로그래밍적으로 호출되며, 중개자가 필요 없다. 

애플리케이션의 요청은 직접 항공사로 보내진다.

탈중앙화 시스템에서 확인자, 검증자, 보관자의 역할을 하는 블록체인을 표현하면 그림 2.10과 같다.

Blockchain In Action 그림 2.10

  1. 고객이 항공사 A에 예약해 두었던 좌석을 바꾸어 달라는 요청을 한다.
  2. 항공사 A의 에이전트나 애플리케이션은 ASK 컨소시엄 회원들에게 공유된 스마트 컨트랙트 로직을 통해 이 요청을 확인하고 검증한다.
  3. 확인 후에 이 요청 Tx는 컨펌되고 변조 불가능한 분산 장부에 기록된다. 이제 컨소시엄에 속해 있는 모든 주체는 정당한 요청이 생성되었음을 알게된다.
  4. 가장 단순한 설계에서는 항공사 A의 에이전트가 확인 및 검증된 요청을 항공사 B에게 보낸다. 
  5. 항공사 B의 에이전트 또는 애플리케이션이 자사의 데이터베이스를 검색해 가용성을 확인한다.
  6. 항공사 B의 에이전트는 컨소시엄의 공동 이해관계와 공유된 규칙을 확인하고 검증하는 공유 스마트 컨트랙트 로직을 통해서 응답을 한다. 
  7. 확인 후에 응답 Tx는 컨펌되고 변조 불가능한 분산 장부에 기록된다. 이제 컨소시엄의 모든 참여자가 응답이 보내졌음을 알 수 있다.
  8. 항공사 B는 이 응답을 항공사 A의 에이전트에게 보낸다.
  9. 항공사 A는 자기의 데이터베이스를 업데이트하고 변화된 것을 기록한다.
  10. 항공사 B의 에이전트는 고객에게 좌석과 기타 상세 정보를 보낸다. (직접 고객에게 보내는 것)
  11. 지급은 참여 항공사들이 공유하고 있는 스마트 컨트랙트에 에스크로해 두었거나, 예치했던 금액을 이용한 P2P 트랜잭션을 통해 정산한다. 지급 정산은 시스템 내의 다른 오퍼레이션에서도 포함할 수 있지만, 모두 공유 스마트 컨트랙트로 처리하여 장부에 기록한다. 

항공사 스마트 컨트랙트

우선 컨트랙트 다이어그램 설계부터 해야한다. 지금은 책에서 제시해준 컨트랙트 다이어그램을 살펴보고 이를 따라간다.

Blockchain In Action 그림 2.11

이번 케이스에서 피어 참여자는 고객의 요구에 대응하는 각 항공사의 에이전트들이 될 것이다. 

ASK 컨소시엄 관리 기관은 컨소시엄 의장으로 불리는 감시자를 둔다. 그렇다고 해서 이것이 중앙화를 의미하는 것은 아닌데, 감시자 또는 의장은 참여자들간에 정기적으로 서로 순환하여 임명되기 때문이다. 의장은 어떤 중앙화된 데이터베이스도 관리하지 않는다. 

데이터 asset은 피어 참여자들에 의해 처리되고 있는 비행기 좌석과 자금이다. 

규칙이 몇 가지 있는데,

  1. 항공사를 대신해서 에이전트는 에스크로/예치와 register()함수를 이용해 직접 ASK(항공사) 회원으로 등록할 수 있다.
  2. 에이전트는 비행기 좌석을 요청할 수 있고, 항공사를 대신해서 좌석 가용성을 확인하기 위해 자신들의 중앙화 데이터베이스를 조회하고 응답할 수 있다.
  3. 피어 에이전트는 만일 좌석이 이용 가능할 경우 서로 지급 정산을 할 수 있다. 
  4. 컨소시엄의 의장은 회원을 탈퇴시키고 남은 예치금을 반환할 수 있는 단독 권한을 가지고 있다.

이번에는 이전 예제 케이스에는 없었던 수정자, modifier가 생겼다.

수정자는 스마트 컨트랙트의 특수한 요소로서, 데이터와 함수에 대한 접근을 통제하기 위한 규칙을 나타낸다.

오직 유효한 회원만이 시스템에서 트랜잭션을 생성할 수 있고, 오직 의장만이 항공사를 회원에서 탈퇴시킬 수 있다.

코드는 다음과 같다. 코드를 먼저 살펴보자.

pragma solidity ^0.6.0;
contract Airlines {
    address chairperson;
    struct details { // 항공사 회원 구조
        uint escrow;
        uint status;
        uint hashofDetails;
    }

    mapping (address=>details) public balanceDetails; // 항공사 어카운트 페이먼트와 회원 매핑
    mapping (address=>uint) membership;


    // onlyChairperson 규칙을 위한 수정자
    modifier onlyChairperson {
        require (msg.sender == chairperson);
        _;
    }
    // onlyMember 규칙을 위한 수정자 
    modifier onlyMember {
        require (membership[msg.sender] == 1);
        _;
    }

    // 생성자 함수 payable 함수를 위한 msg.sender와 msg.value 사용 
    constructor() public payable {

        chairperson = msg.sender;
        membership[msg.sender] = 1;
        balanceDetails[msg.sender].escrow = msg.value;
    }

    function register() public payable {
        address AirlineA = msg.sender;
        membership[AirlineA] = 1;
        balanceDetails[msg.sender].escrow = msg.value;
    }

    function unregister(address payable AirlineZ) onlyChairperson public {
        membership[AirlineZ] = 0;
        AirlineZ.transfer(balanceDetails[AirlineZ].escrow);
        balanceDetails[AirlineZ].escrow = 0;
    }

    function request (address toAirline, uint hashofDetails) onlyMember
    public {
        if (membership[toAirline]!= 1) {
            revert ();
        }
        balanceDetails[msg.sender].status = 0;
        balanceDetails[msg.sender].hashofDetails = hashofDetails;
    }

    function response (address fromAirline, uint hashofDetails, uint done)
    onlyMember public {
        if (membership[fromAirline] != 1) {
            revert ();
        }
        balanceDetails[msg.sender].status = done;
        balanceDetails[fromAirline].hashofDetails = hashofDetails;
    }

    function settlePayment (address payable toAirline) onlyMember payable public {
        address fromAirline = msg.sender;
        uint amt = msg.value;

        balanceDetails[toAirline].escrow = balanceDetails[toAirline].escrow + amt;
        balanceDetails[fromAirline].escrow = balanceDetails[fromAirline].escrow - amt;

        toAirline.transfer(amt); // 외부 어카운트로 금액을 전송하는 스마트 컨트랙트 어카운트 
    }
}

address = 의장의 아이덴티티를 나타낸다.

struct = 에스크로 또는 예치금을 포함한 항공사의 데이터를 집합적으로 정의한다.

mapping = 회원의 어카운트 주소를 그들의 상세 정보에 매핑 

modifier = memberOnly와 chairpersonOnly를 정의