본문 바로가기
학교공부/[블록체인]

[블록체인] - 이더리움_Accounts, Transaction, Blocks

by 윈디개 2025. 12. 12.

지난 글에서는 이더리움의 기본 개념들을 정리해 보았다. 이번 글에서는 이더리움의 Account와 Transaction, Block의 구조와 어떻게 동작하는지 한 번 정리해 보려고 한다.


1. Account

 비트코인에서는 Address라는 개념을 사용했지만, 이더리움에서는 Account 개념을 사용한다.

 

 이더리움에서는 Account에 두 종류가 존재하는데, 다음과 같다.

  • EOA(Externally Owned Accounts): ETH 보관 용도 유형의 Account, Private key에 의해 통제된다.
  • CA(Contract Accounts): Smart Contract 보유 용도 유형의 Account, 이 안에 contract 코드와 관련있는 것들에 의해 통제된다.

 두 종류의 Account에 대해서 더 자세히 정리할 예정이지만, 간단히 정리해보려고 한다.

 

 각각의 account는 20 byte 주소로 되어있고, account를 식별하기 위해 사용된다. 이 주소는 공개키에서 Keccak-256 해시를 취한 뒤, 마지막 20 byte를 사용하여 생성된다. 이는 SHA 3 계열의 해시 함수에 해당한다.

 

 또한, Contract Account는 코드를 deploy하는 트랜잭션에 의해 생성된다. CA는 생성된 이후 해당 주소로 트랜잭션(EOA의 호출 또는 다른 Contract의 invoke)이 도달하면 그 코드가 실행된다. 

 

 이더리움 Account는 다음과 같이 동작한다.

  • EOA는 트랜잭션에 private key로 서명을 하고 메시지를 보낼 수 있다.
    1. 다른 EOA에 트랜잭션을 보내거나,
    2. 다른 CA에 트랜잭션을 보낼 수 있다.
  • EOA에서 CA로 가는 메시지는 contract code를 실행시킬 수 있으며, 그 안의 다양한 동작을 수행하게 만든다.
  • CA는 처음에는 스스로 새로운 트랜잭션을 생성하여 네트워크에 전송할 수 없고 오직 EOA가 보낸 트랜잭션 또는 다른 CA가 발생시킨 message call에 의해서만 실행된다.
  • 이더리움 블록체인에서 일어나는 모든 행위는 항상 EOA가 발생시키는 트랜잭션으로부터 시작된다.
    • 즉, CA는 스스로 트랜잭션을 생성할 수 없다는 의미이다.

 어떻게 동작을 하는지 살펴보았으니 이제 더 자세한 부분에 대해서 알아보려고 한다. Account state는 개별 계정의 상태 정보(balance, storageRoot, codeHash, nonce)를 담고 있다.

  • balance: 이 account에 얼마만큼의 wei가 있는지 나타낸다. (10^18 = 1 ether)
  • StorageRoot: Merkle Patricia tree의 루트 노드 256bit짜리 해시한 값이다. 이 트리는 해당 account의 storage 내용을 담고 있으며, EOA는 storage가 없기 때문에 storageRoot는 empty를 해시한 값이 들어간다.
  • codeHash: account에 저장된 EVM 코드 해시값이다. CA에서 배포된 contract code가 해시되고 저장되고, EOA는 코드가 없기 때문에 빈 string의 해시값이 codeHash로 들어간다.
  • nonce
    • EOA: 이 계정이 지금까지 보낸 트랜잭션 수
    • CA: 이 계정이 지금까지 생성한 contract 개수
    • Nonce가 필요한 이유는 다음 예시 상황을 생각하면 된다.
    • 만약, A가 B로부터 1 ether를 빌린 후, B에게 1 ether를 보내는 트랜잭션을 발생시켰다.
    • 이때, B가 확인한 뒤, 악의적으로 한 번 더 네트워크에 전파하면 Bob은 2 ether를 받을 수 있는 double-broadcast 문제가 발생하게 된다.
    • 이런 문제를 막기 위해, Ethereum에서는 각 트랜잭션을 nonce로 구분한다. 같은 계정에서 같은 nonce를 가진 트랜잭션은 절대로 두 번 실행될 수 없으므로 위와 같은 상황을 방지할 수 있다.

