無知

갈 길이 먼 공부 일기

기술 공부/블록체인

이더리움의 작동방식 (6) | 트랜잭션 실행, RLP, 메시지콜, 계약생성

moozii 2022. 1. 5. 20:28

* 이 글은 How does Ethereum work, anyway? 라는 글을 읽어가며 이해한 바를 한국어로 번역하여 정리하는 글입니다. 시리즈의 형태로 끊어가며 업로드되었으니, 참고 부탁드립니다.

 

이더리움 블록체인의 구성 요소

 

이더리움 블록체인은, 

계정 / 상태 / 가스 및 수수료 / 상호작용 / 블록 / 상호작용의 집행 / 채굴 / 작업 증명 

등으로 구성된다. 

 

지난 글에 이어서 계속 설명한다.

 

 

6. 트랜잭션의 실행

이더리움 네트워크에 트랜잭션을 보내고, 

그 트랜잭션을 기반으로 이더리움 네트워크의 상태가 전이되는 과정을 살펴보도록 하자.

 

트랜잭션 실행 선결 조건

 

먼저, 실행을 위해서는, 트랜잭션은 다음과 같은 요건을 충족해야 한다.

 

1. 트랜잭션은 올바르게 포매팅된 RLP를 포함해야 한다

RLP란, Recursive Length Prefix로 이진 데이터의 중첩 배열을 인코딩하는 데에 사용하는 데이터 형식이다.

 

2. 트랜잭션 서명이 유효해야 한다.

3. 트랜잭션 논스가 유효해야 한다. 

트랜잭션에서의 논스는 작업증명에서의 논스와 달리, "계정에서 보낸 트랜잭션 수"이다.

유효하다는 의미는, 전송자 계정 내의 논스 값과 트랜잭션에 적힌 논스가 일치해야 한다는 것이다.

 

4. 트랜잭션 가스 한계량이 트랜잭션 실행 시 사용되는 내부 가스량보다 크거나 같아야 한다.

내부 가스량이라 함은, 

4-1) 트랜잭션 실행을 위해 미리 정의된 가스량 21,000

4-2) 트랜잭션과 함께 전송하는 데이터의 가스 수수료

(데이터나 코드 중 0인 바이트의 경우 4가스, 0이 아닌 바이트의 경우 68가스)

4-3) 트랜잭션이 계약 생성 트랜잭션일 경우 계약 생성에 필요한 32,000 가스량

 

5. 전송자 계정 잔고가 <선결제 가스비>보다 충분해야 한다.

선결제 가스비란, 트랜잭션 가스 한계량 * 트랜잭션 가스 가격 + 전송자가 수령자에게 전공하는 이더리움 값

전체를 의미한다. (트랜잭션 최대 가스비 + 전송 값)

 

이더리움 네트워크에서 노드에 데이터 구조를 저장하거나, 혹은 노드끼리 데이터 구조를 주고 받으려면 통일된 형식이 필요하다. 그리고 이 통일된 형식을 만드는 것을 직렬화(serialization)라고 하고, 만들어진 통일된 형식을 바이트 스트림이라고 한다.
RLP는 이더리움 네트워크에서 쓰이는 직렬화(serialization) 기법이다. 바이트 스트림은 최소 단위를 바이트로 하는 데이터 묶음이다. 데이터를 파일에 저장하거나 네트워크에서 전송하기 위해서는 바이트 스트림 형식으로 변환되어야 한다. RLP는 데이터를 저장하거나 전송하는데 필요한 통일된 포맷을 제공한다. 데이터는 RLP로 변환되어 트랜잭션 전송, 블록 state 및 receipt 저장, DB 저장 등에 사용된다.

