지난 글에서는 트랜잭션을 “상태 변경 요청”이라고 정리했습니다.
그렇다면 다음 질문은 이것입니다.
트랜잭션은 정확히 무엇의 상태를 바꾸는가?
이더리움에서는 그 답이 계정입니다.
이더리움의 상태는 여러 계정의 현재 정보를 담고 있습니다. 트랜잭션이 실행되면 계정의 잔액, nonce, 컨트랙트 저장소 같은 값이 바뀔 수 있습니다.
현재 상태
├── A 계정
├── B 계정
└── Contract 계정
+ 트랜잭션 실행
= 새로운 상태
├── A 계정 balance 감소
├── B 계정 balance 증가
└── Contract 계정에 저장된 상태값 변경
Account 모델이란 무엇인가
Account 모델은 이더리움 상태를 계정 단위로 관리하는 방식입니다.
처음에는 은행 계좌처럼 생각해도 됩니다. 어떤 계정이 있고, 그 계정에 잔액이 있습니다. 트랜잭션이 실행되면 한 계정의 잔액이 줄고 다른 계정의 잔액이 늘 수 있습니다.
하지만 이더리움 계정은 단순한 잔액표보다 더 넓은 의미를 가집니다.
계정은 ETH 잔액뿐 아니라, 트랜잭션 순서를 위한 nonce, 컨트랙트 코드, 컨트랙트 저장소와도 연결됩니다.
Ethereum.org는 이더리움 계정을 ETH 잔액을 가지고 이더리움에서 메시지를 보낼 수 있는 entity로 설명합니다. 계정은 사용자가 관리할 수도 있고, 스마트 컨트랙트로 배포될 수도 있습니다.
계정의 기본 정보
Ethereum.org의 accounts 문서는 이더리움 계정이 네 가지 필드를 가진다고 설명합니다.
noncebalancecodeHashstorageRoot
처음에는 이렇게 이해하면 됩니다.
nonce: 이 계정에서 사용한 트랜잭션 순서 정보
balance: 이 계정이 가진 ETH 잔액
codeHash: 이 계정에 연결된 코드의 해시
storageRoot: 이 계정의 저장소를 대표하는 root
앞에서 배운 개념과 연결하면 더 자연스럽습니다.
nonce는 트랜잭션 글에서 봤던 값입니다. 같은 계정에서 같은 nonce를 가진 트랜잭션이 반복 실행되지 않도록 도와줍니다.
balance는 계정이 가진 ETH 양입니다. 단위는 보통 wei로 표현합니다.
codeHash는 컨트랙트 계정과 관련이 있습니다. 코드가 있는 계정은 호출되었을 때 그 코드가 실행됩니다.
storageRoot는 컨트랙트 저장소와 연결됩니다. 저장소 전체를 직접 블록 헤더에 넣는 것이 아니라, 저장소를 요약하는 root를 계정 상태에 연결합니다.
두 종류의 계정
이더리움 계정은 크게 두 종류입니다.
계정
├── 사용자가 개인키로 관리하는 계정
└── 코드로 동작하는 컨트랙트 계정
정확한 이름은 다음과 같습니다.
사용자가 개인키로 관리하는 계정 = EOA
코드로 동작하는 계정 = Contract Account
EOA는 Externally-Owned Account의 줄임말입니다.
이 용어가 처음에는 어색할 수 있습니다. 우선은 “개인키를 가진 사용자가 직접 관리하는 계정”으로 이해하면 됩니다.
Contract Account는 스마트 컨트랙트가 배포되면서 만들어지는 계정입니다. 이 계정은 개인키가 아니라 코드에 의해 동작합니다.
EOA는 무엇인가
EOA는 개인키로 관리되는 계정입니다.
트랜잭션 글에서 “사용자가 개인키로 관리하는 계정이 트랜잭션에 서명한다”고 했습니다. 그 계정이 EOA입니다.
사용자
-> 개인키로 트랜잭션 서명
-> EOA에서 트랜잭션 시작
-> 네트워크 전파
EOA의 중요한 특징은 트랜잭션을 시작할 수 있다는 점입니다.
예를 들어 A가 B에게 ETH를 보내는 경우, A의 EOA가 트랜잭션에 서명합니다.
A의 EOA
-> B 주소로 1 ETH 전송 트랜잭션 생성
-> 서명
-> 실행되면 A balance 감소, B balance 증가
개인키는 이 계정을 움직일 수 있는 권한입니다. 그래서 개인키를 가진 사람이 해당 계정의 자산 이동을 승인할 수 있습니다.
Contract Account는 무엇인가
Contract Account는 코드로 동작하는 계정입니다.
스마트 컨트랙트를 배포하면 네트워크에는 컨트랙트 주소가 생깁니다. 이 주소도 계정입니다.
하지만 EOA와 다르게 Contract Account에는 개인키가 없습니다. 대신 코드가 있습니다.
Contract Account
├── balance
├── code
└── storage
Contract Account는 누군가 호출했을 때 코드가 실행됩니다.
예를 들어 사용자가 어떤 컨트랙트의 함수를 호출하는 트랜잭션을 보내면, 그 컨트랙트 계정의 코드가 실행되고 storage 안의 값이 바뀔 수 있습니다.
EOA
-> 컨트랙트 호출 트랜잭션
-> Contract Account 코드 실행
-> Contract Account에 저장된 상태값 변경
중요한 점은 Contract Account가 스스로 트랜잭션을 시작하지 못한다는 것입니다.
Ethereum.org는 Contract Account가 트랜잭션을 받은 것에 대한 응답으로만 메시지를 보낼 수 있다고 설명합니다. 즉, 먼저 바깥에서 호출이 들어와야 코드가 실행됩니다.
지갑과 계정은 같은 말인가
정확히는 같지 않습니다.
계정은 이더리움 상태 안에 있는 대상입니다. 주소, 잔액, nonce 같은 값과 연결됩니다.
지갑은 그 계정을 사용하기 위한 앱이나 인터페이스입니다.
계정: 이더리움 상태 안의 대상
지갑: 계정을 보고, 서명하고, 트랜잭션을 보내는 도구
예를 들어 MetaMask 같은 지갑 앱은 사용자가 계정을 관리하고 트랜잭션에 서명하도록 도와줍니다. 하지만 지갑 앱 자체가 이더리움 계정은 아닙니다.
Ethereum.org도 account와 wallet을 구분합니다. Wallet은 이더리움 계정과 상호작용하게 해주는 인터페이스나 애플리케이션입니다.
stateRoot와 Account 모델
블록 헤더 글에서 stateRoot를 봤습니다.
stateRoot는 블록 안의 트랜잭션을 실행한 뒤 이더리움 상태가 어떤 모습인지 대표하는 root입니다.
이제 이 말을 조금 더 구체적으로 이해할 수 있습니다.
이더리움 상태
-> 계정들의 현재 정보
-> 계정 정보들이 트리 구조로 정리됨
-> 그 root가 블록 헤더의 stateRoot에 들어감
트랜잭션이 실행되면 계정 상태가 바뀔 수 있습니다.
트랜잭션 실행 전 stateRoot: Root A
트랜잭션 실행
-> 계정 balance 변경
-> 계정 nonce 변경
-> 컨트랙트에 저장된 상태값 변경
트랜잭션 실행 후 stateRoot: Root B
그래서 노드는 블록을 검증할 때 트랜잭션을 실행해보고, 자신이 계산한 결과 상태의 root가 블록 헤더의 stateRoot와 맞는지 확인할 수 있습니다.
여기서 “비교한다”는 말은 조금 더 풀어볼 필요가 있습니다.
블록을 만든 쪽은 블록 헤더에 stateRoot를 넣습니다. 이 값은 일종의 주장입니다.
이 블록 안의 트랜잭션들을 실행하면
최종 상태는 Root B가 됩니다.
검증하는 노드는 이 주장을 그대로 믿지 않습니다.
같은 이전 상태에서 시작해서, 블록 안의 트랜잭션을 순서대로 다시 계산합니다.
여기서 “다시 계산한다”는 말은 노드 프로그램이 블록 안의 트랜잭션을 하나씩 처리해본다는 뜻입니다.
이전 상태
+ 블록 안의 트랜잭션들 순서대로 처리
= 내가 계산한 새 상태
그다음 자신이 계산한 새 상태를 다시 root 값으로 요약합니다.
내가 계산한 새 상태
-> 내가 계산한 stateRoot
마지막으로 두 값을 비교합니다.
블록 헤더에 적힌 stateRoot
vs
내가 계산한 stateRoot
둘이 같으면, 이 블록의 트랜잭션 실행 결과가 헤더에 적힌 결과와 일치한다는 뜻입니다.
둘이 다르면, 블록 헤더에 적힌 결과와 실제 실행 결과가 맞지 않는다는 뜻입니다.
단순한 예로 보면 이렇습니다.
이전 상태:
A: 10 ETH
B: 0 ETH
블록 안 트랜잭션:
A -> B 1 ETH
순서대로 다시 계산한 결과:
A: 9 ETH
B: 1 ETH
이 결과를 root로 요약:
Root X
이때 블록 헤더의 stateRoot도 Root X라면 실행 결과가 일치합니다.
하지만 블록 헤더의 stateRoot가 Root Y라면 검증에 실패합니다.
즉 stateRoot는 상태 전체가 아니라, 상태 전체를 대표하는 요약값입니다. 노드는 상태를 직접 계산한 뒤 그 요약값이 블록 헤더의 값과 같은지 확인합니다.
트랜잭션과 계정의 연결
여기까지 연결하면 흐름은 이렇게 됩니다.
EOA가 트랜잭션에 서명한다
-> 트랜잭션이 블록에 포함된다
-> EVM이 트랜잭션을 실행한다
-> 계정 상태가 바뀐다
-> 새 stateRoot가 계산된다
-> 블록 헤더의 stateRoot와 비교할 수 있다
Account 모델을 배우는 이유는 이 연결을 이해하기 위해서입니다.
트랜잭션은 그냥 기록되는 데이터가 아닙니다. 트랜잭션은 계정 상태를 바꾸는 입력입니다.
계정은 그냥 주소 문자열이 아닙니다. 계정은 이더리움 상태를 구성하는 단위입니다.
정리
오늘 기준으로는 이렇게 정리합니다.
이더리움 Account 모델은 상태를 계정 단위로 관리하는 방식이다. EOA는 개인키로 관리되어 트랜잭션을 시작할 수 있고, Contract Account는 코드와 저장소를 가진 계정으로 호출되었을 때 실행된다.
다음에는 EOA와 Contract Account를 더 자세히 나눠서 봅니다.
참고 자료
- Ethereum.org, Accounts: https://ethereum.org/developers/docs/accounts/
- Ethereum.org, Transactions: https://ethereum.org/developers/docs/transactions/
- Ethereum.org, Ethereum Virtual Machine: https://ethereum.org/developers/docs/evm/
- Ethereum Yellow Paper: https://ethereum.github.io/yellowpaper/paper.pdf