1. 계정 기반 모델 (Account Based Model)

  • 계정(Account): 이더리움은 UTXO 모델이 아닌 ‘Account 계정’ 개념을 사용한다. 크게 Extrnally Owned Account ( EOA )와 Contract Account로 구분된다.
    • EOA : 일반 사용자가 개인 키를 통해서 제어하는 계정이다. 거래 발생 시 서명이 필수며, 보유한 이더(ETH) 잔액도 존재한다.
    • Contract Account: Smart Contract가 배포된 게정이다. 코드를 담고 있으며, 사용자의 트랜잭션이나 다른 컨트랙트로부터의 호출에 따라 EVM 코드가 실행된다.

UTXO Model이란?

UTXO ( Unspent Transaction Output ) Model은 비트코인(BTC)에서 사용하는 모델로, 각 코인이 어디서 왔고, 어디로 가는지를 ‘트랜잭션 출력(Output)’으로 추적하는 방식이다.

  • 트랜잭션은 여러 입력(Input)과 출력(Output)을 가질 수 있다.
  • 입력(Input): 이전 트랜잭션의 미사용 출력(UTXO)를 참조
  • 출력(Output): 새로운 소유권을 명시 ( 예: 어떤 주소가 얼마만큼의 비트코인을 얻게 되는지)
  • 이때, 잔액이라는 개념은 단순히 “아직 쓰이지 않은 트랜잭션 출력(UTXO)의 총합”이다.
  • 장점
    • 각 트랜잭션 자체가 독립적으로 유효성이 검증됨(해당 UTXO가 실제 존재하고 서명이 올바른가 확인)
    • Double Spending(이중 지불)을 체크하기가 쉬움 ( 동일한 UTXO가 다시 사용되면 무효 )
  • 단점
    • UTXO가 많아지면 관리가 복잡해짐
    • Smart Contract를 다루기엔 Account-based Model보다 직관성이 떨어질 수 있음

  • 계정 상태: 각 계정에는 nonce, balance, storage, code가 존재하며, 전역 state는 World State를 이진 트라이(Merkle Patricia Trie)로 관리한다.

State란?

  • State는 특정 시점에서 각 계정(Account)이 보유한 데이터(잔액, nonce, storage, code etc..)을 총칭하는 말이다.
    • EOA의 경우: nonce, balance
    • Contract Account의 경우: nonce, balance, storage, code

트랜잭션 실행이나 블록이 채굴(또는 검증)될 때마다 상태가 갱신된다.

World State란?

  • World State는 이더리움 전체 네트워크에 존재하는 모든 계정의 상태를 합친 거대한 맵(또는 자료 구조)이다.
    • 이 World State는 머클 패트리샤 트라이(MPT) 형태로 저장되며, 무결성과 변경 내역을 추적할 수 있게 한다.
    • 흔히 상태 루트(stateRoot)라는 해시값으로 대표된다. 블록마다 해당 stateRoot가 박혀있어 ‘이 블록 시점에 전 세계 계정들의 상태가 이것이 맞다’라고 인증하는 역할이다.

정리하자면 State는 각 계정의 스냅샷, World State는 그 모든 계정 스냅샷을 합친 것이며, 블록체인은 매 블록마다 World State가 어떻게 달라지는지를 기록한다.


Key-Value DB란?

  • Key Value DB : 각 계정의 상태 정보를 저장하는 구조이다. 해당 구조에서 각 계정의 32 byte address를 고유한 Key로 사용하고, 상태 객체( State Object )를 값으로 저장한다.

해당 구조는 문제점이 존재한다. 24년 4월 기준 Ethreum의 Account는 약 2억 6천만개이다. 간단한 Key-Value 구조는 네트워크의 규모가 확장됨에 따라 이러한 대규모 데이터를 처리하는 느린 탐색 속도를 보여준다. 느린 탐색 속도는 트랜잭션의 속도를 저하시키고, 이는 곧 네트워크 전체의 성능에 영향을 미친다. 따라서 이더리움은 MPT라는 효율적인 데이터 구조와 알고리즘을 사용한다.


MPT ( Merkle Patricia Trie )

정리해보면, 이더리움은 하나의 state machine이며, 트랜잭션은 state를 변경한다. 이 state는 key-value pair로 표현된다. key-value pair를 저장하는 방법은 여러가지 있지만 이더리움은 Modified Merkle Patricia Trie ( MPT )라는 특수한 방법을 사용한다.

MPT는 기본적으로 Patricia trie와 Merkle Tree를 합친 것이며, ethreum의 특성에 맞게 최적화를 추가적으로 하였다.

Trie, Patricia Trie란?

 

위 사진은 Tree 구조이다. 탐색 Tree는 데이터를 효과적으로 검색하는 방법 중 하나이다. 그 중 이진 탐색 트리는 흔한 형태 중 하나이다.

 

 

Trie는 키가 문자열(address)인 경우 효율적인 검색을 제공하는 트리 기반 데이터 구조이다. Trie에서는 각 노드가 키의 한 문자를 표현하며, 루트에서 노드까지의 경로를 전체 키를 나타낸다.

 

 

Patricia trie는 Prefix tree, radix tree, trie 등 다양한 이름으로 불린다. 이는 기존 Trie의 메모리 효율을 개선한 데이터 구조로 경로 압축( Path Compression )을 통해 메모리 사용량을 줄인다. Patricia Trie는 path에 key를 집어넣어 공통된 prefix를 가지는 노드들은 같은 path를 가진다는 특성을 이용한다. 이러한 공통 prefix를 찾는 방법이 가장 빠르고 적은 메모리를 사용하며 구현도 간단하기에 router 등 낮은 사양의 기계에 들어가는 routing table 등에서 사용되기도 한다.

실제로 address들은 많은 공통 prefix를 가지기에 기존 Trie의 단점인 메모리 사용량을 크게 줄이면서 효율적인 검색까지 가능하다.


Merkle Tree란?

 

 

위에서 Trie는 효율적인 저장과 처리가 목적이였다면 Merkle Tree는 위변조 방지가 목적이다.

Merkle Tree는 hash들의 tree이다. Leaf 노드에는 데이터를 보관하며, leaf의 부모는 leaf의 hash를 가지고, 그 부모는 자식들의 hash의 합을 다시 hash한 값을 가진다. 이는 leaf 노드를 제외한 노드들은 모두 hash를 가지고 있기에 hash tree라고도 불린다.

이를 사용하면, 서로 다른 2개의 노드가 같은 데이터를 가졌는지 효율적으로 비교할 수 있다. 예를 들어, L1, L2, L3, L4가 있을 때 같은 Top Hash를 가졌는가만 따지면 된다. 만약 Top Hash가 다르고, 어떤 데이터가 다른지 알고 싶다면, 바로 자식인 Hash 0과 Hash 1을 비교하고, 둘 중 다른 브랜치의 hash를 비교해나가면서 어떤 데이터가 다른지 검증할 수 있다.

Light Client란?

  • Full Node는 모든 블록 트랜잭션, 상태(World State) 전체를 저장하고 검증하기에 용량이 크고 동기화 시간이 오래 걸린다.
  • Light Client는 블록 헤더만 내려받고, 필요한 상태 정보를 머클 증명(Merkle Proof)를 통해 검증하기에 리소스를 크게 절약할 수 있다.
  • 장점
    • 디스크/메모리 사용량이 훨씬 적고, 동기화(sync)가 빠르다.
    • 스마트폰, 임베디드 장치 등 자원 한정적 환경에서 쉽게 구축 가능
  • 단점
    • 매번 특정 계정/트랜잭션 상태를 확인할 때, 풀 노드에게 머클 증명을 요청해야하므로 네트워크 의존도가 높다.
    • 100% 스스로 검증을 못하기에, 신뢰 모델이 풀 노드에 어느정도 의존한다.
  • 악의적인 노드가 해당 Merkle Tree Root에 존재하지 않는 Transaction을 위조하여 생성하고자 한다면, 정확히 같은 Root를 뽑아내는 Data를 찾아내야 한다.