+) Ethereum's global state(world state): Account 주소와 그 account states의 매핑 관계를 Merkle Patricia tree를 통해 나타낸다. Merkle Patricia tree는 다음과 그림과 같은 예시를 참고하면 이해하기 쉽다.

즉, 그림에 대해서 설명하면, 공통된 prefix를 기반으로 데이터를 정렬하는 tree 구조에, 각 노드의 해시값을 저장하는 Merkle 구조가 결합된 형태라고 이해하면 된다.

 

 Prefix 구조를 통해 문자열을 묶어 저장하여 효율적인 검색과 최소한의 키 비교로 데이터를 찾을 수 있고, 동시에 Merkle의 특징으로 인해 각 노드가 해시로 연결되어 데이터 변조를 쉽게 탐지할 수 있다.

 

 다음 그림은 Merkle Patricia Tree가 이더리움에서 저장되는 구조이다.

 

 그림에 대해서 설명하면 다음과 같다.

 

 왼쪽 그림으로 global state가 Merkle Patricia tree로 되어 있는 구조를 확인할 수 있다.

 

 오른쪽 그림은 Block 175223과 Block 175224의 state root를 나타낸 그림인데,  다음 상황을 가정한 것이다.

  • 어떤 EOA가 Account 175에 해당하는 CA를 실행시키는 트랜잭션을 발생시켰다.
  • 그 트랜잭션 실행 결과, CA의 storage 값이 바뀌면서 해당 account의 state가 변화되었다.
  • 변경된 account state는 global state에 반영되고, 그 결과 블록의 stateRoot가 변경되었다.

 그런데, 이런 트랜잭션들이 매우 많이 발생하기 때문에, 블록마다 전체 global state를 다시 계산하고 모든 노드를 새로 저장하는 것은 비효율적이다.

 

 따라서 변경된 account가 위치한 path에 해당하는 노드들만 업데이트하고, 변경되지 않은 나머지 노드들은 이전 블록의 노드를 그대로 참조하도록 구성된다. 즉, 변경된 부분만 해시를 다시 적용하고, 나머지는 재사용하여 효율적으로 stateRoot를 계산한다.

- EOA(Externally Owned Accounts)

 이제 EOA에 대해서 더 자세히 정리해 보려고 한다.

 

 EOA는 private key로부터 public key를 생성하고 그 public key로 address를 생성한다. 이는 비트코인의 address 생성 과정과 거의 동일하며, public key에 Keccak-256 해시를 취하고 마지막 20바이트를 사용하여 주소가 만들어진다.

 

 만약, Alice가 그녀의 ether를 그녀의 account로부터 Bob의 account로 보내고 싶어한다면, Alice는 transaction을 만들고 자신의 private key로 서명한 뒤 네트워크로 전파해야 한다.

 

 비트코인과 마찬가지로 Alice의 서명은 public key를 통해 검증되므로 validator는 해당 트랜잭션이 실제로 Alice가 서명한 것임을 확인할 수 있다.

 

- CA(Contract Accounts)

 다음으로는 CA이다.

 

 CA는 다음과 같이 EOA와 마찬가지로 20바이트(40자리 16진수)의 주소를 가진다.

  • 0x06012c8cf97bead5deae247070f9587f8e7a266d

 CA는 contract가 이더리움 블록체인에 배포될 때 새롭게 생성된다. 이 때 생성되는 CA 주소는 다음 두 요소로 결정된다.

  • creator(EOA)의 주소
  • creator의 nonce(해당 EOA가 지금까지 생성한 트랜잭션 수, contract 생성 트랜잭션을 보낼 때 nonce로 CA주소가 정해진다)

