IMG-LOGO
Home 암호화폐 강의 이더리움백서 6편

이더리움백서[6편]


Remind


이더리움의 목적은 분산 어플리케이션 제작을 위한 대체 프로토콜을 만드는것입니다. 튜링 완전 언어를 내장하고 있는 블록체인이라는 필수적이고 기본적인 기반을 제공함으로써, 대규모 어플리케이션에 유용한 개발 기법을 제공하고, 개발 시간 단축, 높은 보안성, 다른 어플리케이션과 상호작용할 수 있는 생태계를 만들고자하였습니다.

이더리움에서 상태(state)는 어카운트(account)라고 하는 오브젝트(object)들로 구성되어 있습니다. 어카운트 오브젝트는 20바이트의 주소와, 값과 정보를 전달해주는 상태변화(state transition)을 가지고 있으며, 논스(nonce), 이더(ether), 계약 코드, 저장 공간, 4가지의 필드 정보로 구성되어있습니다.

이더리움에서 화폐는 이더(ether)로 표기되며, EOA와 CA 두가지 타입의 계정으로 분류됩니다. 튜링 완전성을 보장하기 위해 악의적인 공격자가 네트워크 리소스를 소비하는 만큼 강제로 수수료(gas)를 지불하게함으로써 악의 적인 공격을 방지할 수 있다는 개념을 설명하였습니다.

메시지(Message)