1. 첫번째 바이트가 [0x00, 0x7f] 사이의 값이면 그값 자체가 문자 데이터입니다. 
2. 첫번째 바이트가 [0x80, 0xb7] 사이의 값이면 문자열이고, 첫번째 바이트에서 0x80을 뺀 값이 문자열의 길이입니다. 
3. 첫번째 바이트가 [0xb8, 0xbf] 사이의 값이면, 첫번째 바이트에서 0xb7을 뺀값이 바이트로 표현된 RLP 아이템의 개수가 되고, 이어서 그 개수만큼 문자가 이어집니다.
4. 첫번째 바이트가 [0xc0, 0xf7] 사이의 값이면, 배열을 의미하고, 첫번째 바이트에서 0xc0를 뺀 값이 배열의 갯수를 의미합니다. 
5. 첫번째 바이트가 [0xf8, 0xff] 사이의 값이면, 배열을 의미하고, 첫번째 바이트에서 0xf7을 뺀값이 아이템의 갯수이고, 이어서 RLP 인코딩되어 연결된 데이터가 첫번째 바이트 이후에 이어집니다. 

https://medium.com/ethereum-core-research/rlp-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1c05a8150a04
RLP(Recursive Length Prefix)는 중첩된 배열 형태인 데이터를 인코딩하는 방법으로 이더리움 노드가 들고 있는 트랜잭션, 스마트 컨트랙트와 같은 오브젝트 데이터들을 직렬화(serialize)하기 위해 처음 제안 되었습니다. RLP는 두 가지 인코딩 형식을 사용합니다. 첫 번째는 문자열(string)이고 다른 하나는 배열입니다. 배열의 아이템은 다시 문자열과 배열 두 가지 형식으로 인코딩 되기 때문에 Recursive라는 이름이 붙었습니다. 또한 단일 문자열 인코딩을 제외하고는 인코딩된 데이터의 길이를 결과물 앞쪽에 붙이기 때문에 Length Prefix라고 할 수 있겠습니다. 코드체인에서도 노드의 오브젝트를 직렬화 하고 다른 노드에 보낼 때 RLP를 이용하고 있습니다.

https://medium.com/codechain-kr/%EC%9B%B9%EC%9C%BC%EB%A1%9C-rlp-%EB%94%94%EB%B2%84%EA%B9%85%ED%95%98%EA%B8%B0-d36048d4b556

 

https://preethikasireddy.medium.com/how-does-ethereum-work-anyway-22d1df506369

트랜잭션 실행

Step 1. 사전 처리

  • 전송자 잔고에서 선결제 비용을 감소시킨다
  • 전송자 계정의 논스를 1 증가시킨다

여기서의 가스 잔여량

= 전체 가스 한계량 - 내부 가스량

= 전체 가스 한계량 - (미리 정의된 21000 + 4 * 0인 바이트 수 + 68 * 0이 아닌 바이트 수 + 계약 생성시 32000)

 

Step 2. 실행과 그 도중의 정보의 기록

이더리움은 실행 과정에서, <SubState> 서브상태를 추적하는데, 

서브상태는 트랜잭션 종료 직후 필요한 정보를 트랜잭션 실행 도중에 미리 기록해놓는 방식이다.

예를 들어, 아래의 요소들이 포함된다.

  • 자기파괴 집합 (Self-desturct Set) : 트랜잭션 종료 시 삭제될 계정의 집합
  • 로그 시리즈 : 가상 머신 코드 실행을 위한 체크포인트 아카이빙 및 인덱스 정보
  • 환불 잔고 : 트랜잭션 직후 전송자 계정에 환불할 이더리움 양

이더리움은 환불 계산기, Refund Counter를 사용하여, 

트랜잭션에서 스토리지 상의 무언가를 삭제할 때마다, 그 수를 계산하여 환불량을 책정한다.

 

Step 3. 트랜잭션이 요구한 컴퓨팅 완료

 

Step 4. 상태 및 환불량 확정

트랜잭션 완료 후 모든 상태가 유효하면, 

전송자에게 미사용 가스량만큼 환불할 양을 결정지으며 상태가 확정된다.

미사용 가스량에 더불어, 앞서 설명한 환불잔고 등에 따라 환불량이 늘어나기도 한다.

 

Step 5. 정산 및 정리

  • 확정된 환불량대로 전송자에게 환불한다
  • 채굴자에게 가스에 대한 이더리움을 지급한다
  • 트랜잭션 중 사용된 가스는 블록 가스 계산기에 추가된다
    (블록 가스 계산기는 블록 내 트랜잭션 전체의 가스 총량을 계산하여, 블록 유효성 검증 과정에 활용된다.)
  • 모든 자기 파괴 집합 내 계정은 삭제된다