Merkle Patricia Trie

 

 

  • Patricia Trie는 키의 공통 prefix를 기반으로 데이터를 효율적으로 저장 및 검색을 하는 구조이고, Merkle Tree는 데이터 무결성 검증에는 유용하지만, 키 기반 검색 및 데이터 저장에는 Patricia Trie만큼 효율적이지 않다.
  • MPT는 두 구조의 장점을 합친것이다.
  • MPT는 Merkle Tree처럼 각 노드가 hash를 가지며, 이는 노드 내용의 sha3 hash로 결정된다.
  • hash는 노드를 지칭하는 key로도 사용된다.
  • geth에서는 leveldb를, parity에서는 rocksdb라는 key-value storage에 state를 저장한다.
  • 이때 storage에 저장되는 key-value는 ethereum state의 key-value가 아니라 각각 노드의 hash와 MPT 노드 내용이다.
  • ethereum-state의 key는 MPT에서 path로 사용된다.
  • MPT에서 key가 같은지 비교하는 단위는 nibble이다.
    • 하나의 node에서 최대 16개의 branch를 가질 수 있다.
  • 노드도 값을 가지기 때문에, 16개의 branch와 값을 합쳐 17개의 아이템을 가진 배열이 branch node가 된다.
  • 아래로 자식이 없는 노드는 leaf node라 불리며, 자신의 path와 value 2개의 아이템으로 이루어진 배열이다.
    • 예를 들어 “0xBEA”라는 키에 1000이 들어있고, “0xBEE”라는 키에 2000이 들어있다고 생각해보자. 그렇다면 “0xBE”를 path로 가지는 branch node가 있고, 그 아래 “0xA”와 “0xE”를 path로 가지는 2개의 leaf node가 붙는다.

  • 이 외에 extension node라고 있는데 이는 branch node의 최적화이다.
    • Ethereum에서 state는 하나의 branch node가 하나의 자식만 가지는 경우가 많기에 MPT는 하나의 자식만 가지는 branch node를 path와 자식의 hash를 가지는 extension node로 압축한다.
  • 이때 2개 node ( leaf node, extension node ) 모두 2개의 아이템을 가진 배열이기에 구분할 방법이 필요하다.
  • 그래서 MPT는 path에 prefix를 붙인다.
    • 만약 node가 leaf node고 path가 짝수개의 nibble로 구성돼 있으면 0x20을 붙이며 홀수개의 nibble로 구성되어 있어면 0x3을 붙인다.
    • extension node의 경우 짝수면 0x00, 홀수면 0x1을 prefix로 붙인다.
  • 짝수면 2개의 nibble을 prefix로, 홀수면 1개를 붙이기 때문에 path는 항상 byte로 표현된다.

2. 합의 알고리즘과 네트워크 구조

PoW에서 PoS로의 전환

  • PoW(Proof of Work): 초기 이더리움(ETH1)은 PoW 기반으로, 난이도 목표를 만족하는 해시를 찾는 과정을 통해 블록을 생성했다.
  • PoS(Proof of Stake): 2022년 9월 ‘Merge’ 업그레이드를 통해 PoW에서 PoS로 전환되었다. 이제 이더를 스테이킹한 검증인(Validator)이 블록 생성 및 검증에 참여한다.
    • Beacon Chain: PoS 검증인들의 스테이킹, 보상/벌칙 분배, 랜덤 시드 생성을 담당하는 체인.
    • 검증인(Validator): 32 ETH를 예치하여 활성 검증인 세트에 등록되며, 새로운 블록을 제안하고 서명(Attestation)에 참여한다
    • 슬래싱(Slashing): 이중 서명이나 악의적 행위를 할 경우 예치금이 일부 혹은 전부 소각되는 처벌 메커니즘이다.

Validator

  • PoS(Proof of Stake) 체제에서, 블록을 만들어(제안) 네트워크에 내놓는 주체를 “검증인(Validator)”라고 부른다.
  • 검증인은 32 ETH를 예치(stake)해야 활성 검증인 세트에 들어갈 자격이 생긴다.
  • 검증이라는 행위는 크게 두 가지 의미가 있다.
    1. 블록 제안(Block Proposal): 라운드(슬롯)마다 무작위로 선정된 검증인이 새 블록을 만들어 네트워크에 전파
    2. 투표(Attestation): 다른 검증인들은 제안된 블록이 유효한지 확인하고, 서명(투표)을 통해 “이 블록이 맞다”고 지지함
  • 왜 새 블록을 제안하는가?
    • 블록을 제안해야 체인이 연속적으로 이어지며 트랜잭션이 처리됨.
    • 이를 통해 트랜잭션이 최종 확정(Finality)되면서, 네트워크가 합의에 도달함.
    • 검증인들은 제안/투표에 참여해 보상을 받거나(정상 동작) 처벌(슬래싱)을 당할 수 있음(규칙 위반 시)

Beacon Chain과 다른 체인

  • Beacon Chain: PoS 이더리움의 핵심. 검증인들의 스테이킹, 무작위 시드 생성, 블록 제안자 선정 등을 처리하는 체인.
  • Execution Layer(이전 PoW 체인): 실제로 EVM 트랜잭션이 실행되는 “실행 레이어(Execution Layer)” 체인이 있습니다. Merge 이후에도 이 체인은 계속 존재하며, 우리가 흔히 말하는 ‘이더리움의 상태’(account 상태, 트랜잭션)는 이 실행 레이어에 기록된다.
  • 상호작용:
    • Beacon Chain은 Validator를 관리하고, 제안자 선택/투표 절차(합의 레이어)를 담당.
    • Execution Layer는 스마트 컨트랙트 실행, 계정 상태 변경 등(실행 레이어)을 담당.
    • 두 체인은 서로 블록 헤더를 참조하고, 최종 합의 여부 등을 교환한다.

네트워크 계층

  • P2P 네트워크: 이더리움 노드는 Kademlia 기반의 DHT(Distributed Hash Table)를 변형해 사용하며, 트랜잭션과 블록을 전파한다.
  • 블록체인 구조: 각 블록은 이전 블록 해시, 트랜잭션 목록, 상태 루트, 트랜잭션 루트, 수수료 수익 등 메타데이터를 포함한다.

Kademlia란?

  • Kademlia는 P2P 네트워크에서 노드를 효율적으로 찾고 라우팅하는 알고리즘(프로토콜)이다.
  • 노드들은 “Kademlia 주소 공간”에서 자신의 Node ID(해시) 기반으로 위치를 결정하고, 거리(metric) 개념을 사용해 근접한 노드를 찾거나 라우팅합니다.
    • 예: XOR 거리. 두 노드의 ID를 XOR 연산했을 때 결과값이 작을수록 가까운 노드로 간주.

DHT(Distributed Hash Table)란?

  • 분산 해시 테이블: ‘Key-Value’ pair를 Worldwide 노드에 분산 저장하고, 특정 키에 대응하는 값을 효율적으로 조회할 수 있게 해주는 구조.
  • 이더리움 노드들은 Kademlia 기반 DHT를 사용해 “내가 연결해야 할 피어(노드)는 어디 있는가?”를 찾고, 트랜잭션·블록 등을 전파합니다.