즉, CA 주소는 EOA account address와 nonce 조합을 Keccak-256 해시 함수를 적용해 마지막 20바이트를 주소로 사용한다.


2. Transactions

 이더리움에서의 트랜잭션을 간단하게 정의하면 다음과 같다.

  • 비트코인의 트랜잭션에서 할 수 있는 기능 + 코드 실행 기능(상태를 변화시킨다.)

 즉, 이전의 world state가 존재하고, 어떤 트랜잭션이 실행되면 그 결과로 다음 world state로 변화한다.

 

 이더리움의 트랜잭션은 다음과 같이 구성되어 있다.

  • Metadata: 트랜잭션이 블록체인에 포함되면 채워지는 정보들과 트랜잭션 자체의 기본 필드들 포함
  • Cache: 코드 실행 시 접근해야 할 storage 슬롯과 address 등을 미리 정의하여 gas 비용 절감 또는 예측을 위한 영역
  • Data: Contract에 넘겨줄 실제 파라미터들이 포함되는 영역

이때, Metadata 쪽 필드를 확인하면 다음과 같이 구성되어 있다.

  필드들에 대해서 정리하면 다음과 같다.

  • chainId ~ transactionIndex: 블록이 생성될 때 채워지는 값들로, 트랜잭션이 어느 블록 어떤 위치에 포함되어있는지 결정
  • from ~ (r,s,v): transaction을 발생시킨 account가 채워야하는 필드들
    • from ~ value: 비트코인 트랜잭션과 유사한 sender, reciever, ETH 양을 표현한다.
    • gas ~ maxPriorityFee: 트랜잭션 실행 중 얼마나 많은 gas를 사용할지, gas 가격을 얼마로 설정할지 결정하는 필드
    • (r, s, v): ESDSA 서명을 위한 필드

 이때, 서명 필드에 (r, s, v) 세 개의 값이 들어가 있다. 이는 비트코인과 조금 다른데, 그 이유는 다음고 같다.

  • 서명을 검증할 때, (r, s)를 해시한 값을 통해 public key의 후보들이 계산되어 나온다.
  • 이때, 후보들 중, 최종 어느 public key에 해당하는 값인지 알아내기 위해  v값을 추가로 사용한다.

 

 트랜잭션의 기본 구성들을 살펴보았는데, 이제는 트랜잭션이 어떻게 사용되는지 구체적으로 정리해 보려고 한다. 정리하면 다음과 같다.

  •  트랜잭션은 암호학을 적용한 서명이 필요하다.
  • EVM의 상태를 변화시킬 수 있는 트랜잭션은 전체 네트워크에 broadcast 될 필요가 있다.
  • 모든 노드가 broadcast를 할 수 있지만, validator는 이 트랜잭션들을 선택하여 EVM에서 실행하고 그 결과 상태를 블록에 기록한 뒤 나머지 네트워크에 전파한다.
  • 검증을 마친 뒤, 트랜잭션은 블록에 포함된다.

 트랜잭션을 생성할 때, 다음과 같은 정보를 포함시켜야 한다.

  • from: 트랜잭션에 서명할 계정의 주소(반드시 EOA이다. CA는 트랜잭션을 보낼 수 없기 때문이다)
  • to(recipient): 트랜잭션을 받는 주소
    • EOA라면, 트랜잭션은 ether를 보내는 트랜잭션
    • CA라면 트랜잭션은 contract를 실행시키는 것이다.
  • signature: sender의 private key로 만든 서명
  • nonce: 트랜잭션의 번호(해당 계정이 보낸 tx의 개수)
  • value: 보내는 ETH 양으로 wei 단위로 작성해야 한다.(1ETH를 보내더라도 10^18 wei로 기재해야 된다.)
  • input data: optional field
  • gasLimit: 해당 트랜잭션이 소비할 수 있는 gas의 최대치 (gas를 충분히 높게 설정하는 것이 좋다. 부족하면 트랜잭션이 실패하지만, 남으면 다시 회수할 수 있기 때문이다.)
  • maxPriorityFeePerGas: validator에게 전달되는 gas 팁
  • maxFeePerGas: gas 1단위당 ETH를 최대 얼마까지 낼 수 있는지 상한(baseFeePerGas와 maxPriorityFeePerGas를 합친 것보다 큰 값을 지정해야한다.)

 +) maxFeePerGas와 gas 개념이 등장한 이유

 maxFeePerGas 필드를 통해 이더리움에서 gas라는 개념이 등장한 이유를 설명할 수 있다. 이더리움에서는 트랜잭션을 실행하게 되면, CA의 상태가 변경될 수 있다. 즉, contract 내부의 코드를 실행시키며 상태를 변화시킬 수 있는데 만약 이 실행 비용을 ETH로 직접 지불하는 방식이라면 다음과 같은 문제가 생긴다.

 

 만약, 어제 원주율 17자리까지 계산하는 smart contract를 실행했는데 1 ETH가 들었다고 가정해보자. 오늘 다시 실행하려고 했는데 오늘의 ETH 가격이 어제보다 1.2배 상승했다면, 동일한 계산 작업을 실행하는데 어제보다 더 비싼 비용을 지불해야 하는 불합리한 상황이 발생한다.

 

 이런 문제를 해결하기 위해 이더리움은 gas라는 독립적인 단위를 도입했다.

  • gas는 연산 난이도를 측정하기 위한 고정 비용 단위로, 이더리움 프로토콜에서 opcode마다 gas cost가 정해져 있다.
  • gas의 가격(gas price)만 시장 상황에 맞게 변동된다.

 즉, 비싼 ETH 가격 변화 때문에 동일한 연산이 더 비싸지는 것을 방지하기 위해 (연산에 소비되는 양 gas) * (gas 가격) = 실제 ETH 비용으로 구조를 분리했다.

 

 ETH 가격이 20% 올랐어도 사용자는 maxFeePerGas를 낮게 조절해서 비슷한 비용으로 코드를 실행할 수 있다.

 