Step 6. 신규 상태와 트랜잭션 로그 집합

 

 

트랜잭션과 계약 생성, 메시지 콜의 차이

계약 생성

계약 생성은, 신규 계약 계정을 생성하는 트랜잭션을 의미한다.

신규 계약 계정을 생성하기 위해서는, 신규 계정의 주소를 정의하는 특정 공식을 활용해야 한다.

 

신규 계정 init 과정은 다음과 같다. 

  1. 계정 논스 값을 0으로 설정한다 
  2. 전송자가 계정 생성 트랜잭션과 함께 이더리움을 보냈을 경우 이를 잔고에 추가한다
  3. 전송자의 잔고에서 보낸만큼 전송자 잔고 값을 차감한다
  4. 스토리지가 비어 있도록 설정한다
  5. 계정의 코드 해시가 빈 스트링의 해시이도록 설정한다.

init 코드를 활용해 신규 계정을 생성한 후에는, 

init 코드의 실행 양상은 계약의 생성자마다 다르게 진행된다.

계정의 스토리지를 업데이트할 수도 있고, 다른 계약 계정을 생성할 수도 있고, 메시지 콜을 만들 수도 있다.

 

계약 실행 코드가 실행될 때 사용되는 가스의 경우, 

트랜잭션은 잔여 가스 이상 가스를 소비할 수 없고, 

만약 잔여가스량보다 더 많이 소비해야 할 경우에는 가스 부족(Out of Gas, OOG) 상태가 되어, 종료된다.

OOG 상태로 종료되면, 트랜잭션 이전의 상황으로 복원하게 되고, 

전송자는 가스 부족 상태에 이르기 전에 소비한 가스에 대해서 환불받지 못한다.

 

하지만 트랜잭션과 함께 전송자가 보낸 이더리움 값이 존재한다면, 

그에 대해서는 계약 생성이 OOG로 종료될 때 복원된다. 

 

계정 생성 작업이 잘 종료되면, 최종적으로 계정 생성 비용이 발생한다.

이는 스토리지 비용으로, 신규로 생성된 계약의 코드에 비례해서 발생한다.

최종 비용을 지불할 가스가 남아 있지 않을 경우, 트랜잭션은 또다시 OOG 상태를 맞아 취소된다.

 

앞서 말한 OOG들이 발생하지 않았다면, 잔여 가스량의 경우 전송자에게 환불되고, 

트랜잭션의 결과로서 변경된 신규 상태가 유지된다.

 

메시지 콜

계약 생성과 메시지 콜에는 몇가지 차이점이 있다. 

 

1. 메시지 콜은 계약 생성과 달리 init 코드가 포함되지 않는다.

신규 계정이 생성되지 않지만, input data는 포함시킬 수 있다. 

이 데이터는 트랜잭션 전송자가 제공한다.

 

2. output data를 포함할 수 있는 추가 요소도 보유하고 있다.

뒤에 이어지는 실행이 이 데이터를 필요로 할 수 있기 때문이다.

 

3. 메시지 콜이 OOG로 종료되거나 유효하지 않아 종료되면, 가스는 환불되지 않는다.

대신, 잔여 미사용 가스는 소비되어, 모든 상태를 복원하는 데에 사용된다.

 

비잔티움 업데이트를 통한 복원

기존의 이더리움은, 트랜잭션을 중지시키고 이전 상태로 복원하기 위한 방법은, 

가스 전체를 소비하는 것만이 유일했다. 

 

예를 들어, 계약을 통해 부른 Caller가 트랜잭션 실행을 할 수 없는, 유효하지 않은 계정이어서, 

에러가 발생하는 경우, 여전히 잔여 가스량은 모두 소비되었고, 전송자는 따라서 환불받지 못했다. 

 

하지만, 비잔티움 업데이트를 통해 revert 코드가 추가되면서, 

계약의 실행을 정지 시키고, 상태 변화를 복원하는 것을,

잔여 가스량 소비 없이 진행할 수 있고, 

트랜잭션이 실패한 이유를 리턴할 수 있다.