Kademlia DHT란?

  • DHT(Distributed Hash Table): 네트워크에 참여하는 여러 노드가 키–값(key-value)을 분산 저장하여, 필요한 정보를 빠르게 조회할 수 있도록 만든 자료구조/프로토콜이다.
  • Kademlia는 여러 DHT 구현 중 하나로, 기존의 다른 DHT들이 동시에 제공하지 못했던 여러 장점을 가지는 것으로 알려져 있다.
  • (1) 노드 간에 교환되는 설정 메시지(configuration message) 수가 최소화된다.
  • (2) 이미 “검색(query)” 중에 얻게 되는 정보(노드 주소 등)가 자연스럽게 구성(학습)된다.
  • (3) 노드는 저지연 경로(low-latency path)를 활용해 쿼리를 라우팅할 수 있다.
  • (4) 장애가 발생한 노드(죽은 노드)를 빠르게 회피하기 위해 병렬·비동기 쿼리를 사용한다.
  • (5) 병렬·비동기 처리는 서비스 거부 공격(DoS/DDoS)이나 네트워크 장애에 좀 더 강인하다.
  • (6) uptime 분포(노드 가동 시간)에 대한 약한 가정(weak assumptions on updime distributions)을 통해, 특정 노드들이 오래 살아 있는 안정된 네트워크를 선호한다.

정리하면 Kademlia는 노드 식별자(Node ID)를 이용해 근접성(closeness)을 정의하고, 그 근접성에 따라 키–값 쌍을 적절히 분산 저장/검색하여, 네트워크 규모가 크더라도 효율적인 조회를 가능하게 하는 DHT 프로토콜이다.

추가 : 병렬 비동기 쿼리가 장애 노드 및 DoS에 강한 이유

  1. 장애 노드 ( 죽은 노드 ) 회피
  • 동기(Synchronous) 쿼리 방식이라면, 어떤 노드에게 요청을 보내었을 때 응답이 없으면 일정 시간( timeout ) 동안 기다려야 하므로, 전체 탐색 과정이 지연됨.
  • 병렬 비동기 ( parallel/asynchronous ) 쿼리를 쓰면 여러 노드에게 동시에 요청을 보내고, 응답이 오는 대로 살아 있는 노드와의 통신을 계속 진행할 수 있다.
    • 예시 : 노드 A, B, C에게 동시에 FIND_NODE 요청 → A가 죽었다면 응답이 안 올 뿐, B, C가 응답을 주므로 빠르게 다음 단계로 넘어감
    • 결과적으로 장애 노드를 빠르게 배제하고, 탐색 지연을 최소화할 수 있다.
  1. Dos/DDos 방어
  • 공격자가 특정 노드에 트래픽 집중(DoS)을 유발하거나, 거짓 응답을 유도한다 해도, 병렬 쿼리를 통해 여러 후보 노드 중 일부만 정상 동작해도 탐색이 이어질 수 있다.
  • 공격자가 모든 경로(노드)에 동시에 공격을 가하지 않는 이상, 병렬 비동기 로직을 통해 전체 네트워크가 완전히 마비되는 위험을 줄일 수 있다.
  • 또한, Kadmlia는 노드가 응답을 주지 않으면 해당 노드를 k-버킷에서 빠르게 제거 또는 후순위로 밀어내기때문에, 공격 노드를 빠르게 ‘신뢰도 낮은’ 상태로 두고 대체 노드를 이용할 수 있다.

추가: 약한 가정( weak assumptions on uptime distributions )란?

언급된 ‘약한 가정(weak assumption)은 아래와 같다.

  • 노드들이 일정 수준 이상으로 온라인 상태를 유지할 것이다.
  • 완벽하게 영원히 살아 있는 노드는 없지만, 일정 확률로 오래 접속한 노드일수록 계속 살아 있을 가능성이 크다.

등과 같은 강하지 않은( 절대적이지 않은 ) 통계적 및 확률적 전제 조건을 의미한다.

즉, 네트워크 운영 시 ‘어느 정도 노드들이 안정적으로 접속해 있을 것’이라고 가정하는데, 이를 약한 가정이라고 본다. 완전히 신뢰할 수 있는 것은 아니지만, p2p 환경에서 충분히 현실적인 전제라는 의미이다. Kademlia는 이런 수준의 가정으로도 효율적으로 동작하게 설게되었다.

Kademlia의 노드 ID와 키–값 저장

Node ID (160비트 공간)

  • 참여하는 각 노드는 160비트 길이의 Node ID를 가진다
  • 키(key) 또한 160비트라고 생각할 수 있으며, “어떤 키를 저장할 때, 그 키와 가장 가까운(근접한) Node ID를 가진 노드들에 저장한다”는 것이 핵심입니다.

근접성 개념

  • “키(예: Key1)를 갖는 노드 10개 중 1개가 죽으면, 그 다음으로 가까운 노드에 저장된다”고 비유적으로 설명할 수 있다.
  • 거리(metric)는 보통 XOR 거리를 사용한다. 두 노드(또는 키) ID를 XOR 연산했을 때 결과값이 작을수록 ‘가깝다’고 판단한다.

Node-ID 기반 라우팅 알고리즘

  • Kademlia는 노드들이 서로 ‘k-버킷(k-bucket)’이라는 구조에 다른 노드들의 ID, IP, 포트 정보를 저장해 둔다.
  • 검색할 때는 “타겟 키에 가장 가까운 노드(현재 알고 있는 노드 중)”부터 차례대로 질의(query)를 보내고, 응답받은 노드들이 알고 있는 더 가까운 노드 정보를 또 받아서, 점점 타겟 키에 가까워지는 방식이다.

‘Shortest Unique Prefix’ 개념과 이진 트리 예시

Shortest Unique Prefix

Input: [Zebra, dog, duck, dove]
Output: {z, dog, du, duv}

 

 

  • 예: ["Zebra", "dog", "duck", "dove"]일 때,
    • “Zebra”는 접두사 z만으로도 충분(“dog”나 “duck”과 겹칠 일이 없음).
    • “dog”는 dog 자체가 접두사(“duck”이나 “dove”와 달라야 하므로).
    • “duck”은 du로는 “duck/dove” 구분이 불가능하니 duc 혹은 duck까지 가야 함(문맥에 따라 다름).
    • “dove”는 dov 혹은 dove가 필요.
  • Kademlia에서 노드 ID의 이진 표현을 트리 형태로 가정했을 때, 루트에서부터 (0/1) 어느 방향으로 가야 특정 노드에 도달하는지를 찾는 개념이 이와 유사하다고 보시면 된다.

Kademlia 이진 트리

  • 루트에서 시작해 비트가 0이면 왼쪽, 1이면 오른쪽 식으로 분기되어 노드의 위치가 결정된다고 볼 수 있다.
    • 예시 ) 어떤 노드의 ID가 00111... ( 이진 형식 )이라면, 트리의 루트에서
      1. 1 번째 비트 → 0이면 왼쪽
      2. 두 번째 비트 → 0이므로 또 왼쪽
      3. 1이므로 오른쪽
      4. 1이므로 오른쪽
      • 이런 식으로 내려가면 해당 노드의 위치가 정해진다.
  • Kademlia에서 중요한 특징은 자신이 속하지 않은 서브 트리에 대해서도 적어도 하나의 노드를 알고 있어야 한다 이다.
    • 그래서 “모든 노드는 필요 시 상대방 노드를 XOR 거리 기준으로 찾을 수 있다”는 것이 가능해진다.
    • 예시: 노드가 00111에 속해 있다면, ‘00111과 전혀 겹치지 않는 prefix(예시: 1, 01, 000 … ) 쪽 서브 트리에 존재하는 노드 정보도 k-버킷을 통해 일부 가지고 있어야 한다.
  • 이렇게 하면 어느 경로(접두사 prefix) 쪽으로 탐색을 가야 할 때도, 그쪽 분기를 대표하는 노드 정보가 있으므로 그곳에 질의를 보낼 수 있다.
  • 결과적으로 “루트에서부터 어느 비트가 다른지”를 따라가면서, 타겟 노드 ID와 XOR 거리가 점차 줄어드는 방향으로 검색을 이어갈 수 있게 된다.