- 이더리움 Gas와 Payment

 Gas의 정의를 위에서 정리했으니, Payment가 어떻게 이루어지는 지 한 번 알아보겠다. 

 

 모든 트랜잭션을 실행하면서 발생하는 모든 computation은 fee를 포함해야 하고, gas라는 단위로 지불되어야 한다. Sender는 매 트랜잭션 마다 gas limit과 gas price(maxFeePerGas, maxPriorityFeePerGas)을 정해놓아야 한다. 그리고 사용되지 않은 gas는 sender에게 transaction이 끝난 후 환불된다.

+) Storage를 사용하는 경우에도 별도의 비용을 지불해야 한다.

 

 만약, 트랜잭션이 실행하다가 out of gas상태가 된다면, 트랜잭션 실행이 즉시 중지되고 지금까지 state 변화는 모두 되돌려진다. 이때 사용된 gas는 환불되지 않고 burn 된다. 이는 DoS 공격을 방지하기 위해서이다.

 

 이러한 Fee를 정해놓는 이유는 다음과 같다.

  • 네트워크의 과부하 방지
  • 공격자가 무한 loop 테스트를 하지못하도록 방지

 그럼, Smart contract마다 어떻게 가스를 계산하는지 정리해보면 다음과 같다.

  • 간단한 ETH를 송금하는 데 드는 gas 비용은 고정 21,000이다.
  • Bob이 Alice에게 1 ETH를 baseFeePerGas를 190 gwei이고 maxPriorityFeePerGas 10 gwei라면, Bob은 다음과 같은 비용을 지불해야 한다.
  • (190 + 10) * 21000 = 4,200,000 gwei
  • 이때, 4,200,000 gwei는 0.0042 ETH이다.

