-
이더리움 - 상태변환단계 및 코드실행 설명블록체인 2018. 7. 31. 23:15
상태변환함수
이더리움을 송금했다고 했을 때 계정의 상태가 어떻게 변하는지 아래에서 설명합니다.
상태변환 단계
- 트랜잭션이 형식에 제대로 맞는지(즉, 올바른 개수의 값을 가지고 있는지) 체크하고, 서명이 유효한지, nonce가 발신처 계정의 nonce와 일치하는지를 체크. 그렇지 않다면 오류를 반환
- gaslimit * gasprice 로 트랜잭션 수수료를 계산하고, 서명으로부터 발신처 주소를 결정
- 발신처 계정 잔고에서 위에서 구한 트랜잭션 수수료를 빼고 발신자 nonce를 증가. 발신처 잔고가 충분하지 않으면 오류를 반환
- GAS = gaslimit 으로 초기화 한 후, 트랜잭션에서 사용된 바이트에 대한 값을 지불하기 위해 바이트당 gas의 특정 양을 차감
- 발신처 계정에서 수신처 계정으로 트랜잭션 값을 보냄. 수신처 계정이 존재하지 않으면 새로 생성
- 수신처 계정이 컨트랙트면, 컨트랙트의 코드를 끝까지, 또는 GAS가 모두 소모될 때 까지 수행
- 발신처가 충분한 ‘돈'을 가지고 있지 못해서 값 전송이 실패하거나, 코드 수행시 GAS가 부족하면, 모든 상태 변경를 원상태로 돌려놓음. 단, 수수료(코드를 수행한 GAS)는 제외되고 이 수수료는 채굴자 계정에 더해지게 됨. 그 외에는, 모든 남아있는 모든 GAS에 대한 수수료를 발신처에게 돌려주고, 소모된 GAS에 지불된 수수료를 채굴자에게 보냄
상태변환 예제
아래와 같이 값을 가지고 있다고 가정하면 상태 변환 함수의 프로세스는 다음과 같습니다.
컨트랙트 계정의 storage는 0이라고 가정.
트랜잭션은 아래와 같이 필드를 가지고 전달된다고 가정
- value = 10 ether
- gaslimit = 2000
- gasprice = 0.001ether
- data = 64바이트(0-31 바이트까지는 숫자 2를 나타내고, 32-63 바이트는 CHARLIE 라는 문자열)
트랜잭션은 총 170바이트 길이를 가지고, 바이트당 GAS수수료는 5라고 가정
코드 실행 시, index 설정 작업의 GAS수수료는 187GAS로 가정
- 트랜잭션이 유효하고 형식이 제대로 맞는지 확인
- 트랜잭션 발송처가 최소 2000 * 0.001 = 2 ether를 가지고 있는지 확인하고 유효한 경우, 발송처의 계정에서 2ether를 차감
- GAS = 2000으로 초기화 한 후, 트랜잭션 처리 수수료인 850 GAS를 차감하면 1150 GAS가 남음
- 발송처 계정에서 보내고자 하는 10ether를 차감하고 이것을 컨트랙트 계정에 더함
- 코드(이미 작성되어 있는 컨트랙트 코드)를 실행. 이 경우는 간단한데, 컨트랙트의 index 2에 해당하는 스토리지가 사용되었는지 확인하고 (이 경우, 사용되지 않음) index 2에 해당하는 스토리지 값을 CHARLIE 로 설정. 이 작업을 하는 GAS 수수료를 차감하면, 남아있는 GAS의 양은 1150 - 187 = 963
- 963*0.001 = 0.963 ether를 송신처의 계좌로 되돌려주고, 결과상태를 반환
트랜잭션의 수신처에 컨트랙트가 없으면, 총 트랜잭션 수수료는 제공된 gasprice와 트랜잭션의 바이트 수를 곱한 값과 같아지고, 트랜잭션과 함께 보내진 데이터는 관련이 없어지게 됩니다. 즉, 위 예제를 사용하여 설명하면 수신처에 컨트랙트가 없으면 1150 GAS가 총 수수료이고 코드를 실행시킬 필요가 없기 때문에 187GAS 수수료를 사용하지 않게 됩니다.
메시지는 트랜잭션과 마찬가지 방식으로, 상태를 원래 상태로 되돌린다는 것에 주목해야 합니다. 메시지 실행시 GAS가 부족하게 되면 메시지 실행과 실행에 의해 발생된 다른 모든 실행들은 원래대로 되돌려지게 되지만, 그 부모 실행은 되돌려질 필요가 없습니다. 이것은 컨트랙트가 다른 컨트랙트를 호출하는 것은 안전하다는 것을 의미합니다. 예를 들어, A가 100 GAS를 가지고 B를 호출하면, A의 실행은 최대 100 GAS만 잃는다는 것을 보장받게 됩니다. 컨트랙트를 생성하는 CREATE라는 opcode를 보면, 실행 방식은 대체로 CALL과 유사하나, 실행 결과는 새로 생성된 컨트랙트의 코드를 결정한다는 차이가 있습니다.
코드 실행
코드 실행은 이미 작성되어 있는 컨트랙트의 코드를 실행하는 것입니다. 이미 예약되어 있는 작업을 실행하는 것으로 볼 수 있습니다.
이더리움 컨트랙트를 구성하는 코드는 이더리움 버추얼 머신 코드 또는 EVM 코드로 불리는 로우-레벨, 스택 기반의 바이트코드 언어로 작성됩니다. 이 코드는 연속된 바이트로 구성되어 있고, 각각의 바이트는 연산(operation)을 나타냅니다. 보통, 코드 실행은 0부터 시작하는 프로그램 카운터(pc)를 하나씩 증가 시키면서 반복적으로 연산을 수행하도록 구성된 무한 루프이고 코드의 마지막에 도달하거나 ERROR, STOP, RETURN 명령을 만나면 실행을 멈추게 됩니다. 연산을 수행하기 위해서는 아래 데이터를 저장하는 세가지 타입의 공간에 접근할 수 있어야 합니다.
- 스택(stack): Last-In-First-Out(LIFO) 컨테이너(리스트형태)로 여기에 값들을 넣거나(push) 제거(pop) 할수 있음
- 메모리(memory): 무한대로 확장 가능한 바이트 배열
- 컨트랙트의 영속적인(long-term) 저장소(storage): 키/값 저장소 (key/value storage). 계산이 끝나면 사라지는 스택이나 메모리와는 달리 저장소는 영속적으로 유지
코드는 또한 블록 헤더의 데이터 뿐만 아니라 특정 값이나 발송자 및 수신되는 메시지의 데이터에 접근할 수 있고 계산하여 결과값을 데이터의 바이트 배열을 반환할 수도 있습니다.
EVM 코드의 공식 실행 모델은 단순한 구조로 되어 있습니다. EVM이 실행되는 동안 모든 계산 상태는 (block_state, transaction, message, code, memory, stack, pc, gas) 으로 튜플로 정의될 수 있고, block_state는 모든 계정을 포함하는 전역상태(global state)로서 잔고(value)와 저장소(storage)를 포함합니다. 무한 루프에 의해 반복되는 코드를 실행 할 때, code의 pc(프로그램 카운터)번째 바이트의 현재 명령이 실행되고(pc 가 코드의 길이보다 같거나 크면 pc는 0으로 초기화), 실행되는 각각의 명령은 튜플을 어떻게 변화시킬지에 대한 자신의 정의를 알고 있습니다. 예를 들어, ADD는 스택에서 두개의 아이템을 꺼내(pop), 그 합을 구한 후 다시 스택에 넣고(push) GAS를 1만큼 감소시키고, pc는 1 증가시킵니다. SSTORE 는 스택에서 두개의 아이템을 꺼내 이 아이템의 첫번째 값이 가리키는 컨트랙트 저장소 인덱스에 두번째 아이템을 넣습니다.