추가: K-Bucket이란?

  • k-bucket은 Kademlia 노드가 유지하는 데이터 구조로, “서로 다른 XOR 거리 범위”마다, 그 범위 안의 노드 정보를 (최대 k개) 리스트로 보관한다.
    • 예시: 160 비트 기준 0 ≤ distance < 2^0, 2^0 ≤ distance < 2^1, ..., 2^(n-1) ≤ distance < 2^n 같은 식으로 구간을 나누고, 각 구간마다 최대 k개의 노드를 저장
    • 일반적으로 k값은 20 정도로 잡는다고 알려져 있다.

동작 방식

  • 새 노드를 알게 되었을 때: 해당 노드와의 XOR 거리에 따라 적절한 k-버킷에 넣는다.
  • 버킷이 가득 찼을 때: 가장 오래 응답이 없는(혹은 오래 접속하지 않은) 노드를 내보내고(또는 뒤로 밀고), 새 노드 등록
  • 이렇게 하면 노드 A가 자신의 k-버킷에 “XOR 거리별로 고르게 분포된 노드 정보를 유지”하게 되고,
    • 어떤 키 또는 노드 ID를 찾고자 할 때 “현재 XOR 거리상 가장 가까운 노드에게 물어본 뒤 → 응답 받은 더 가까운 노드에게 물어보는” 과정을 반복해 효율적으로 탐색할 수 있다.

RPC 메시지 흐름과 탐색 과정

  1. 노드 A는 “타겟 노드 ID(혹은 키) α”를 찾고자 할 때, 먼저 자기 k-버킷에서 가장 가까운 노드 여러 곳(예: 3개)에 요청을 보냄.
  2. 응답 노드는 “나는 직접 타겟 α를 모르지만, 대신 α에 더 가까운(또는 근접한) Node IDs”를 알려준다.
  3. 노드 A는 새로 얻은 노드들에게 계속해서 질의.
  4. 이 과정을 반복하면, 타겟에 점점 더 근접한 노드를 찾거나, 실제 타겟 노드가 응답을 주는 지점에 도달함.
  5. 각 쿼리는 비동기적으로 날아가고, 장애 노드는 응답을 주지 않으므로 빠르게 제외.
  6. 최종적으로 O(log N) (N은 네트워크 노드 수, 2^n 규모) 정도의 탐색 단계 안에 타겟 노드 또는 키-값에 닿을 수 있게 된다.

이 때, 다음과 같은 RPC 동작이 일어난다:

  • PING / PONG: 상대 노드가 살아 있는지 확인.
  • STORE: key–value 쌍을 저장하도록 요청.
  • FIND_NODE: 특정 Node ID와 XOR 거리가 가까운 노드를 알려 달라고 요청.
  • FIND_VALUE: 특정 key에 대응하는 value(또는 그 key에 가장 가까운 노드)를 알려 달라고 요청.

5. Kademlia의 실제 적용 예시 & 변형

  1. BitTorrent에서 수천만 노드가 사용하는 DHT가 Kademlia 계열 프로토콜을 기반으로 동작.
  2. Coral DSHT는 Kademlia를 확장한 형태로, 키 자체를 저장하기보다는 “파일을 제공할 수 있는 피어의 주소 목록”만 저장. 지역성(locality) 등을 활용.
  3. S/Kademlia(Secure Kademlia): Node ID 생성 과정에 PKI를 활용해서 시빌 공격(Sybil Attack)을 방어하고, 노드가 자신의 메시지를 서명하도록 요구하는 등의 강화책을 도입.

추가: Sybil Attack (시빌 공격)

  • 시빌 공격은 하나의 물리적 주체(공격자)가 다수의 가짜 ID(다른 노드 ID)를 만들어 네트워크에 참여, 투표권이나 영향력을 부풀리는 공격 기법을 말한다.
  • P2P 환경에서 공격자가 “수십, 수백 개의 노드 ID”를 생성해 특정 키 범위를 장악하거나, 잘못된 정보를 퍼뜨리는 식으로 문제를 일으킬 수 있다.

추가: PKI(Public Key Infrastructure)

  • 공개 키 기반 구조: 노드 ID를 개인 키/공개 키를 통해 서명·인증받도록 하는 시스템이다.
    • 예: 노드가 자신의 Node ID를 만들 때, 그에 대응하는 공개 키(공개키 해시 등)를 ID로 삼고, 통신 시 메시지에 서명하여 “내가 진짜 이 공개 키의 소유자임”을 증명.
  • Kademlia의 Secure 확장판(S/Kademlia)에서는 노드가 무작위로 Node ID를 막 만드는 것이 아니라, 정당한 인증서 또는 서명이 있어야 유효한 노드로 인정받도록 설계하여, 시빌 공격을 어렵게 만든다.
    • 완벽 차단은 아니더라도, 노드 ID를 대규모로 생성하려면 PKI 인프라가 요구하는 비용·절차가 늘어나므로 공격 난이도가 상승함

Sync(동기화)

노드 동기화란?

이더리움 네트워크에 새로 참여한 노드는 현재까지 생성된 블록블록에 포함된 상태다운로드 및 검증해야 합니다. 이를 노드 동기화(Sync)라고 부르며, 노드는 동기화를 마친 뒤에야 메인넷과 동일한 상태를 유지하면서 트랜잭션을 처리할 수 있게 됩니다.

Geth 기준 동기화 모드

  1. Full Sync(고전적 풀 동기화)
    • 블록 #0(제네시스)부터 모든 블록을 순서대로 다운로드하고,
    • 각 블록의 트랜잭션을 실제로 실행(EVM)해 보면서 World State(MPT)를 재구성.
    • 이 방식은 시간이 오래 걸리고 디스크 사용량도 많지만, 완전한 재현성을 가진다
  2. Fast Sync(패스트 동기화)
    • 블록 헤더와 트랜잭션만 빠르게 다운로드하고,
    • 최신 블록 근처에서 최신 상태(머클 루트)를 통째로 전송받아 재구성.
    • 이후 트랜잭션 검증은 최신 블록까지만 빠르게 검증하고, 이전 블록들은 헤더만 대조(또는 일부 샘플링)합니다.
    • 블록 전체를 재현하는 대신, 합의가 끝난 블록에 대한 최종 상태를 신뢰하면서 시간을 크게 단축합니다.
  3. Snap Sync(스냅샷 동기화)
    • Geth 1.10.x 이후 도입된 방식으로, 상태 스냅샷(계정·스토리지 정보)을 ‘스냅샷’ 형태로 빠르게 전송받아 재구성.
    • 이전 블록은 최소한으로만 체크하고, 최신 상태를 최대한 빠르게 합쳐 가는 최적화 기법입니다.
    • Fast Sync보다도 더 빠른 시간 안에 동기화를 완료할 수 있음.
  4. Checkpoint Sync (상황에 따라)
    • 특정 체인 분기(체크포인트) 이상을 신뢰 기반으로 받아들이고, 그 이후 블록만 집중적으로 동기화.
    • PoS 전환 후에는 검증인들의 최종성(finalized block) 지점 등을 체크포인트로 삼아 동기화 시간이 단축될 수 있습니다.

동기화 과정 개요

  • Geth 노드는 부트노드(bootnode) 목록에서 첫 연결을 맺고,
  • 이웃(peer) 노드들로부터 “체인 헤더”, “블록 데이터”, “상태 트리 일부” 등을 병렬로 수신.
  • 일정 시점(최신 블록)에 도달하면, 이후에는 실시간으로 새 블록을 받아 검증하고 메인넷과 보조를 맞추게 됩니다.

Fork(포크)

임시 포크(체인 분기)