이 결과로 다음과 같이 account에 변화가 생긴다.

  • Bob의 account: -1.0042 ETH
  • Alice의 account: +1 ETH
  • base fee: 190 * 21000 = 0.00399 ETH
  • Validator's tip: +0.000210 ETH

+) baseFeePerGas는 이 트랜잭션이 블록에 포함되기 위해 반드시 지불해야하는 gas 1단위당 기본 가격

++) 2015년 7월에 시작한 이더리움은 2022 The merge라는 업데이트 이전에 2021년 London upgrade를 적용했다.

London Upgrade는 이전전까지 트랜잭션 실행 비용이 모두 채굴자에게 가던 구조를 바꾸어 그 중 base fee는 무조건 burn되고 오직 priority fee만 채굴자에게 가도록 업데이트 했다. 이후, 이더리움은 트랜잭션이 발생할 때마다 일정량의 ether가 자동으로 burn되는 구조가 되었고 이를 방지하기 위해 Foundation에서 조절하고 있다.

 

- Contract-related Transactions

 만약, 이더리움에 smart contract를 처음 등록하려고 하면 어떻게 될까? 이를 contract deployment transaction이라고 하는데, 이때는 트랜잭션을 address를 지정하지 않고(0x0000으로) 발생시킨다. 

 

 위의 경우를 제외하고, 이미 배포된 smart contract를 실행시키는 트랜잭션은 해당 contract의 주소를 명시하여 발생시켜야 한다.

- Transaction Lifecycle

트랜잭션이 한 번 이더리움에 제출되면 다음과 같은 과정을 거치게 된다.

  1. 트랜잭션 해시가 Keccak-256에 의해 생성된다. 이 트랜잭션 ID가 broadcast된다.
  2. 트랜잭션은 네트워크에 broadcast되고 pending transaction pool에 추가된다.
  3. validator는 트랜잭션을 블록에 포함시켜 실행한다.
  4. 블록이 만들어지고 broadcast되면 시간이 지나면서
    • 먼저 justified 상태로 업그레이드 된다. 이 상태는 2/3 이상 validator의 찬성을 받은 상태이다.
    • 그후 finalized 상태로 업그레이드 된다. 이 상태는 비트코인 6confirmation같이 더 이상 되돌릴 수 없는 상태이다.
  5. 블록이 finalized되면, 네트워크 레벨에서는 그 블록 및 블록 내 트랜잭션을 변경할 수 없다.

3. Blocks

 다음으로는 이더리움 블록에 대해서 정리해 보려고 한다.

 

 비트코인에서의 블록체인과 유사하게 이더리움 블록체인도 여러 트랜잭션들과 이전 블록의 해시값을 새로운 블록에 포함시킨다. 이를 통해 블록을 체인 형태로 연결하여 일관된 기록을 유지할 수 있다.

 

 비트코인과 동일하게 블록 구조를 사용하는 이유는 트랜잭션 단위로 모든 참여자의 상태를 즉시 sync를 맞추기 어렵기 때문에 트랜잭션을 블록 단위로 묶어서 처리하는 방식이 네트워크 전체에 대한 sync를 맞추기 쉽기 때문이다.

 

 이더리움에서는 12초마다 새로운 블록이 생성되는데, 이 값은 PoS 환견에서 각 slot마다 단 한 명의 validator가 블록을 제안하도록 설계된 시간 단위로 네트워크 참여자들이 새로운 블록을 수신하고 consensus 프로토콜을 수행하기에 충분한 시간이다. 따라서 블록 단위로 네트워크 상태를 빠르게 동기화할 수 있다.

 

 

 그럼 이더리움에서 블록은 다음과 같이 동작한다.

  • 트랜잭션 기록들을 보존하기 위해서 블록들의 순서는 엄격하게 지켜져야 한다. 모든 새로운 블록들은 이전 블록의 해시를 참조해야 한다. 
  • 네트워크의 모든 참여자(validator)는 결국 동일한 블록 순서를 공유해야 한다.
  • 랜덤하게 선택된 validator가 블록을 제안하면, 해당 블록은 나머지 네트워크 참여자들에게 전송되어야 한다.
  • 모든 노드들은 이 블록을 유효성 검증을 마친 뒤, 그들의 블록체인 끝에 추가해야 되고 이어서 다음 slot(12초)에서 새로운 validator가 다음 블록을 생성한다.
  • 이 모든 과정은 현재 이더리움의 PoS(Proof-of-stake, 지분증명) 합의 프로토콜에 의해서 수행된다.
  • 이더리움에서 validator가 되기 위해서는 일정 ETH를 예치해야 하며, 현재 validator로 참여하기 위한 예치금은 32 ETH라고 한다. 이러한 staking 구조 때문에 지분증명(PoS)라고 불린다.