컨트랙트는 또 다른 컨트랙트에게 메시지를 보낼 수 있으며, 메시지는 따로 저장될 필요가 없으며, 이더리움의 실행 환경에서만 존재하는 가상의 오브젝트입니다. 이러한 메시지는 아래의 항목들을 포함하고 있습니다.

  • * 메시지 발신처(암묵적)
  • * 메시지 수신처
  • * 메시지와 함께 전달되는 이더
  • * 선택적 데이터 필드
  • * STARTGAS 값

  • 메시지는 외부 실행자가 아닌 컨트랙트에 의해 생성된다는 것을 제외하면 트랜잭션과 유사합니다. 현재 코드 수행을 하고 있는 컨트랙트가 메시지를 생성하고 실행하라는 CALL opcode를 만나게 되면 메시지를 생성합니다. 트랜잭션과 마찬가지로, 메시지는 수신자 어카운트에 전달됩니다.

    컨트랙트는 외부 실행자가 하는 것과 정확히 같은 방식으로 다른 컨트랙트와 관게를 맺을 수 있으며, 트랜잭션이나 컨트랙트에 의해 할당된 gas 허용치는 그 트랜잭션과 모든 하위의 실행 연산에 적용됩니다.

    예를 들어, 외부 실행자 A가 B에게 1000 gas와 함께 트랜잭션을 보내고, B는 600 gas를 소모한뒤 C에게 메시지를 보내고, C의 내부 실행에 300 gas를 소모한 후 반환하면 B는 gas가 모두 소모되기 전에 100 gas를 더 사용할 수 있습니다. (1000 - 600 = 400, 400 -300 = 100)

    이더리움 상태 변환 함수(Ethereum State Transition Function)


    이더리움 상태 전이 함수 APPLY(S, TX) -> S’ 는 다음처럼 정의될 수 있다.


    IMG
    [출처 : 이더리움 백서(링크)]

    트랜잭션의 형식이 맞는지(전송하고자 하는 이더의 수량이 맞는지 확인) 체크하고, 서명이 유효한지, 논스가 발신처 어카운트의 논스와 일치하는지를 체크하며, 트랜잭션이 유효하지 않을 경우 오류를 반환합니다.

    STARTGAS * GASPRICE의 정보는 트랜잭션 수수료이며, 서명으로 부터 발신처 주소를 결정하게됩니다. 발신처 어카운트에서 수수료를 제외하고 발신자의 논스 정보를 증가 시키며, 발신처 잔고가 충분하지 않을 경우 오류를 반환합니다.

    GAS = STARTGAS로 초기화 한후, 트랜잭션에서 사용된 바이트에 대한 값을 지불하기 이해 바이트 양에 비례하여 gas를 차감합니다. 발신처 어카운트 에서 수신처 어카운트로 트랜잭션 값이 전송되며, 수신처 어카운트가 컨트랙트일 경우 컨트랙트의 코드 끝까지 수행 하거나, gas가 모두 소진될 때 까지 수행하게됩니다. (만약 악의적으로 무한 루프를 발생시킬 경우 gas가 다 소모되는 시점에서 종료됩니다.)

    만약 발신처의 잔고가 충분하지 않을 경우 트랜잭션 전송이 실패할 수 있으며, 컨트랙트 코드 수행시 gas가 부족할 경우 수수료를 제외한 모든 상태 정보는 원상태로 돌아가며, 해당 수수료는 채굴자의 어카운트에 지급됩니다. 모든 컨트랙트가 정상적으로 수행된 후 남아있는 gas에 대한 수수료는 발신처에 돌려주고, 소모된 gas의 수수료는 채굴자에게 보내지게됩니다.


    예를 들어, 아래와 같은 컨트랙트 코드가 있다고 가정해보겠습니다.

    1
    2
    if !self.storage[calldataload(0)]:
        self.storage[calldataload(0)] = calldataload(32)
    cs


    실제 컨트랙트 코드는 로우-레벨 EVM(이더리움 버츄얼 머신) 코드로 작성되지만, 이해를 돕기 위해 이더리움 하이-레벨 언어중 하나인 Serpent로 작성되었습니다(해당 코드는 EVM 코드로 컴파일 될 수 있습니다).

    컨트랙트의 스토리지가 비어있다고 가정하고, 트랙잭션 정보는 10tehr, 2000 gas, 0.001 ether gasprice, 63바이트의 데이터(0-31바이트는 숫자 2를, 32-63 바이트는 문자열 CHARLIE)를 보낸다고 가정했을 경우 상태 변환 함수의 프로세스는 아래와 같이 실행됩니다.

  • (1) 트랜잭션 정보가 유효하고, 형식이 정상적인지 확인 한다.
  • (2) 트랜잭션을 전송하는 발신 자의 이더 잔고가 최소 수수료 정보 만큼(2000 gas * 0.001 ether = 2 ether) 있는지 확인 한다, 만약 잔고가 충분할 경우 발신 자의 어카운트에서 수수료 금액인 2 ether를 차감한다.
  • (3) gas = 2000으로 초기화한다. 트랜잭션 총 길이가 170 바이트, 수수료가 5gas라고 가정했을 경우 170 * 5 = 850의 수수료가 소모되며, 초기 가스 정보인 2000에서 850을 제외한 1150 gas가 남게된다.
  • (4) 발신측 어카운트에서 전송할 ether의 양인 10 ether를 차감하고, 이것을 컨트랙트 어카운트에 더한다.
  • (5) 컨트랙트 코드를 실행 시키며, 컨트랙트의 index 2에 해당하는 스토리지가 사용되었는지 확인하고 index 2에 해당하는 스토리지 값을 CHARLIE로 설정 한다. 이 작업을 통해 187 gas가 소비되었다고 가정했을 경우 남아 있는 gas의 양은 1150 - 187 = 963 gas가 남게된다.
  • (6) 남은 수수료인 963 * 0.001 = 0.963 ether를 발신측 어카운트로 되돌려주고, 결과 상태를 반환한다.

  • 트랜잭션의 수신처에 컨트래트가 없을 경우, 트랜잭션의 총 수수료는 제공된 GASPRICE와 트랜잭션의 바이트 수를 곱한 값만이 수수료로 지출되며, 트랜잭션가 함께 보내진 데이터는 관련이 없어지게됩니다. (실행 할 컨트랙트가 없기 때문입니다.)

    메시지는 트랜잭션과 마찬가지 방식으로, 상태 정보를 원래 상태로 되돌린다는 것을 주목해야합니다. 메시지 실행시 gas가 부족할 경우, 메시지 실행에 의해 발생된 다른 모든 실행들은 원래대로 되돌려지게 되지만, 메시지를 실행 시켰던 부모의 상태는 되돌려질 필요가 없습니다. 이것은 컨트랙트가 다른 컨트랙트를 호출하는 것은 안전하다는 것을 의미하며, A가 X 만큼의 gas를 가지고 B를 호출할 경우, A의 실행은 최대 X gas만을 잃는다는 것을 보장 받게됩니다.

    코드 실행(Code Exceution)


    이더리움 컨트랙트 코드는 "이더리움 버추얼 머신 코드" 또는, "EVM 코드"로 불리는 스택 기반의 바이트 코드 언어로 작성됩니다. 컨트랙트 코드는 연속된 바이트로 구성되어 있고, 각각의 바이트는 연산(operation)을 나타냅니다. 이러한 컨트랙트 코드는 반복적으로 연산을 수행도록 구성된 무한 루프, 혹은 코드의 마지막에 도달될 경우 종료되거나, 오류나 STOP, RETURN과 같은 명령어를 마주했을 경우 종료됩니다. 또한, 연산을 수행하기 위해서는 아래와 같이 데이터를 저장하는 세가지 타입의 공간에 접근할 수 있어야 합니다.


  • * 스택
  • * 메모리
  • * 컨트랙트의 영속적인(long-term) 저장소(storage)


  • * 컨트랙트 코드는 블록 헤더 데이터 뿐만 아니라, 특정 값이나, 발송자 및 수신되는 메시지의 데이터에 접근 할 수 있으며, 결과 값으로 데이터의 바이트 배열을 반환할 수도 있습니다.


    이러한 EVM 코드의 공식 실행 모델은 놀랍도록 단순합니다. 이더리움 버추얼 머신이 실행되는 동안, block_state, transaction, message, code, memory, stack, pc, gas 와 같은 모든 계산 상태는 튜플(tuple)로 정의될 수 있으며, block_state는 모든 어카운트를 포함하는 전역 상태(global state)로서 잔고와 저장소(storage)를 포함합니다.

    스마트컨트랙트 code의 pc(프로그램 카운터)번째 바이트의 현재 명령이 실행되고, pc가 코드의 길이보다 크면 (pc>=len(code) pc는 0), 각각의 명령은 튜플이 어떻게 변화되는지 알 수 있습니다.

    예를 들어 ADD는 스택에서 두개의 아이템을 꺼내(pop), 그 합을 구한 후 다시 스택에 넣고(push) gas를 1만큼 감소 시키고, pc는 1 증가시킵니다. SSTORE는 스택에서 두개의 아이템을 꺼내 이 아이템의 첫번째 값이 가리키는 컨트랙트 저장소 인덱스에 두번째 아이템을 넣습니다.

    이더리움 버추얼 머신 환경을 JIT 컴파일을 통해 최적화 하는 많은 방법이 있지만, 기본적인 이더리움은 수백줄의 코드로 구현될 수 있습니다.

    Think


    이더리움에서 메시지는 컨트랙트에 의해 생성되며, 컨트랙트가 실행되는 과정에서 수수료가 발생하는 과정이 어떻게 되는지에 대한 설명으로 시작되었습니다. 컨트랙트는 특정 조건이 맞았을때 실행되는 프로그램 코드이며, 해당 코드는 EVM 환경에서 작동됩니다. 지난 시간에서 언급되어듯이 악의적인 프로그램을 막기 위해 코드가 실행되는 바이트의 양에 따라 수수료인 gas가 소모되는 과정, 그리고 gas가 남았을 경우 발신자에게 반환되는 과정에 대한 설명으로 이어졌습니다.

    이러한 스마크 컨트랙트 코드는 정상적으로 수행이 완료되던지, gas가 부족하여 종료되던지, 혹은 오류가 발생하는 시점에서 종료가되며, 비트코인의 부족한 상태 변화와 달리 이더리움에서는 다양한 상태 변화를 가질 수 있다는 것을 설명하였습니다. 또한, 컨트랙트 코드는 연산을 위해 스택, 메모리, 저장소에 접근할 수 있어야 하며, 블록 헤더 데이터 뿐만 아니라 특정 값이나, 메시지 발송자 및 수신되는 메시지의 데이터에 접근할 수 있으며, 결과 값으로 데이터의 바이트 배열을 반환 할 수도 있었습니다.


    [참고문헌]


    * 이더리움 백서(한글)
    * 이더리움 백서(영문)



    작성자 : yahweh87