블록체인은 동시에 2개 이상의 새 블록이 전파될 수 있다. 예를 들어, 서로 다른 검증인(또는 채굴자)이 같은 높이(Block Height)의 블록을 거의 동시에 만들면 네트워크상에서 잠시 **분기(Fork)**가 생긴다.

  • PoW 시절에는 “가장 길고(또는 난이도가 높은) 유효 체인”을 정본으로 채택.
  • PoS 시절에는 검증인들의 투표(Attestation) 결과로 체인이 합의에 도달하며, 어느 블록이 다수의 지지를 얻었는지(또는 더 많은 attestations를 모았는지)에 따라 한쪽 분기가 살아남았다.

체인 선택 규칙

  • PoS(BEACON CHAIN): 블록 제안 → 여러 검증인의 서명(Attestation)으로 지지받은 체인이 점차 높은 확률로 정본이 됨.
  • 일시적 분기는 보통 짧은 시간(1~2슬롯) 안에 해소되고, 한쪽 블록이 고립(uncle/orphan) 처리된다.

하드포크 vs 소프트포크

  • 하드포크(Hard Fork): 프로토콜(합의 규칙, EVM 규칙 등)이 바뀌어 이전 버전 노드와 호환 불가능. 예) The DAO 사건, Constantinople 업그레이드 등.
  • 소프트포크(Soft Fork): 호환 범위 내에서 규칙을 제한하거나 변경하는 방식으로, 이전 노드와도 어느 정도 호환.

Finality(최종성)

PoS에서의 최종성

  • **Finality(최종성)**이란 “한 번 블록이 확정되면, 경제적으로나 기술적으로나 되돌릴 수 없는 상태”를 말한다.
  • PoW에서는 절대적 “최종성”이 존재하지 않고, 확률적으로 긴 체인이 뒤늦게 나타날 가능성이 작아지는 것에 의존했다(6 confirmations 등).
  • PoS 이더리움에서는 Beacon Chain의 합의 알고리즘(예: Gasper, 현재 LMD-GHOST + Finality Gadget)으로 “epoch 단위”로 블록을 투표(attestation)하고, **수퍼다수(Supermajority)**가 동의하면 “최종화(Finalized Block)”로 간주한다.

Epoch, Checkpoint

  • 한 에폭(epoch)은 여러 슬롯(slot)으로 구성되며(보통 32 슬롯 = 6.4분), 검증인들이 각 슬롯마다 블록을 제안·투표한다.
  • 체크포인트(Checkpoint): 각 에폭의 첫 번째 블록을 기준으로, 해당 에폭이 끝난 후 충분한 투표가 모이면 그 체크포인트가 “정말로 확정”된다.
  • 이렇게 체인이 최종화된다는 것은, 그 시점 이전 블록들을 되돌리면 스테이킹 된 예치금이 슬래싱되어 막대한 경제적 피해가 발생하므로, 네트워크상에서 사실상 불가능에 가깝다.

Reorg(재조직) 한계

  • PoS 체제에서는 “Finalized” 블록 이전 구간을 되돌릴 경우, 대규모 슬래싱이 발생하므로 실용적으로 불가능하다.
  • 반면, 아직 최종성이 부여되지 않은 “head(최신) 블록” 근방에서는, **임시 포크(짧은 reorg)**가 일어날 수 있습니다만, 보통 몇 개의 슬롯을 넘어서지 않는다.

 

참조

eth는 RLPx 전송에서 실행되는 프로토콜로, 피어 간에 이더리움 블록체인 정보를 교환하는 기능을 합니다. 현재 프로토콜 버전은 eth/68이므로 이를 기준으로 분석한 문서이다. devp2p를 보고 정리했다.


Basic Operation


연결 설정: 연결이 설정되면 Status 메시지를 전송해야 하며, 상대 피어로부터 Status 메시지를 수신하면 이더리움 세션이 활성화됩니다. 이후 다른 모든 메시지를 보낼 수 있습니다.

세션 내 작업 : 세션 내에서 세 가지 high-level task(chain synchronization, block propagation and transaction exchange)가 수행됩니다. 각 작업은 별도의 프로토콜 메시지 집합(disjoint sets)를 사용하며, 클라이언트는 모든 피어 연결에서 이 작업들을 동시에 수행합니다.

프로토콜 메시지 크기 제한 : 클라이언트는 프로토콜 메시지 크기에 대한 제한을 적용해야 합니다. RLPx 전송에서는 단일 메시지 크기를 16.7MiB로 제한하지만, 실제로 eth 프로토콜에서는 보통 10MiB로 제한됩니다. 수신된 메시지가 이 한계를 초과하면 피어 연결을 끊어야 합니다.

소프트 한계 : 수신된 메시지에 대한 강력한 제한 외에도, 클라이언트는 전송하는 요청과 응답에 대해 '소프트' 한계를 설정해야 합니다. 메시지 유형에 따라 권장되는 소프트 한계가 다릅니다. 이러한 한계는 블록 동기화와 트랜잭션 교환이 동일한 피어 연결에서 원활하게 이루어지도록 돕습니다.

그럼 이제 3가지 High-Level task들을 살펴보자


Chain Synchronization


  • eth 프로토콜에 참여하는 노드는 제네시스 블록부터 현재 최신 블록까지의 모든 블록에 대한 정보를 알고 있어야 하며, 이를 다른 피어로부터 다운로드하여 얻습니다.
  • 연결이 설정되면, 양쪽 피어는 자신의 ‘best' 블록의 총 난이도(Total Difficulty, TD)와 해시를 포함한 Status 메시지를 서로 전송합니다.
  • 체인 동기화 : 난이도가 더 낮은(worst) 클라이언트는 GetBlockHeaders 메시지를 통해 블록 헤더를 다운로드합니다. 받은 헤더에서 작업 증명(PoW) 값을 확인한 후, GetBlockBodies 메시지를 사용하여 블록 본문을 다운로드합니다. 이 후, 이더리움 가상 머신(Ethereum Virtual Machine)을 사용해 블록을 실행하여 상태 트리(state tree)와 영수증(receipts)을 재생성합니다.
  • 동시 작업 : 헤더 다운로드, 블록 본문 다운로드, 블록 실행은 동시에 발생할 수 있습니다.

State Synchronization (a.k.a “fast sync”)


  • 상태 트리 동기화 : 프로토콜 버전 eth/63부터 eth/66까지는 상태 트리 동기화를 허용했습니다. 그러나 eth/67부터는 이더리움 상태 트리를 eth 프로토콜을 통해 더 이상 검색할 수 없으며, 상태 다운로드는 보조 프로토콜인 snap 프로토콜을 통해 이루어집니다.
  • 상태 동기화 과정 : 상태 동기화는 일반적으로 블록 헤더 체인을 다운로드하고 그 유효성을 검증하는 과정으로 진행됩니다. 블록 본문은 체인 동기화와 마찬가지로 요청되지만, 트랜잭션은 실행되지 않고 '데이터 유효성'만 확인됩니다.
  • 피벗 블록 선택 : 클라이언트는 체인 헤드 근처의 블록(pivot block)을 선택하고 해당 블록의 상태를 다운로드합니다.

Block Propagation