- Block의 구조

 위 그림들은 이더리움 블록의 구조를 Header와 Body 단위로 나타낸 것이다. Header 구조는 기본적으로 비트코인에서의 블록체인 헤더와 비슷하지만, stateRoot와 gas 부분이 추가되었다는 것을 알 수 있다.

 

 Body 부분도 트랜잭션이 들어가는 것으로 동일한 구조를 가지고 있다는 것을 알 수 있다.

- Block Time

 다음으로는 블록 Time에 대해서 정리해보려고 한다.

 

 이더리움에서는 12초 단위의 slot이라는 시간 단위를 사용한다. 각 슬롯에서는 단 한 명의 validator가 선택되며, 오직 그 validator만 해당 slot에 블록을 제안할 수 있다. 이 slot마다 validator가 랜덤하게 선택되기 때문에 공격자가 특정 slot에서 자신이 선택될 시점을 예측하기 어렵고, 미리 여러 악의적 블록을 준비해두고 공격하는 것이 쉽지 않은 구조가 형성된다.

 

 그러나 때로는 validator가 offline 상태이거나 네트워크 사정으로 인해 블록을 제안하지 못할 수도 있는데, 이는 곧 해당 slot에는 블록이 생성되지 않고 빈 slot으로 남게 된다는 것을 의미한다. 

 

 이더리움의 평균 블록 생성 시간은 비트코인과 달리 12초로 일관적이다. 그 이유는 비트코인은 PoW에서 확률적으로 블록을 채굴하는 구조인데 반해, 이더리움은 PoS 기반으로 slot마다 정해진 validator가 블록을 제안하는 방식이기 때문이다. 따라서 평균적으로 12초의 slot time을 매우 안정적으로 유지할 수 있다.

- Block Size

 다음으로는 이더리움의 블록 사이즈에 대해서 정리해 보려고 한다.

 

 이더리움도 비트코인과 동일하게 사이즈의 제한이 존재한다. 그렇지만, 비트코인의 byte 단위로 블록 크기를 제한하는 것과 달리 이더리움은 gas 단위로 블록 크기를 제한한다.

 

 이더리움의 gas 제한은 1500만 gas로, 네트워크에 따라 사이즈 제한은 최대 3000만 까지 증가하거나 감소할 수 있다. 이유는 앞서 12초 내에 블록이 생성되지 못한 경우, 그 블록에 포함되려고 했던 트랜잭션과 현재 블록에 들어가려는 트랜잭션의 가스 양을 합쳐야 하는 경우가 발생할 수 있기 때문이다. 

 

 블록에 포함되는 트랜잭션들의 가스 총량은 블록의 가스 총량보다 당연히 낮아야 하며, 블록 크기가 제한 없이 증가할 수 있다고 가정하면 모든 노드들이 해당 블록안의 트랜잭션을 수행하는데 시간이 오래 걸리게 되므로 네트워크 동기화 속도가 크게 느려지는 문제가 생기게 된다.