마스터링 이더리움 책을 읽고, 요약하고자 함.
이더리움 프로토콜의 핵심에는 이더리움 가상 머신(Ethereum Virtual Machine, EVM)이 있다. 이름에서 알 수 있듯이, 이더리움 가상 머신은 마이크로소프트의 닷넷(.NET) 프레임워크 가상 머신이나 자바와 같은 바이트코드로 컴파일된 프로그래밍 언어의 인터프리터와 비슷한 계산 엔진이다.
EVM은 스마트 컨트랙트 배포 및 실행을 처리하는 이더리움의 일부다. 하나의 EOA에서 다른 EOA로의 간단한 값을 전송하는 트랜잭션은 사실상 EVM이 필요없지만, 그 외 모든 것은 EVM에 의한 상태 업데이트를 수반한다. 넓게 보면 이더리움 블록체인에서 실행되는 EVM은 자체 영구 데이터 저장소가 있는 수백만 개의 실행 가능 객체를 가진 전 세계의 탈중앙화된 컴퓨터다.
EVM은 유사 튜링 완전 상태 머신(quasi-Turing-complete state machine)이다. 이것은 스마트 컨트랙트 실행에 사용할 수 있는 가스양에 따라 모든 실행 프로세스가 유한개의 계산 단계로 제한된다는 것을 의미한다. 따라서 정지 문제(Halting problem)은 해결되고 실행이 영원히 지속되어 이더리움 플랫폼이 전체적으로 중단되는 상황을 피할 수 있다.
EVM은 메모리 내의 모든 값을 스택에 저장하는 스택 기반 아키텍처다. 또한, 256비트의 단어 크기(주로 네이티브 해싱과 타원 곡선 작업을 쉽게 하기 위해)로 동작하며, 주소지정이 가능한 여러개의 데이터 구성요소를 가지고 있다.
'가상 머신'이라는 용어는 일반적으로 Virtual Box나 QEMU 같은 '하이퍼바이저'에 의한 실제 컴퓨터의 가상화, 또는 리눅스의 KVM과 같은 전체 운영체제 인스턴스의 가상화에 종종 사용된다. 이러한 가상화는 실제 하드웨어, 시스템 호출, 기타 커널 기능에 대해서 각각 소프트웨어 추상화를 제공해야 한다.
하지만 EVM은 훨씬 제한된 영역에서 작동한다. 이는 계산 엔진일 뿐이며, JVM 사양과 유사한 계산 및 스토리지의 추상화를 제공한다. 상위 레벨 관점에서 JVM은 기본 호스트 OS 또는 하드웨어에 구속받지 않고 런타임 환경을 제공하도록 설계되어 다양한 시스템에서 호환이 가능하게 한다. Java, Scalar, C# 같은 고급 프로그래밍 언어는 해당 가상 머신의 바이트코드 명령어 집합으로 컴파일된다. 같은 방식으로 EVM은 LLL, Serpent, Mutan, Solidity 같은 고수준 스마트 컨트랙트 프로그래밍 언어가 컴파일되어 생성된 자체 바이트코드 명령어 집합을 실행한다.
EVM은 실행 순서가 외부에서 구성되기 때문에 스케줄링 기능이 없다. 즉, 이더리움 클라이언트가 검증된 블록 트랜잭션을 통해 어떤 스마트 컨트랙트가 어떤 순서로 실행되어야 하는지를 결정한다. 이러한 의미에서 이더리움 월드 컴퓨터는 JS처럼 단일 스레드다. 또한 EVM에는 'System Interface' 처리 또는 '하드웨어 지원'이 없다. 즉, 인터페이스할 물리적인 장비가 없다. 즉, 이더리움 월드 컴퓨터는 완전히 가상 환경이다.
EVM 명령어 집합은 다음과 같은 대부분의 작업을 제공한다.
일반적인 바이트코드 작업 외에도 EVM은 계정 정보(주소 및 잔액) 및 블록 정보(블록 번호 및 현재 가스 가격)에 접근할 수 있다.
EVM의 작업은 이더리움 프로토콜에 정의된대로 스마트 컨트랙트 코드의 실행 결과로 유효한 상태 변화를 계산하여 이더리움 상태를 업데이트하는 것이다. 이러한 측면에서 이더리움을 트랜잭션 기반의 상태 머신으로 설명할 수 있고, 이것은 외부 주체(즉, 계정 소유자 및 채굴자)가 트랜잭션 생성, 수락 및 주문을 통해 상태 변화를 시작한다는 사실을 반영한다. 이 시점에서 이더리움 상태를 구성하는 것은 무엇일까?
가장 상위 레벨에서 보면 이더리움 월드 상태(world state)가 있다. 월드 상태는 이더리움 주소(160비트)를 계정(account)에 매핑한 것이다. 좀 더 세부적으로 살펴보면, 각 이더리움 주소는 이더 잔액, 논스, 계정의 스토리지, 계정의 프로그램 코드를 의미한다. 이더 잔액(balance)은 계좌가 소유한 웨이로 저장되고, 논스(nonce)는 계정이 EOA일 경우 해당 계정에서 성공적으로 전송한 트랜잭션의 수로 나타내며, 컨트랙트 계정의 경우에는 생성된 컨트랙트의 수를 나타낸다. 계정의 스토리지(storage)는 스마트 컨트랙트에서만 사용하는 영구 데이터 저장소이고, 프로그램 코드(program code)는 계정이 스마트 컨트랙트 계정일 때만 존재한다. EOA에는 항상 코드가 없고 스토리지는 비어 있다.
트랜잭션이 스마트 컨트랙트 코드를 실행할 때
EVM은 생성 중인 현재 블록 및 처리 중인 특정 트랜잭션과 관련하여 필요한 모든 정보로 인스턴스화된다. 특히 EVM의 프로그램 코드 ROM에는 컨트랙트 계정 코드가 로드되고, 프로그램 카운더는 0으로 설정되며, 스토리지는 컨트랙트 계정의 스토리지에서 로드되고, 메모리는 모두 0으로 설정되어 모든 블록 및 환경 변수가 설정된다. 주요 변수는 이 실행을 위한 가스 공급량이며, 트랜잭션 시작 시 솔금자가 지급한 가스의 양으로 설정된다. 코드 실행이 진행되면서, 실행된 작업의 가스 비용에 따라 가스 공급량이 감소한다. 어떤 시점에서 가스 공급량이 0으로 감소하면 '가스 부족(Out Of Gas, OOG)' 예외가 발생하고, 실행이 즉시 중단되고 트랜잭션이 중단된다. 이더리움 상태를 변경되지 않으며, 단지 송금자의 논스가 증가되고 이더 잔액이 정지 지점까지 코드를 실행하는 데 사용된 자원에 대한 블록의 수혜자에게 지급하기 위해 줄어든다. 이 시점에서 EVM은 이더리움 월드 상태의 샌드박스 사본에서 실행되고 있다고 생각할 수 있다. 어떤 이유로든 실행을 완료할 수 없는 경우 이 샌드박스 버전은 완전히 삭제된다. 그러나 실행이 성공적으로 완료되면 실제 상태가 호출된 컨트랙트의 저장 데이터 변경, 생성된 새로운 컨트랙트 및 시작된 모든 이더 잔액 전송을 포함하여 샌드박스 버전과 일치하도록 업데이트된다.
스마트 컨트랙트 그 자체가 사실상 트랜잭션을 시작할 수 있기 때문에 코드 실행은 재귀적 프로세스다. 컨트랙트는 다른 컨트랙트를 호출할 수 있으며, 각 호출이 새로운 호출 대상을 중심으로 다른 EVM을 인스턴스화한다. 각 인스턴스는 상위 레벨 EVM 샌드박스의 초기화된 샌드박스 월드 상태를 갖는다. 각 인스턴스는 또한 가스 공급을 위해 지정된 양의 가스를 공급받으며, 실행을 완료하기에는 가스가 너무 적어서 예외가 발생할 수 있다. 이 경우에도 샌드박스 상태는 삭제되고 실행은 상위 레벨 EVM으로 돌아간다.
어떤 종류의 프로그램이라도 실행할 수 있다면 그 시스템 또는 프로그래밍 언어는 튜링 완전(Turing complete)이다. 그러나 이 기능에는 매우 중요한 주의사항이 있다. 일부 프로그램은 영원히 실행될 수 있다는 점이다. 여기서 중요한 것은 단순히 프로그램을 살펴보는 것만으로는 영원히 실행되는지 또는 실행되지 않는지 여부를 알 수 없다는 것이다. 우리는 실제로 프로그램의 실행을 끝내고 결과가 어떻게 되는지 기다려야 한다. 물론, 영원히 실행된다면 영원히 기다려야 한다. 이것을 정지 문제(haltinf problem)라고 부르며, 이것이 해결되지 않으면 이더리움에게는 큰 문제가 아닐 수 없다.
정지 문제로 인해 이더리움 월드 컴퓨터는 절대로 멈추지 않는 프로그램이 실행될 위험이 있다. 이더리움은 어떤 스케줄러도 없이 단일 스레드 머신처럼 동작한다. 그래서 무한 루프에 빠지게 되면 전체 이더리움을 사용할 수 없게 된다.
그러나 가스를 사용하면 해결 방법이 생긴다. 미리 지정된 최대 계산량을 수행한 후에 실행이 종료되지 않는다면 프로그램 실행은 EVM에 의해 중단된다. 이렇게 하면 EVM이 '유사(quasi)' 튜링 완료 머신이 된다. 프로그램이 특정 계산량 내에서 종료되는 경우에만 입력한 모든 프로그램을 실행할 수 있다. 이더리움에서는 그 한도가 고정되어 있지 않다. 최대 한도까지 지급할 수 있다. 이를 '블록 가스 한도'라고 한다. 모든 사람이 시간이 지남에 따라 그 최대치를 늘리는 것에 동의할 수도 있다. 그럼에도 불구하고 주어진 특정 시점에서는 한계가 주어져 있기 때문에 실행 중 가스를 너무 많이 소비하는 트랜잭션은 중단된다.