이 내용은 과거 PoW 및 PoA 환경에서의 블록 전파 방식과 관련된 설명이며, 현재 PoS로 전환된 상태에서는 더 이상 유효하지 않음을 참고로 알아두시면 됩니다.


  • PoW에서 PoS로의 전환 : PoW에서 PoS로 전환(The Merge)된 후에는 블록 전파가 더 이상 eth 프로토콜에 의해 처리되지 않습니다. 아래 설명은 과거 PoW 및 PoA(clique) 네트워크에만 적용되며, 블록 전파 메시지(NewBlock, NewBlockHashes 등)는 향후 프로토콜 버전에서 제거될 예정입니다. 현재 PoS로 전환된 eth/68 버전에서는 이 기능이 더 이상 필요하지 않습니다.
  • 블록 전파(Block Propagation) : 새로 채굴된 블록은 모든 노드에 전달되어야 하며, 이는 2단계로 이루어집니다. 피어로부터 NewBlock 메시지를 수신하면, 클라이언트는 작업 증명(PoW) 값이 유효한지 확인하여 블록의 기본 헤더 유효성을 먼저 확인합니다. 그런 다음 NewBlock 메시지를 사용해 연결된 일부 피어(일반적으로 피어 수의 제곱근)에 블록을 전송합니다.
  • 블록 처리 : 헤더 유효성 검사 후, 클라이언트는 블록에 포함된 모든 트랜잭션을 실행하고, 블록의 'Post State'를 계산하여 로컬 체인에 블록을 추가합니다. 이때 블록의 state root 해시는 계산된 Post State root와 일치해야 합니다. 블록이 완전히 처리되고 유효하다고 판단되면, 클라이언트는 이전에 알리지 않은 모든 피어에게 NewBlockHashes 메시지를 보냅니다. 블록을 받지 못한 피어는 전체 블록을 요청할 수 있습니다.
  • 정보 중복 전송 방지 : 노드는 이전에 블록을 알린 피어에게 다시 정보를 보내지 않도록 해야 합니다. 이를 위해 각 피어와 최근에 공유된 블록 해시 세트를 저장하여 중복 전송을 방지합니다.
  • 체인 동기화 유발 가능성 : 블록 알림을 수신한 후, 해당 블록이 클라이언트의 최신 블록의 즉각적인 후속 블록이 아닌 경우, 체인 동기화를 유발할 수도 있습니다.

Transaction Exchange


  • 트랜잭션 교환 : 모든 노드는 채굴자에게 중계하기 위해 대기 중인 트랜잭션을 서로 교환하며, 채굴자는 이를 선택하여 블록체인에 포함시킵니다. 각 클라이언트는 '트랜잭션 풀'에서 대기 중인 트랜잭션을 관리하며, 이 풀에는 수천 개의 트랜잭션이 포함될 수 있습니다.
  • 피어 연결 시 동기화 : 새로운 피어 연결이 설정되면, 양쪽의 트랜잭션 풀을 동기화해야 합니다. 초기에는 양측이 로컬 풀에 있는 모든 트랜잭션 해시를 포함한 NewPooledTransactionHashes 메시지를 교환하여 동기화를 시작합니다.
  • 트랜잭션 요청 : 클라이언트는 NewPooledTransactionHashes 메시지를 수신하면, 로컬 풀에 없는 트랜잭션 해시를 필터링하여 수집합니다. 그런 다음 GetPooledTransactions 메시지를 사용하여 해당 트랜잭션을 요청할 수 있습니다.
  • 트랜잭션 전파 : 클라이언트의 풀에 새로운 트랜잭션이 나타나면, Transactions 및 NewPooledTransactionHashes 메시지를 통해 네트워크에 전파합니다. Transactions 메시지는 완전한 트랜잭션 객체를 소수의 무작위 피어에게 전송하며, 나머지 피어는 트랜잭션 해시만 수신하고 필요 시 전체 트랜잭션 객체를 요청할 수 있습니다. 이를 통해 모든 노드가 트랜잭션을 수신하고, 추가 요청 없이 처리할 수 있도록 보장합니다.
  • 중복 전송 방지 : 노드는 이미 해당 트랜잭션을 알고 있는 피어에게 다시 트랜잭션을 보내지 않아야 합니다. 이를 위해 각 피어가 최근에 중계한 트랜잭션 해시를 기억하는 방식으로 중복 전송을 방지합니다.

Transaction Encoding and Validity


  • 피어 간에 교환되는 트랜잭션 객체는 두 가지 인코딩 중 하나를 가집니다. 우리는 이 두 가지 인코딩의 트랜잭션을 txₙ이라는 식별자로 참조합니다.
    • tx = {legacy-tx, typed-tx}
  • 유형이 지정되지 않은 레거시 트랜잭션은 RLP 목록으로 제공됩니다.
legacy-tx = [
    nonce: P,
    gas-price: P,
    gas-limit: P,
    recipient: {B_0, B_20},
    value: P,
    data: B,
    V: P,
    R: P,
    S: P,
]
  • EIP-2718형식의 트랜잭션은 RLP 바이트 배열로 인코딩되며, 첫 번째 바이트는 트랜잭션 유형(tx-type)이고 나머지 바이트는 유형별로 특정한 데이터(tx-data)입니다.
    • typed-tx = tx-type || tx-data

트랜잭션은 수신될 때 반드시 유효성을 검사해야 합니다. 유효성은 이더리움 체인 상태에 따라 달라집니다. 이 명세서에서 다루는 유효성의 특정 종류는 트랜잭션이 EVM에 의해 성공적으로 실행될 수 있는지 여부가 아니라, 로컬 풀에 임시 저장되고 다른 피어와 교환되는 것이 허용되는지 여부입니다.

트랜잭션은 아래 규칙에 따라 유효성을 검사합니다. 형식이 지정된 트랜잭션의 인코딩은 불투명하지만, 이들은 nonce, gas-price, gas-limit 값을 제공하며 트랜잭션의 서명에서 보낸 사람 계정을 확인할 수 있다고 가정합니다.

  • 트랜잭션이 형식화된 경우, tx-type은 구현체에 알려져 있어야 합니다. 정의된 트랜잭션 유형은 블록에 포함되기 전에 유효한 것으로 간주될 수 있습니다. 알 수 없는 유형의 트랜잭션을 전송하는 피어와는 연결을 끊어야 합니다.
  • 서명은 체인이 지원하는 서명 스키마에 따라 유효해야 합니다. 형식화된 트랜잭션의 경우, 서명 처리는 해당 유형을 소개하는 EIP에 정의되어 있습니다. 레거시 트랜잭션의 경우, 활성화된 두 가지 스키마는 기본 Homestead 스키마와 EIP-155 스키마입니다.
  • gas-limit는 트랜잭션의 '고유 가스(intrinsic gas)'를 충족해야 합니다.
  • 서명에서 파생된 트랜잭션의 보낸 사람 계정은 트랜잭션 비용(gas-limit * gas-price + value)을 충당할 수 있는 충분한 이더 잔액을 가져야 합니다.
  • 트랜잭션의 nonce는 보낸 사람 계정의 현재 nonce와 같거나 커야 합니다.
  • 트랜잭션을 로컬 풀에 포함할지 고려할 때, 구현체는 현재 계정 nonce보다 큰 '미래' 트랜잭션이 얼마나 유효한지, 그리고 'nonce gap'이 얼마나 허용되는지 결정할 수 있습니다.

구현체는 트랜잭션에 대해 다른 유효성 검사 규칙을 강제할 수 있습니다. 예를 들어, 128 kB보다 큰 인코딩된 트랜잭션을 거부하는 것이 일반적인 관행입니다.

달리 명시되지 않는 한, 구현체는 유효하지 않은 트랜잭션을 보낸다고 해서 피어와의 연결을 끊어서는 안 되며, 대신 단순히 트랜잭션을 폐기해야 합니다. 이는 피어가 약간 다른 유효성 검사 규칙을 따르고 있을 수 있기 때문입니다.


