1.3 이더리움 가상머신
살펴보기
이더리움 가상머신 혹은 EVM은 이더리움 스마트 콘트렉트의 런타임 환경이다. EVM은 이것이 실행되는 인프라와 철저하게 고립되어 있는데, 이는 EVM에서 실행되는 코드는 네트워크나 파일시스템 혹은 다른 프로세스에 접근할 수 없음을 뜻한다. 스마트 콘트렉트는 심지어 다른 스마트 콘트렉트에 대해서도 제한된 접근 권한을 가지고 있다.
계정(account)
이더리움에는 두 개의 주소의 개념이 있다. 하나는 외부 계정(External Account)로 공개 키 쌍에 의해서 통제된다.(i.e. 사람). 나머지 하나는 콘트렉트 계정(Contract Account)으로 계정과 함께 저장된 코드에 의해서 통제된다.
외부 계정에 의한 주소는 공개키에 의해서 결정되고 콘트렉트 계정에 의한 주소는 콘트렉트가 만들어질 때 결정된다.(이 주소는 생성한 사람의 주소와 넌스(nonce)라 불리는 그 트랜젝션를 만들어낸 사람의 주소로부터 발생한 트랜젝션의 수로부터 만들어진다)
그 주소가 코드를 포함하건 그렇지 않건, EVM은 그 둘을 동일한 것으로 취급한다.
모든 계정은 영속적인 256비트 문자와 256비트 문자를 매핑하는 키-값쌍의 저장소(Storage)를 가지고 있다.
더해서, 모든 계정은 이더리움 잔액을 가지고 있고(단위는 정확히 Wei=1/10^18 ether) 이는 이더를 주고받는 트랜젝션에 의해서 바뀔 수 있다.
트렌잭션(Transactions)
트랜젝션는 한 계정에서 다른 계정으로 보내는 메시지다. (여기서 다른 계정의 특별한 형태로 제로계정이 될 수 있다. 아래 참조). 이 트랜젝션는 이진 데이터나 (이것의 페이로드) 이더리움을 포함할 수 있다.
만약 트랜젝션의 대상 계정이 코드를 가지고 있다면, 코드는 실행되고 트랜젝션에 포함된 값은 인풋값으로 쓰인다.
만약 트랜젝션의 대상 계정이 제로 계정이라면 (계정의 주소가 0), 그 트랜젝션는 새로운 콘트렉트를 생성한다. 앞서 언급했듯이, 콘트렉트의 주소는 0인 주소가 아니며, 트랜젝션를 생성한 사람의 주소와 그 주소가 만들어낸 트랜젝션의 숫자(넌스)로부터 생겨난다. 이렇게 콘트렉트가 만들어낸 트랜젝션에 실린 데이터는 EVM의 바이트코드로 여겨져 실행된다. 이로부터 생성된 아웃풋은 콘트렉트의 상태로 저장된다. 이것이 의미하는 것은 콘트렉트를 만들기 위해서는 콘트렉트의 실제 코드를 보내는 것이 아니라, 코드를 리턴하는 코드를 보내야 한다는 것을 뜻한다.
가스(Gas)
트랜젝션 생성 과정에서 각각의 트랜젝션는 일정한 양의 가스를 소모하게 되는데, 이것의 목적은 그 트랜젝션를 실행하는데 드는 작업의 횟수를 제한하고 이러한 실행의 댓가를 지불하고자 하는 데에 있다. EVM이 트랜젝션를 실행하는 도중에 가스는 일정한 규칙에 의해 조금씩 소모된다.
가스 가격(gas price)는 트랜젝션의 생성자에 의해서 설정되며 생성자는 ( gas price * gas )를 보내는 계정에서 지불해야 한다. 만약 실행 후 가스가 남았다면, 남은 가스는 동일한 방식으로 환불된다.
만약 가스가 실행 도중에 모두 소모되었다면(예를 들어 마이너스) out-of-gas 예외가 발생하고 현재의 트랜젝션 호출이 만들어낸 변동을 모두 취소해 원래대로 되돌려 놓는다.
스토리지, 메모리, 스텍
각각의 계정은 스토리지(storage)라고 불리는 영속적인 메모리 영역을 가지고 있다. 스토리지는 256비트의 단어와 256비트의 단어를 매핑한다. 콘트렉트 내에서 스토리지를 열거하는 것은 불가능하며, 상대적으로 이를 읽거나 쓰는것은 더욱 많은 비용이 소모된다.
두 번째 저장공간은 메모리(memory)라 불리는 영역이며, 이는 각각의 메시지 호출 과정에서 깨끗히 정리된 객체로 얻어진다. 메모리는 바이트 수준에서 얘기되지만 32바이트(256비트) 청크 단위로 읽거나 쓰여진다. 메모리는 크기가 커질수록 비용은 더욱 커진다(급수적으로 증가)
EVS은 레지스터 머신이 아닌 스택머신이므로 모든 연산은 스택이라 불리는 연산영역에서 처리된다. 이 스택영역은 1024개의 요소를 지닐 수 있고, 각각의 요소는 256비트의 단어를 포함할 수 있다. 스택에 접근하는 것은 상위 끝부분에 한정되어 있고, 구체적으로는 다음과 같은 방법으로 이뤄진다. 상위 16개의 요소들은 복사되고, 바로 밑에 16개의 요소들이 최상위로 올라온다. All other operations take the topmost two (or one, or more, depending on the operation) elements from the stack and push the result onto the stack. Of course it is possible to move stack elements to storage or memory, but it is not possible to just access arbitrary elements deeper in the stack without first removing the top of the stack.
인스트럭션 집합(instruction set)
The instruction set of the EVM is kept minimal in order to avoid incorrect implementations which could cause consensus problems. All instructions operate on the basic data type, 256-bit words. The usual arithmetic, bit, logical and comparison operations are present. Conditional and unconditional jumps are possible. Furthermore, contracts can access relevant properties of the current block like its number and timestamp.
메세지 콜(message call)
Contracts can call other contracts or send Ether to non-contract accounts by the means of message calls. Message calls are similar to transactions, in that they have a source, a target, data payload, Ether, gas and return data. In fact, every transaction consists of a top-level message call which in turn can create further message calls. A contract can decide how much of its remaining gas should be sent with the inner message call and how much it wants to retain. If an out-of-gas exception happens in the inner call (or any other exception), this will be signalled by an error value put onto the stack. In this case, only the gas sent together with the call is used up. In Solidity, the calling contract causes a manual exception by default in such situations, so that exceptions “bubble up” the call stack. As already said, the called contract (which can be the same as the caller) will receive a freshly cleared instance of memory and has access to the call payload - which will be provided in a separate area called the calldata. After it finished execution, it can return data which will be stored at a location in the caller’s memory preallocated by the caller. Calls are limited to a depth of 1024, which means that for more complex operations, loops should be preferred over recursive calls.
델리게이트콜 / 콜코드와 라이브러리
There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values. This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address. This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage in order to e.g. implement a complex data structure.
로그
It is possible to store data in a specially indexed data structure that maps all they way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts cannot access log data after it has been created, but they can be efficiently accessed from outside the blockchain. Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and cryptographically secure way, so network peers that do not download the whole blockchain (“light clients”) can still find these logs.
생성
Contracts can even create other contracts using a special opcode (i.e. they do not simply call the zero address). The only difference between these create calls and normal message calls is that the payload data is executed and the result stored as code and the caller / creator receives the address of the new contract on the stack.
자가소멸(selfdestruction)
The only possibility that code is removed from the blockchain is when a contract at that address performs the selfdestruct operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed. Note that even if a contract’s code does not contain a call to selfdestruct, it can still perform that operation using delegatecall or callcode.