Account
이더리움 account는 크게 2가지가 있다.
- externally owned accounts (eoa) : 스마트 컨트랙트를 갖고 있지 않으며, private key로 관리되는 account
- contract account(ca) : 스마트 컨트랙트(코드)를 갖고 있으며, 코드로 관리되는 account
1번 계좌는 평범한 일반 유저의 계좌이다.
2번 계좌는 만들어진 account, 코드가 포함된 어카운트이다.
contract account 는 누군가 스마트 컨트랙트를 이더리움에 업로드하고 싶다고 할 때 '만드는 것' 이며, 이때 gas를 지불해야 한다.
그래서 eth를 지불하면서 요청을 보내면 보통 별 문제 없이 만들어진다. (gas = eth 인가..?)
업로드된 smart contract 는 자신만의 주소를 갖고 있고, 누군가 자신을 호출해주기를 기다린다.
자기 스스로 initiate 해서 실행된다거나 할 수는 없다. (하지만 자기가 실행하면서 내가 다른 contract account 를 호출하는 것은 가능하다)
각 account 는 자신을 유일하게 구분할 수 있는 id로, 20바이트 크기의 address가 있다.
(이 주소는 비트코인과 비슷하게 해시값의 일부를 가져와서 사용한다.)
이더리움의 글로벌 state는 이런 각각의 account 정보들을 포함하는데,
externally owned account 는 이 계좌에 남아있는 잔액을, contract account 는 이 코드에 어떤 변수가 있고, 최근 실행 결과로 어떤 변수를 무슨 값으로 어떻게 갱신되었는지에 대한 정보를 저장한다.
정리하면 각 account 는 상태와 주소를 갖고 있으며, 각 account 의 상태들을 모은 것이 이더리움의 global state 가 된다.
Account의 state 에는 다음과 같은 값이 들어간다.
- nonce
EOA에서의 nonce 값은 이 어카운트에서 발생시킨 트랜잭션의 수를 나타낸다.
CA에서의 nonce 값은 이 어카운트가 호출된 수를 나타낸다.
nonce는 트랜잭션을 구분하기 위해 사용된다.
예를 들어 A가 B에게 1이더를 보낸 후에 또 1이더를 보냈다고 해보자.
하지만 nonce가 없다면 누군가 같은 트랜잭션을 복사해서 한번 더 호출한 것과 같다고 판단할 수도 있다. (위 예시라면 B가 복사해서 호출했을 때 1이더를 추가로 받으니 이득이다.)
따라서 이를 구분하기 위해 nonce 필드를 사용한다. (비트코인에서는 이런 double spend 문제를 utxo로 막았다.)
- balnace
EOA 라면 각 계좌에 남은 잔액을 가리키는 것이 자명하다.
CA 에서도 잔액을 갖는데, smart contract 를 작성할 때 그 내부적으로 특정 비용을 지불하도록 할 수도 있기 때문이다.
(지난 글에서 살펴본 코드에서 value 가 10 미만이면 도메인 이름을 등록하지 않았던 것과 같이)
- storage Root
CA에서 사용하는 변수의 값은 Merkle Patricia tree 구조에 저장한다.
이 값은 매번 CA를 호출할 때마다 바뀌는 static 변수의 값과 같으며, smart contract 를 실행할 때 변수의 값을 쉽고 빠르게 읽기 위해 트리구조에 저장하였다. storage Root 는 이 머클 트리의 루트를 해싱한 값을 저장한다.
- code hash
EOA에서는 코드가 없기 때문에 빈 문자열이 저장되지만, CA에서는 smart contract에 저장된 코드에 대한 해시값을 저장한다.
코드를 실행하기 전에 해시값을 확인해서 코드가 변경되지 않았음을 확인하고 실행한다.
- world state (global state)
각 어카운트 주소와 각각의 상태에 대한 매핑 정보를 갖고 있으며, merkle patricia tree에 저장되어있다.
Patricia tree 는 Proctical Algorithm To Retrieve Information Coded In Alphanumeric 의 줄임말로,
위와 같은 구조를 갖고 있다.( 마치 prefix trie 구조와 비슷한 것 같다.)
줄임말을 해석해보면, 알파벳 숫자로 된 정보를 신속하게 꺼내오는 구조라고 보면 된다.
실제 이더리움에서는 이런 모양의 Merle Patricia Tree 를 사용한다.
world state 에는 다른 주소들의 account 값과 그 상태가 key - value로 저장되어 있다.
이때 account 값은 해시값이니, 이를 적절히 알파벳 numeric 형식을 바꿨을 때, 어떤 주소에 얼마가 들어있는지는 위와 같은 트리 구조를 통해 빠르게 확인할 수 있다.
world state 를 보면 key(=account)가 a711355 이고, value(=주소에 저장된 현재 잔액) 로 45.0 eth 가 들어있다고 할 때, 이 정보를 빠르게 찾기 위해서 a7이라는 공통 항목을 가진 노드를 먼저 들어가고, 그 노드와 연결된 branch node 를 타고 들어간다.
다음으로 3번째 문자를 보면 1 7 f 7 로 다르기 때문에 각각의 문자에 대해 연결된 포인터를 타고 들어가면 leaf node 또는 extension node 가 있고, 다시 extension node 는 branch node 와 견결되어 그 다음 문자에 대해 어디로 타고 들어가야 하는지 포인터를 갖고 있다.
마지막 leaf node 에는 나머지 key 내용이 들어있는 key-end 와, 실제 value 값이 저장되어 있다.
계좌를 만드는 방식은 EOA와 CA가 다르다.
EOA에서는 월렛을 통해 랜덤하게 256bit private key를 만든다.
public key는 private key로부터 얻어지니 월렛에 굳이 저장하지 않는다.
그리고 public key를 해시해서 20byte를 추리면 그것이 EOA의 주소가 된다.
이때 사용하는 해시 알고리즘은 Keccak-256 (케샥) 알고리즘을 사용한다고 한다.
CA 역시 40글자 = 20byte 의 주소를 가진다.
CA 주소를 만들 때는 CA를 만드는 사람의 address 와 nonce 값을 사용해서 만든다.
Externally Owned Account (EOA)
EOA는 비트코인에서와 마찬가지로 public key, private key 를 가지며, 트랜잭션을 전송할 때는 자신의 서명을 함께 제시하는 매커니즘을 그대로 따른다.
EOA는 P2P 네트워크로 메세지(트랜잭션)를 보낼 수 있는데, 크게 2가지 종류의 메세지를 보낸다.
1. 다른 EOA로 보내는 경우 : 노드와 노드 사이 eth 전달 (value transfer)
2. 다른 contract account 로 보내는 경우 : smart contract 생성 또는 invoke
그리고 메세지를 보낼 때는 내가 보낸다는 증거로 서명을 함께 제시해야 한다.
이를 그림으로 보면 위와 같다.
위 그림은 eoa가 ca로 트랜잭션을 보내서 invoke 시키는 것을 보여주고, ca는 internal transaction 을 통해 또 다른 ca를 invoke 하는 것을 보여준다.
아래 그림은 eoa 와 eoa 사이의 value transfer를 보여준다.
그리고 모든 트랜잭션은 그 종류와 목적에 상관없이 EVM에서 실행된다.
Transaction
이더리움의 제일 단순한 트랜잭션은 eth를 전달하는 것이지만,
제일 중요한 목표는 트랜잭션을 통해서 smart contract 를 invoke 시키는 것이다.
그래서 결국 하나의 트랜잭션은 world state 를 변화시키며, 이더리움은 여러 트랜잭션을 모은 블록 단위로 컨펌하기 때문에, 블록단위로 state 가 변화하게 된다.
이더리움 트랜잭션의 구조는 위와 같이 생겼다.
주요 필드만 설명을 붙여보면
Metadata
- chain id : 이더리움은 테스트 네트워크가 존재하여, 내가 만든 아이디어를 구현한 smart contract 를 테스팅하는 공간이 존재한다.
이 테스팅 공간은 실제 네트워크와 다른 체인을 가지고 있다.
- type : 스마트 컨트랙트를 생성하는 트랜잭션인가 아닌가
- from : 이 트랜잭션을 만든 주체의 address, 이 필드는 EOA 의 주소가 될 것이다.
- value : 얼마의 이더를 보낼지 기술한다. 이때 작성하는 단위는 WEI (웨이)이다. 10*18 WEI = 1eth 이다. 너무 작은 단위라 보통 GWei 를 많이 쓴다. (기가웨이)
- gas : 이 트랜잭션에서 최대 지불할 수 있는 gas 양 (기본 비용)
- gasPrice : 내가 1gas 당 지불할 가격
- max fee per gas : 이 트랜잭션을 검증하는 validator 에게 추가로 지불할 gas 당 수수료. 원래 base fee + priority fee 가 기본적으로 지불되는데, 이게 너무 커지면 부담스러우니까 최대 이거까지만 지불하겠다고 정하는 상한선
- max priority fee (per gas): gas 당 추가로 지불할 팁, 네트워크 상황에 따라 자동 결정되는 base fee 에 더해진다. 이 값이 이 트랜잭션이 높으면 우선적으로 검증될 확률이 높아진다.
- (r, s, v) : (r, s) 는 서명에 해당하는 내용이고 v는 public key 를 유도하는데 필요한 내용이다. 트랜잭션을 검증할 때 원본 메세지 (트랜잭션의 해시값) 과 서명, 그리고 public key 가 필요하기 때문에 이렇게 (r, s, v) 쌍으로 제시한다.
Cache
이 트랜잭션이 접근하고자 하는 smart contract 의 영역을 리스트로 제시한 것 (음..?)
Data
Smart Contract 를 호출할 때 인자로 넘길 데이터,
만약 새로운 smart contract 를 만들려고 하는 경우에는 그 코드가 데이터에 들어간다.
생성된 트랜잭션은 이더리움 네트워크에 브로드캐스팅되며, validator 는 트랜잭션을 받아서 블록에 포함한 뒤 검증한다.
(2022년부터는 비트코인과 달리 validator 라는 말을 사용하는데, 나중에 컨센서스 정리할 때 자세히 정리할 예정)
computation을 진행할 때는 gas 가 필요하고, 이때 추가적으로 수수료도 발생한다고 했다.
수수료는 computation 뿐 만 아니라 storage 에도 발생한다. (보통 32Byte 배수에 비례하여 발생)
이렇게 수수료를 매기는 목적은, computation 과 storage 모두 이더리움 플랫폼의 소중한 자산인데, 이를 낭비하지 않게 하기 위함이다.
트랜잭션을 전송한 사람이 250 gas 를 설정했고, 각 operation 마다 gas를 소모하는데, 중간에 gas 가 다 떨어지면 이는 환불되지 않고 상태에는 아무 변화도 일어나지 않는다.
아무런 코드를 실행하지 않는, simple transfer transaction 의 경우, 21000 단위 gas 를 요구한다.
그래서 이보다 작은 gas를 넣으면 어떤 트랜잭션도 시작하지 않는다.
이제 간단한 simple transfer transaction 에서의 gas 비용 계산을 해보자.
밥이 앨리스에게 1ETH를 전송할 때, 시스템이 결정한 base fee per gas 는 190 Gwei 였다고 해보자.
그리고 밥이 트랜잭션에 설정했을 때는 maxPriorityFeePerGas 를 10Gwei 로 잡았다고 해보면 최종적으로 지불할 fee 는 다음과 같다.
(수수료 상한을 아직 모르므로)
기본 수수로 + 밥이 추가로 지불할 수수료 = 190 + 10 = 200 Gwei / gas
이때 21000 gas 를 소모해야 하므로, 200 * 21000 = 4200000 Gwei
이를 이더단위로 환산하면 10**18 wei 으로 나눠주면 되므로 0.0042 ETH 가 된다. (4200000 은 기가wei 였다.)
따라서 밥의 계좌에서는 최종적으로 1.0042 ETH 가 빠져나갈 것이고,
앨리스는 1ETH를 받으며, base fee 는 사라지고 validator 에게 들어가는 돈은 추가 수수료 10 에 대한 ETH만 지불된다.
따라서 0.000210 ETH 를 얻게 된다.
이와 같이 동작하는 건 2021년 진행된 London Upgrade 에 의한 내용이다.
(2021년에는 PoW 로 채굴하던 방식이었지만, 그때도 채굴에 성공하면 수수료 일부를 소각하고, 나머지만 채굴자에게 주었다고 한다.
소각은 절대 소비할 수 없는 임의의 주소로 ETH를 보내버리면 된다.)
이더리움은 PoS 이후 12초마다 한번씩 새로운 블록이 생겨나고, 검증자에게 이더리움을 보상으로 제공한다.
하지만 블록에 들어있는 트랜잭션의 수수료는 일부는 소각시킨다. (base fee 소각)
비트코인은 2100만 블록의 상항을 두어 비트코인의 가치를 유지시킨다면, 이더리움은 이더리움 파운데이션의 결정을 통해 중간중간 코인을 소각하는 방법을 통해 통용되는 이더리움 코인의 수를 일정하게 유지시켜서 가치를 유지한다.
이더리움의 트랜잭션에는 2가지 타입이 존재한다.
1. Message Call
2. Contract Creation
1번은 기존의 코드를 호출하는 것으로 위 그림을 보면 contract code 는 다른 contract code를 호출하면서 간접적으로 타고타고 들어가 호출될 수 있다.
2번 타입의 트랜잭션은 Contract Deployment Transaction 이라고도 부른다.
이런 트랜잭션은 목적지가 존재하지 않으므로 to 필드에 0을 집어넣는다.
Transaction Life Cycle
1. 트랜잭션 생성
2. 트랜잭션 브로드캐스트
3. 검증자가 트랜잭션을 담아서 블록을 만들고 브로드 캐스트
4. 다른 노드가 검증자가 만든 블록을 검증하고 투표를 통해 통과시킴 (justified)
5. 다른 블록이 이어서 추가로 검증되면 기존 블록은 finalized 됨. 이 상태의 블록을 바꾸러면 네트워크 레벨의 공격이 필요하며, 많은 돈이 소모됨.
Block
이더리움 블록에는 이렇게 여러 개 (보통 몇 백개) 트랜잭션이 들어가 있고, 싱글 스레드로 트랜잭션을 하나씩 순서대로 검증한다.
(따라서 블록 내 트랜잭션에도 검증한 '순서' 가 정해져있다.)
블록에는 검증한 사람의 블록 proposal 서명이 들어가고 이전 시그니처도 들어간다.
이더리움의 블록은 거의 정확히 12초마다 한번씩 생성된다.
비트코인은 10분에 한번이 '대략' 이었다. 채굴 경쟁이 존재하기 때문이다.
하지만 이더리움은 경쟁이 없기 때문에 12초마다 한번씩 지정된 검증자가 3~4초 정도 시간을 들여서 검증한 뒤 블록을 뿌리고 투표를 받으면 된다. 이때 투표를 참여하려면 자신의 지분을 담보로 참여해야 하기 때문에, Proof of Stake 지분 증명이라는 방법으로 이 블록이 캐노니컬 블록인지 합의를 하게 된다.
위 그림은 이더리움의 블록이 어떻게 생겼는지 보여준다.
비트코인처럼 현재 블록이 이전 블록의 해시값을 가리키고 있는 구조는 동일하다.
블록 헤더에는 이전 블록의 해시값 말고도, State Root 가 저장되어있다.
state 정보들역시 트리로 구성이 되어있으며, 그 leaf node에는 위에서 정리한 각 account 별 상태에 저장되는 값들이 들어있다.
이중에 storage root는 다시 위에서 정리한 merkle 패트리샤 트리의 루트를 해싱한 값이 들어있다.
블록에는 여러 개의 트랜잭션이 들어있는데, 각각의 트랜잭션으로 인해 어떤 상태의 어떤 필드 값이 어떻게 변화햇는지, 그 변화된 상태의 결과를 블록에 기록한다.
그런데 이더리움 블록에 저장된 상태는 이더리움에 존재하는 모든 account 에 대한 상태를 저장하는 global state 를 저장하고 있다.
매번 이 모든 상태를 다 저장하는 것은 부담스러울 뿐 아니라, 이 블록에 포함된 트랜잭션에 의해서 바뀐상태는 전체 상태 중에서 극히 일부일 것이다.
따라서 블록의 state root는 이 중에서 '바뀐 상태의 필드 값' 만 저장하고, 나머지는 이전 블록에서 참조만 한다.
위 그림이 이를 보여주고 있다.
그래서 이렇게 참조만 하는 경우에는 '그 바뀌지 않은 값' 이 무엇인지는 바로 알 수 없다.
이전 블록쪽으로 타고 넘어가야만 실제로 무슨 값이었는지를 알 수 있다.
따라서 저장공간의 효율성과, 정보를 읽을 때의 비용 사이에 trade off 를 한 것과 같다고 보면 된다.
PoS
간단하게만 블록을 검증하는 프로토콜을 살펴보면
먼저 내가 validator 가 되기 위해서는 32ETH를 먼저 staking 해야 한다. (담보로 제공해야 한다.)
(최근에 비탈릭 부테린이 이거 저개발 국가한텐 너무 비싸지 않냐고 해서 낮출 필요성을 언급하긴 했다.)
그리고 블록은 각 slot (12초 간격) 마다 검증되며, 이때 블록을 검증할 validator (=blcok proposer) 가 랜덤하게 뽑힌다.
검증자는 트랜잭션을 모아 블록을 만들어서 검증한 뒤 네트워크에 뿌리면, 똑같이 32ETH를 담보로 맡긴 노드들이 이 블록 속의 트랜잭션을 다시 실행시켜보면서 만들어진 블록을 추가로 검증한다. 그래서 투표 결과 2/3 이상이 동의하게 되면 캐노니컬 블록으로 인정하고 넘어가게 된다.
(이때 하나의 블록에 대해서만 투표를 하지 않을 수도 있다. 검증자로 선택되지 않은 노드가 그냥 검증자인척 검증해서 블록을 뿌릴 수도 있기 때문. 이때는 다른 정상적인 노드들은 당연히 기존 검증자가 검증한 블록을 투표할 것이다.)
Block Structure
높은 레벨에서 살펴본 블록의 구조는 위와 같다.
body 에는 블록의 중요한 정보들 ( = 트랜잭션) 이 들어간다.
블록 바디에는 이런 것들이 들어간다.
randao_reveal 은 다음 프로포저를 선택하기 위해 필요한 랜덤 넘버이다.
graffiti 는 블록 프로포저가 넣을 수 있는 임의의 내용이다.
slashing 은 벌칙을 말하는 것으로, 어떤 벌칙을 받을 만한 밸리데이터를 식별했다면, 그 정보를 블록에 포함해서 만들 수 있다.
attestations 는 이 블록을 지지하는 지지자들의 정보를 담는다.
Block Time
블록을 구분하는 시간을 말한다.
이더리움 블록은 12초마다 나눠진 슬롯 별로 채굴되는데, 특정 슬롯에서 검증자가 offline 이라거나 하는 이유로 블록이 생성되지 않을 수도 있다. (물론 그 경우엔 해당 프로포저는 벌칙을 받는다. 참고로 검증을 할 때도, 투표를 할 때도 보상을 받는다. 이때 투표 시에는 다수가 투표한 블록에 투표하지 않으면 벌칙을 받는다.)
그래서 이때 슬롯이 비기 때문에 그 다음 블록은 24초뒤에 채굴될 수도 있다.
Block Size
비트코인에서는 세그윗 이후 weigt 단위로 (4MB) 블록 크기의 제한을 두었다면
이더리움은 gas 를 가지고 블록 크기를 제한한다.
그래서 보통 15 million = 1500만 가스를 기준으로 잡고, 최대 2배인 3000만 가스까지 넣을 수 있도록 하였다.
즉, 블록에 포함된 모든 트랜잭션의 가스 합이 이보다 작아야 한다.
그래서 이렇게 gas 에 제한을 두었기 때문에, 어느정도의 고사양 컴퓨터 정도를 갖고 잇다면 누구든지 이더리움 블록을 검증할 수 있따.
하지만 너무 저사양 컴퓨터를 사용하면 12초 안에 블록을 못만들 수도 있기 때문에, 이런 일을 막고자 EVM을 설치할 때는 최소 사양 제한을 둔다.
'CS > 블록체인' 카테고리의 다른 글
[블록체인] 21. 이더리움 - 컨센서스 (1) | 2024.12.14 |
---|---|
[블록체인] 20. 이더리움 - Smart Contract & dApp (1) | 2024.12.13 |
[블록체인] 18. 이더리움 개요 (0) | 2024.12.13 |
[블록체인] 17. Lightning Network (3) : 라이트닝 네트워크 vs 비트코인 (1) | 2024.12.09 |
[블록체인] 16. Lightning Network (2) : Commitment Sign & Payment Channel (0) | 2024.12.04 |