정리

  1. 트랜잭션 종류: 트랜잭션은 레거시 트랜잭션(구형)과 EIP-2718 형식화된 트랜잭션(신형) 두 가지로 구분됩니다.
  2. 유효성 검사: 트랜잭션 수신 시, 이를 로컬 풀에 저장하거나 다른 피어와 교환할 수 있는지 여부를 판단하기 위해 유효성 검사를 수행합니다.
  3. 형식화된 트랜잭션: EIP-2718 형식의 트랜잭션은 첫 바이트가 트랜잭션 유형(tx-type), 나머지는 유형별 데이터(tx-data)로 구성됩니다.
  4. 검사 규칙:
    • 트랜잭션 유형(tx-type)은 구현체에 알려져 있어야 합니다.
    • 서명은 유효해야 하며, 체인에서 지원하는 서명 스키마를 따라야 합니다.
    • gas-limit는 트랜잭션의 고유 가스를 충족해야 합니다.
    • 보낸 사람 계정은 충분한 이더 잔액을 가져야 합니다.
    • nonce는 현재 계정 nonce와 같거나 커야 합니다.
    • 구현체는 추가적으로 '미래' 트랜잭션이나 nonce 갭을 허용할지 결정할 수 있습니다.
  5. 중복 전송 방지: 유효하지 않은 트랜잭션을 보냈다고 해서 피어와의 연결을 끊어서는 안 되며, 단순히 트랜잭션을 폐기해야 합니다. 이는 피어가 다른 유효성 검사 규칙을 따를 수 있기 때문입니다.

Block Encoding and Validity


이더리움 블록은 다음과 같이 인코딩됩니다.


block = [header, transactions, ommers]
transactions = [tx₁, tx₂, ...]
ommers = [header₁, header₂, ...]
withdrawals = [withdrawal₁, withdrawal₂, ...]
header = [
    parent-hash: B_32,
    ommers-hash: B_32,
    coinbase: B_20,
    state-root: B_32,
    txs-root: B_32,
    receipts-root: B_32,
    bloom: B_256,
    difficulty: P,
    number: P,
    gas-limit: P,
    gas-used: P,
    time: P,
    extradata: B,
    mix-digest: B_32,
    block-nonce: B_8,
    basefee-per-gas: P,
    withdrawals-root: B_32,
]

특정 프로토콜 메시지에서는 트랜잭션 목록과 ommer 목록이 ‘block body'라는 단일 항목으로 함께 전달됩니다.


block-body = [transactions, ommers, withdrawals]

블록 헤더의 유효성은 사용되는 상황에 따라 달라집니다. 단일 블록 헤더의 경우 작업 증명(PoW) 봉인(mix-digest, block-nonce)의 유효성만 확인할 수 있습니다. 클라이언트의 로컬 체인을 확장하기 위해 헤더가 사용되거나 체인 동기화 중에 여러 헤더가 순차적으로 처리되는 경우 다음 규칙이 적용됩니다.


  • 헤더는 블록 번호가 연속적이며 각 헤더의 부모 해시(parent-hash)가 이전 헤더의 해시와 일치하는 체인을 형성해야 합니다.
  • 로컬에 저장된 체인을 확장할 때, 구현체는 Yellow Paper에 명시된 프로토콜 규칙에 따라 난이도, 가스 한도, 시간 값이 적절한지 확인해야 합니다.
  • gas-used 헤더 필드는 gas-limit보다 작거나 같아야 합니다.
  • 런던 하드포크 이후의 블록에서는 basefee-per-gas가 헤더에 있어야 하며, 이전 블록에서는 없어야 합니다. 이 규칙은 EIP-1559에 의해 추가되었습니다.
  • 병합(The Merge) 이후의 PoS 블록에서는 ommers 헤더가 존재할 수 없으므로 ommers-hash는 빈 keccak256 해시여야 합니다.
  • 상하이 포크 이후의 블록에서는 withdrawals-root가 헤더에 있어야 하며, 포크 이전의 블록에서는 없어야 합니다. 이 규칙은 EIP-4895에 의해 추가되었습니다.

완전한 블록의 경우, 블록의 EVM 상태 전환의 유효성과 블록의 (약한) '데이터 유효성'을 구별합니다. 상태 전이 규칙의 정의는 이 사양에서 다루지 않습니다. 즉각적인 block propagation와 state synchronization을 위해 블록의 데이터 유효성이 필요합니다.


블록의 데이터 유효성을 판단하기 위해 다음 규칙을 사용합니다. 구현체는 유효하지 않은 블록을 보내는 피어와의 연결을 끊어야 합니다.


  • 블록 header는 유효해야 합니다.
  • 블록에 포함된 transaction은 해당 블록 번호에서 체인에 포함될 수 있도록 유효해야 합니다. 즉, 앞서 언급된 트랜잭션 유효성 검사 규칙 외에도, 해당 블록 번호에서 트랜잭션 유형(tx-type)이 허용되는지 여부와 트랜잭션 가스 유효성을 블록 번호에 맞게 확인해야 합니다.
  • 모든 트랜잭션의 gas-limits 합이 블록의 gas-limit을 초과해서는 안 됩니다.
  • 블록의 transaction의 트랜잭션 목록의 머클 트리 해시를 계산하고 txs-root와 비교하여 검증해야 합니다.
  • 블록 본문의 인출 목록은 머클 트리 해시를 계산하고 withdrawals-root와 비교하여 검증해야 합니다. 인출은 합의 레이어에서 블록에 삽입되므로 추가적인 검증은 불가능합니다.
  • ommers 목록에는 최대 두 개의 헤더가 포함될 수 있습니다.
  • keccak256(ommers)는 블록 헤더의 ommers-hash와 일치해야 합니다.
  • ommers 목록에 포함된 헤더는 유효한 헤더여야 합니다. 이들의 블록 번호는 포함된 블록의 번호보다 크지 않아야 합니다. ommer 헤더의 부모 해시는 포함된 블록의 조상 중 깊이가 7 이하인 블록을 참조해야 하며, 이 조상 집합에 포함된 이전 블록에 포함된 적이 없어야 합니다.

Receipt Encoding and Validity


receipts는 블록의 EVM 상태 전이 결과입니다. 트랜잭션과 마찬가지로 영수증에는 두 가지 인코딩 방식이 있으며, 우리는 이 두 가지 인코딩을 receiptₙ이라는 식별자로 참조합니다.


receipt = {legacy-receipt, typed-receipt}

유형이 지정되지 않은 기존 영수증은 다음과 같이 인코딩됩니다.

legacy-receipt = [
    post-state-or-status: {B_32, {0, 1}},
    cumulative-gas: P,
    bloom: B_256,
    logs: [log₁, log₂, ...]
]
log = [
    contract-address: B_20,
    topics: [topic₁: B, topic₂: B, ...],
    data: B
]

EIP-2718 유형의 영수증은 RLP 바이트 배열로 인코딩됩니다. 여기서 첫 번째 바이트는 영수증 유형(tx-type과 일치)을 제공하고 나머지 바이트는 해당 유형과 관련된 불투명 데이터입니다.


typed-receipt = tx-type || receipt-data

Ethereum Wire Protocol에서 receipts은 항상 블록에 포함된 모든 receipts의 전체 목록으로 전송됩니다. 또한 영수증이 포함된 블록이 유효하고 알려져 있다고 가정합니다. 피어가 블록 receipts 목록을 수신하면 목록의 머클 트리 해시를 계산하고 블록의 receipts 루트와 비교하여 확인해야 합니다. 유효한 receipts 목록은 EVM 상태 전환에 의해 결정되므로 본 사양에서는 receipts에 대한 추가 유효성 규칙을 정의할 필요가 없습니다.


Protocol Messages


이더리움의 eth 프로토콜 메시지는 주로 네트워크 내에서 피어 간의 정보 교환을 위해 사용되며, 각 메시지는 특정 형식과 규칙을 따릅니다. 아래는 주요 메시지와 그 기능에 대한 설명입니다.


  • 요청 ID: 대부분의 메시지에서 첫 번째 요소는 request-id로, 요청하는 피어가 선택한 64비트 정수 값입니다. 응답하는 피어는 이 값을 그대로 응답 메시지에 반영해야 합니다.
  • 소프트 한계: 여러 메시지에서 소프트 한계가 설정되어 있으며, 예를 들어 BlockHeaders, BlockBodies, Receipts 응답은 2 MiB로 권장됩니다.
  • Status (0x00)
    • [version: P, networkid: P, td: P, blockhash: B_32, genesis: B_32, forkid]
    • 기능: 연결이 설정된 직후 피어의 현재 상태를 알립니다. 이는 다른 eth 프로토콜 메시지를 보내기 전에 수행되어야 합니다.
    • 구성 요소:
      • version: 현재 프로토콜 버전.
      • networkid: 블록체인을 식별하는 정수 값.
      • td: 최고 체인의 총 난이도.
      • blockhash: 최고(TD가 가장 높은) 블록의 해시.
      • genesis: 제네시스 블록의 해시.
      • forkid: EIP-2124 포크 식별자.
    • 아래는 일반적인 네트워크 ID와 해당 네트워크가 나열되어 있습니다.
    • 트랜잭션 replay prevention에 사용되는 EIP-155 체인 ID와 일치할 수도 있고, 아닐 수도 있기에 클라이언트는 특정 네트워크 ID를 사용할것을 요구하면 안된다.ID chain
      0 Olympic (disused)
      1 Frontier (now mainnet)
      2 Morden testnet (disused)
      3 Ropsten testnet (disused)
      4 Rinkeby testnet (disused)
      5 Goerli testnet
    • chain 정보들 : https://chainid.network/
  • NewBlockHashes (0x01)
    • [[blockhash₁: B_32, number₁: P], [blockhash₂: B_32, number₂: P], ...]
    • 기능: 네트워크에 새로 나타난 블록을 피어에게 알립니다. 피어가 알지 못할 가능성이 있는 모든 블록을 포함해야 하지만, 이미 알리고 있는 해시를 중복하여 보내는 것은 피어의 평판을 떨어뜨릴 수 있습니다.
    • 나중에 보내는 노드가 진행 중인 GetBlockHeaders 메시지에 따르기를 거부하는 해시를 포함하는 것은 잘못된 형식으로 간주되어 보내는 노드의 평판을 낮출 수 있습니다.
  • Transactions (0x02)
    • [tx₁, tx₂, ...]
    • 기능: 피어가 트랜잭션 큐에 포함시켜야 할 트랜잭션을 지정합니다. 이 메시지는 최소 하나의 새로운 트랜잭션을 포함해야 하며, 동일한 트랜잭션을 같은 세션에서 재전송하지 않아야 합니다.
    • 트랜잭션 메세지에는 하나 이상의 새로운 트랜잭션이 포함되어야 한다.
      • 빈 트랜잭션 메세지는 권장되지 않으며, 연결이 끊어질 수 있다.
    • 재전송된 트랜잭션을 받은 피어에게 중계해서는 안된다.
      • 실제로 이는 이미 전송되거나 수신된 피어 별 볼륨 필터 또는 트랜잭션 해시 세트를 유지하여 구현하는 경우가 많다.
  • GetBlockHeaders (0x03) / BlockHeaders (0x04)
    • GetBlockHeaders (0x03) : [request-id: P, [startblock: {P, B_32}, limit: P, skip: P, reverse: {0, 1}]]
    • BlockHeaders (0x04) : [request-id: P, [header₁, header₂, ...]]
    • 기능: 블록 헤더 요청과 그에 대한 응답 메시지입니다. 요청 시, 시작 블록, 한계, 스킵, 역순 등을 지정하여 블록 헤더 목록을 요청할 수 있습니다.
    • 응답: 요청된 헤더 목록을 포함하며, 요청된 블록 헤더를 찾지 못한 경우 목록이 비어 있을 수 있습니다.
  • GetBlockBodies (0x05) / BlockBodies (0x06)
    • GetBlockBodies (0x05) : [request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...]]
    • BlockBodies (0x06) : [request-id: P, [block-body₁, block-body₂, ...]]
    • 기능: 블록 본문 데이터 요청과 그에 대한 응답 메시지입니다. 요청된 블록의 본문 데이터를 포함하며, 요청된 블록을 찾지 못한 경우 목록이 비어 있을 수 있습니다.
  • NewBlock (0x07)
    • [block, td: P]
    • 기능: 피어가 알아야 할 하나의 완전한 블록을 지정합니다. td는 해당 블록의 총 난이도입니다.
  • NewPooledTransactionHashes (0x08)
    • [txtypes: B, [txsize₁: P, txsize₂: P, ...], [txhash₁: B_32, txhash₂: B_32, ...]]
    • 기능: 네트워크에 나타난, 아직 블록에 포함되지 않은 트랜잭션을 알리는 메시지입니다. 트랜잭션 유형, 크기, 해시 목록이 포함됩니다.
    • 특징: 트랜잭션 해시와 크기를 통해 피어에게 트랜잭션을 알립니다.
  • GetPooledTransactions (0x09)
    • [request-id: P, [txhash₁: B_32, txhash₂: B_32, ...]]
    • GetPooledTransactions 요청에 권장되는 소프트 제한은 256개 해시(8KiB)입니다.
    • 수신자는 응답(크기 또는 제공 시간)에 대해 임의의 제한을 적용할 수 있으며 이는 프로토콜 위반으로 간주되어서는 안 됩니다.
  • PooledTransactions (0x0a)
    • [request-id: P, [tx₁, tx₂...]]
    • 이는 로컬 풀에서 요청된 트랜잭션을 반환하는 GetPooledTransactions에 대한 응답입니다.
    • 트랜잭션은 요청과 동일한 순서여야 하지만 사용할 수 없는 트랜잭션을 건너뛰어도 괜찮습니다.
    • 이때 응답 크기 제한에 도달한 경우 요청자는 다시 요청할 해시(마지막 반환된 트랜잭션에서 시작하는 모든 것)와 사용할 수 없다고 가정할 해시(마지막 반환된 트랜잭션 전의 모든 간격)를 알게 됩니다.
    • NewPooledTransactionHashes를 통해 거래를 알리고, PooledTransactions를 통해 거래 제공을 거부하는것이 허용되는데 이러한 상황은 INFO와 REQUEST 사이에 트랜잭션이 블록에 포함되고, 풀에서 제거될 때 발생할 수 있다.
    • 풀의 트랜잭션과 일치하는 해시가 없으면 피어는 빈 목록으로 응답할 수 있습니다.
  • GetReceipts (0x0f)
    • [request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...]
    • 피어가 지정된 블록 해시의 영수증이 포함된 영수증 메세지를 반환하도록 요구합니다.
    • 단일 메세지에서 요청할 수 있는 확인 수는 구현에 정의된 제한에 따라 달라질 수 있습니다.
  • Receipts (0x10)
    • [request-id: P, [[receipt₁, receipt₂], ...]]
    • 요청된 블록 영수증을 제공하는 GetReceipts에 대한 응답입니다.
    • 응답 목록의 각 요소는 GetReceipts 요청의 블록 해시에 해당하며 블록 영수증의 전체 목록을 포함해야 합니다.
    • 영수증 응답에 권장되는 소프트 제한은 2MiB입니다.

CTF를 운영하게 되면서 간단한 블록체인 문제를 만들어봤다.

공개되어 있는 Zellic의 Template이나 ChainFlag의 Template을 사용하여 만들다가 한 번 구현해볼까?라는 생각으로 어쩌다 만들게 되었다. geth를 이용해 만들다가 결국 Ganache로 결정되었다.

 

https://github.com/KindKillerwhale/Blockchain-CTF-Template

 

GitHub - KindKillerwhale/Blockchain-CTF-Template: Blockchain-CTF-Template

Blockchain-CTF-Template. Contribute to KindKillerwhale/Blockchain-CTF-Template development by creating an account on GitHub.

github.com

 

급하게 구현한다고 코드가 깔끔하고 효율적이지는 않다. 전체적으로 고칠 점이 많다. 대회 운영을 위해 기능 구현만 해보았다. 나중에 차근차근 고쳐봐야겠다.

+ Recent posts