이더리움 비콘 체인 이해하기

Donguk Seo
Decipher Media |디사이퍼 미디어
32 min readAug 26, 2022

본 아티클에서는 이더리움의 비콘체인 영역을 다룹니다. 글에 들어가기 앞서서, 필자는 이더리움과 아무런 연관이 없음을 밝히며, 본문은 정보 제공을 목적으로 작성된 글이며, 투자 의사 결정에 대한 근거로 사용될 수 없습니다.

Authors
신우철(@swc0620)
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)
서동욱(@donguk.seo)
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)
Reviewed by: 정재환

1. 들어가기

  • 이더리움 배경지식

2. 비콘 체인 이해하기

  • 심장처럼 뛰는 비콘 체인
  • 제안자와 위원회
  • 체크포인트와 최종성
  • 검증자의 라이프사이클

3. 비콘 체인 핵심 알고리즘

  • RANDAO
  • Gasper
  • LMD GHOST
  • Casper FFG

4. 코드로 살펴보는 비콘 체인

  • 코드 구조
  • 검증자
  • 블록 제안

5. 결론

  • 정리
  • Phase 0의 한계
  • Nest Step : Merge

1. 들어가기

이 글은 이더리움의 확장성 문제를 해결하기 위한 방법을 비콘 체인 관점으로 깊게 소개하는 글이다. 크게 이더리움의 배경지식으로 부터, 비콘 체인 내용을 개념부터 코드까지 소개한다. 비콘 체인 설명은 ethos.dev의 비콘 체인 소개글을 가장 많이 참고했고, 알고리즘의 경우는 비탈릭 부테린의 논문을, 비콘 체인의 코드는 Typescript로 구현한 ChainSafe의 lodestar를 참고했다.

이더리움 배경지식

이더리움2.0 체인, 출처 : https://media.consensys.net/state-of-ethereum-protocol-2-the-beacon-chain-c6b6a9a69129

이더리움은 확장성 문제를 해결하기 위해, 샤딩과 POS를 도입하기로 했다.

샤딩이란, 데이터베이스(정확히는 노드가 저장하고 있는 상태(state))를 분할해서 데이터베이스의 부하와 체인의 지나친 중앙화를 막는 전략이다. 샤딩을 도입해서 이더리움의 데이터를 분할하는 시도를 하고 있다. 지금은 이더리움의 체인 하나에 모든 것을 기록하고 있는데, 체인을 여러 샤드 체인으로 나눈다.

POS란 지분 증명으로, 기존 POW와는 다르게 속도면에서 빠르다. POW는 컴퓨팅 자원을 소모해야만 합의 과정에 참여할 수 있지만, POS는 가지고 있는 ETH로 합의 과정에 참여할 수 있기 때문이다.

이더리움은 크게 위 두 가지를 도입하기 위해 아래와 같이 3가지 단계를 설정했다.

이더리움2.0의 3가지 단계, 출처 : https://ethereum.org/en/upgrades/
  • Phase 0 : Beacon Chain
  • Phase 1 : The Merge
  • Phase 2: Shard chains

2022년 8월 기준, Phase 0은 달성했고, Phase 1로 향하고 있다. Phase 1은 2022년 9월에 이루어질 예정이다. Phase 0의 결과는 비콘스캔에서 쉽게 확인할 수 있다. 비콘 체인에 블록들이 기록되는 것을 실시간으로 확인할 수 있다. Phase 0는 비콘 체인이 만들어진 것뿐이라, 이더리움 메인넷의 트랜잭션들을 처리하고 있지는 않다. Phase 1에서는 이 과정이 진행되며, 이때부터 POW가 아니라 POS를 이용하게 된다. Phase 2에서는 샤딩을 도입하게 된다.

2. 비콘 체인 이해하기

비콘 체인은 이더리움에 POS를 도입하기 위해 설계된 체인이며, 참여자들을 관리한다. 어떤 검증자가 블록을 제안해야 할지, 어떤 검증자가 위원회가 되어 해당 블록을 증명해야 하는지, 참여자의 행동에 따라 보상을 주거나 제제를 가한다.

두근 두근 심장처럼 뛰는 비콘 체인

슬롯과 에포크, 출처 : https://ethos.dev/beacon-chain/

비콘 체인은 정해진 리듬으로 움직인다. 이 리듬을 슬롯(Slot)과 에포크(Epoch)로 정의했다. 슬롯은 12초며, 에포크는 32개의 슬롯 총 6.4분이다. 비콘 체인에서 슬롯에는 하나의 블록이 추가될 수 있다. 비콘 체인의 블록은 샤드 체인의 블록과 연결되는데, 비콘 체인의 블록에는 최대 64개의 샤드 블록이 추가될 수 있다. 즉 시스템이 최적으로 실행되고 있다면 매 12초마다, 64개의 샤드 블록이 1개의 비콘 블록에 기록된다.

슬롯은 정해진 시간일 뿐이며, 비콘 블록과 정확하게 일치하지 않는다. 1024 슬롯이라고 해서 1024개의 비콘 블록이 있지 않을 수 도 있다는 뜻이다. 슬롯을 기록해주어야 할 검증자가 오프라인이 될 경우, 슬롯이 비어있을 수 있기 때문이다. 비콘 체인과 샤드 체인의 제네시스 블록은 슬롯 0이다.

비콘 체인은 정해진 리듬에 따라 무엇을 하는 걸까? 비콘 체인은 매 에포크마다 검증자를 슬롯으로 고르게 나누고, 각 슬롯당 최소 128개의 검증자를 위원회로 할당한다. 만약 16,384(512 * 32)개의 검증자가 있다면, 1개의 에포크를 구성하는 32개 슬롯에 512개의 검증자가 배정된다.

제안자와 위원회

슬롯에 배정된 검증자들은 블록을 제안하는 제안자가 될 수도, 제안된 블록을 증명하는 위원회가 될 수 도 있다. 이 과정에서 공격의 위험을 방지하기 위해 RANDAO라고 부르는 의사 난수(초기값을 이용하여, 이미 결정되어있는 메커니즘으로 생성되는 수) 과정을 통해 제안자와 위원회가 결정된다.

검증자와 위원회, 출처 : https://ethos.dev/beacon-chain/

위 그림은 1~31 슬롯에 각각 1개의 제안자와 최소 128개의 검증자가 위원회로 배치되어있는 그림이다. 제안자와 위원회는 RANDAO를 통해 결정된다. RANDAO는 다음 장에서 소개한다.

블록 제안 및 증명 과정, 출처 : https://ethos.dev/beacon-chain/

위의 그림은 제안자의 블록 제안과, 위원회의 증명과정을 보여준다. 슬롯 1은 Alice가 슬롯 2는 Bob이 슬롯 3은 Eve가 제안했다. 위원회 A 중 2개의 검증자는 Alice가 제안한 블록을 슬롯 1로 증명했으며, 하나의 검증자는 오프라인 상태다. 위원회 B 중 2개의 검증자는 슬롯 2에 Bob이 제안한 블록을 슬롯 2로 증명했고, 1개의 검증자는 데이터를 잘못 받아 Alice가 제안한 블록을 슬롯 2로 증명했다. Eve가 제안한 블록의 경우 위원회 C의 모든 검증자가 슬롯 3으로 증명했다. 위의 경우 슬롯 2에서 체인이 나뉘는, 포크(fork)가 발생하게 된다.

이 포크를 해결하기 위해서 비콘 체인에서는 LMD GHOST라는 체인 선택 규칙을 사용한다. 기존 이더리움에서 사용하던 가장 긴 체인을 선택하는 “longest-chain-rule” 대신, 최근 메시지가 가장 많은 체인을 선택하는 LMD GHOST 규칙을 사용한다. LMD GHOST는 다음 장에서 소개한다.

체크포인트와 최종성

비콘 체인에서는 체크포인트를 에포크의 첫 슬롯에 있는 블록으로 정의한다. 아래 그림은 에포크가 64개의 슬롯을 가지고 있음을 가정하고, 체크포인트가 어느 블록인지 보여주는 그림이다. 체크포인트는 에포크마다 무조건 하나가 있으며, 하나의 블록은 여러 에포크의 체크포인트가 될 수 있다.

체크포인트, 출처 : https://ethos.dev/beacon-chain/

위 그림에서, 에포크 0의 경우 첫 블록인 제네시스 블록이 체크포인트이다. 에포크 1의 경우 블록이 64번만 있지만, 64번 블록이 에포크의 첫 슬롯의 블록이므로 체크포인트이다. 에포크 2의 경우는 특별한데, 128번 블록이 첫 블록인데 해당 블록이 없기 때문에 이전 에포크의 가장 가까운 블록인 64번 블록을 체크포인트로 둔다. 즉 에포크 2의 체크포인트는 에포크 1의 체크포인트다. 에포크 3의 경우 192번 블록을 체크포인트로 두어야 하지만, 블록이 없기 때문에 가장 가까운 블록인 180번을 체크포인트로 둔다. 정리하면 아래와 같다.

  • 에포크 0의 체크포인트 : 제네시스 블록
  • 에포크 1의 체크포인트 : 에포크 1에 있는 64번 블록
  • 에포크 2의 체크포인트 : 에포크 1에 있는 64번 블록
  • 에포크 3의 체크포인트 : 에포크 2에 있는 180번 블록

체크포인트를 결정하기 위해서 검증자들은 FFG Vote라고 부르는 투표를 해야 한다. 이 투표에는 이전 체크포인트와 현재 에포크의 체크포인트 정보가 담겨 있다. LMD GHOST의 경우 정해진 검증자만 진행하지만, FFG Vote는 모든 검증자가 에포크마다 해야 한다. FFG의 상세한 내용은 다음 장에서 설명한다.

에포크의 체크포인트가 2/3의 투표를 얻었다면, 이 체크포인트는 정당화(justified)되었다고 정의한다. 다음 에포크의 체크포인트가 정당화되었을 경우에, 체크포인트가 확정(finalized) 되었다고 정의한다. 체크포인트가 확정되면, 최종성(Finality)을 가진다. 일반적으로 두 개의 에포크가 정당화되어야 하므로, 하나의 체크포인트는 12.8분이 지나야 확정된다.

평균적으로 유저가 에포크 중간에 트랜잭션을 보냈다면, 해당 트랜잭션이 확정되기 위해서는 가지기 위해서는 2.5 에포크, 16분이 소요된다. 2/3만 투표를 하면 정당화되었다고 할 수 있기 때문에 2번째 에포크의 경우 32 슬롯이 아니라 22 슬롯 정도에서 정당화되었다고 본다면, 16분이 아니라 14분으로 볼 수 있다.

검증자의 라이프사이클

검증자를 활성화 하기 위해서는 32 ETH를 예치해야 한다. 이더리움 메인넷에 있는 컨트랙트에 32ETH를 예치하면, 하나의 검증자를 활성화할 수 있다. 예치가 확인되면, 1 에포크 후에 활성화 큐에 들어가게 되고, 실행 후 4 에포크 후에 검증자가 활성화된다.

활성화된 뒤로, 아래와 같이 3가지 방법으로 예치한 ETH를 출금할 수 있다.

  • 악의적인 행동으로 인해 바로 추방될 경우, 4 에포크가 지나면 Slashing이 실행된다. 이때는 약 36일이 지나야 남은 금액을 출금할 수 있다.
  • 16 ETH 이하로 가지고 있을 경우 비활성화 작업이 초기화된다. 4 에포크 후에, 비활성화가 실행되고, 약 27시간이 지난 후에 출금할 수 있다.
  • 검증자가 활성화된 후 2480 에포크가 지나면 비활성화를 실행할 수 있다. 4 에포크 후에 실행되며, 그 후 약 27시간이 지난 후에 출금할 수 있다.
검증자 라이프 사이클, https://ethos.dev/beacon-chain/

3. 비콘 체인 핵심 알고리즘

RANDAO

RANDAO는 탈중앙화 된 방식으로 난수를 생성하기 위한 방법이다. 검증자를 제안자와 위원회로 배정할 때, 또는 도박 DApp을 만들 때 등 이더리움 체인 상에서 다양한 경우에 난수(random number)를 생성할 필요가 있다. 그러나 컴퓨터는 난수를 간단히 만들 수 없으며, 대개 난수표를 여러 개 만들어 놓은 후 주어진 시드(seed)에 따라 난수표를 선택하는 방식으로 의사 난수(pseudorandom number)를 생성한다. 이때 악의적인 행위자가 시드를 미리 알게 된다면 어떤 난수가 생성될지 예상하여 그에 따라 이익을 탈취할 수 있게 된다. 따라서 의사 난수 생성에서는 시드를 안전하게 결정하는 것이 중요하다. RANDAO는 이 시드를 탈중앙적으로 생성한다.

RANDAO의 과정은 commit-reveal 방식의 3가지 단계(phase)로 이루어진다.

  • 1단계(commit) : 유효한 sha3(s)들을 수집한다.

의사 난수 생성에 참여하고자 하는 참여자들은 스마트 컨트랙트 형태로 배포된 RANDAO 컨트랙트 C에 참여자가 각각 선택한 비밀 숫자 s를 sha3에 적용한 결괏값 sha3(s)를 주어진 시간 내(6개의 블록이 생성될 시간, 대략 72초)에 전송해야 한다. 이때, 저당(pledge)으로서 m ETH를 C에 같이 전송해야 한다. 만약 동일한 sha3(s) 값이 여러 번 제출(commit)되면 첫 번째 값만 C가 받아들인다. 또한 만약 의사 난수 생성에 참여한 참여자 수가 최소한도 이하이면(즉, 수집된 sha3(s)가 일정 수 이하이면) 해당 시간대에 의사 난수 생성은 실패하게 된다.

  • 2단계(reveal) : 유효한 s들을 수집한다.

1단계가 끝나면 유효하게 sha3(s) 값을 전송한 참여자들은 역시 주어진 시간 내에 자신의 s를 C에 전송해야 하고 자신의 s를 공개(reveal) 해야 한다. C는 sha3를 s에 적용하여 s가 유효한지 확인한 후 유효한 s를 의사 난수를 생성하기 위한 시드들의 모음(collection)에 추가한다. 만약 참여자가 자신의 s를 공개하지 않으면 저당 잡힌 m ETH를 환불받을 수 없다. 만약 하나 이상의 s가 공개되지 않으면 해당 시간대에 의사 난수 생성은 실패하며, 압류된 저당은 유효하게 자신의 s를 공개한 참여자들에게 균등하게 분배된다. 난수 생성을 요청했던 다른 컨트랙트가 지불한 비용은 다시 그 컨트랙트로 환불된다.

  • 3단계 : 의사 난수를 생성하고 저당 잡은 ETH와 보상을 환불한다.
  1. 참여자들이 전송한 모든 비밀 숫자 s1, s2, …, sn이 수집되면 C는 함수 f(s1, s2, …, sn)을 적용하여 의사 난수를 생성한다. 비탈릭 부테린의 노트에 따르면, 함수 f로 XOR 함수를 사용한다. 생성된 의사 난수는 C의 스토리지에 저장되고, 이전에 난수 생성을 요청했던 다른 컨트랙트들에 그 값이 전송된다.
  2. C는 저당을 참여자들에게 환불해준다. 동시에 난수 생성을 요청했던 다른 컨트랙트들로부터 받은 추가 이익을 1/n으로 나누어 의사 난수 생성 참여자들에게 보너스로 제공한다.

한편 2단계에서 마지막 공개자 공격(Last Revealer) 문제가 발생하며 이를 해결하기 위해 VDF(Verifiable Delay Function)이 사용된다. 마지막 공개자 공격 문제란, 악의적 참여자가 다른 참여자들이 s를 공개하는 것을 모두 지켜본 수 자신의 s를 공개할지 안 할지를 선택하여 시드를 임의로 조작하는 문제를 뜻한다. 예를 들어 다음과 같은 경우가 있을 수 있다. 홀짝에 따라서 보상을 가져가는 도박 컨트랙트에 참여하여 홀수에 돈을 건 A가 RANDAO의 의사 난수 생성에도 참여할 수 있다. A는 2단계에서 다른 RANDAO 참여자들이 공개하는 s를 지켜보았다. 자신이 s를 제출하면 의사 난수 생성에 성공하게 되며 동시에 시드에 따라 짝수가 생성된다는 사실을 알 수 있다. 따라서 A는 의도적으로 자신의 s를 제출하지 않고 해당 시간대에 의사 난수 생성을 실패시키게 만들 수 있다.

VDF는 계층적이고 복잡한 연산을 통해 악의적 행위자가 시드로부터 의사 난수를 계산하는 데까지 걸리는 시간을 지연시켜 마지막 공개자 공격 문제를 해결한다. VDF는 VDF의 결괏값을 다시 VDF의 입력값으로 이용하여 계층화(folding)한다. 이렇게 하면 새 연산의 입력값이 이전 연산의 결괏값에 의존적이기 때문에 병렬적인 계산이 불가능하다. 예를 들어 ((((((X^2)^2)^2)^2)^2)^2)^2라는 식이 있다. X=5를 입력하면 그 결괏값인 5^2=25를 다시 입력값으로 사용한다. 최종 결괏값은 293873587705571876992184134305561419454666389193021880377187926569604314863681793212890625이 나온다. 계층이 깊어질수록 연산이 어려워지고 따라서 의사 난수를 계산하는 데까지 시간도 오래 걸리게 된다. 한편 실제 RANDAO 컨트랙트에 의한 VDF 계산은 오프체인에서 VDF 계산을 위한 전용 ASIC을 통해 이루어지게 되며, 결과로 생성된 의사 난수는 메인 체인에 기록된다.

Gasper 알고리즘

이더리움 2.0의 비콘 체인은 지분 증명(PoS, Proof Of Sate) 기반의 합의 프로토콜로 Gasper 알고리즘을 사용한다. Gasper 알고리즘은 완결성(finality)을 보장하기 위한 Casper FFG와 포크 된 체인을 선택하는 규칙(fork-choice rule)인 LMD GHOST를 결합하여 수정한 것이다. 이하에서는 Gasper 알고리즘을 LMD GHOST와 Casper FFG로 나누어 살펴본다.

  1. LMD GHOST

LMD GHOST는 Latest Message Driven Greediest Heaviest Observed SubTree의 약자로 포크 된 체인을 선택하는 규칙이다. LMD GHOST의 알고리즘은 다음과 같다.

LMD GHOST, https://medium.com/r?url=https%3A%2F%2Farxiv.org%2Fpdf%2F2003.03052.pdf

LMD GHOST를 설명하면 다음과 같다. 우선 GHOST란 포크 된 체인을 선택하는 규칙 중 하나로, 가장 많은 검증자가 지지하고 있는 포크를 선택하는 탐욕 알고리즘이다. LMD는 가장 최근에 제기된 M개의 주장(attestation)에 대해서만 고려한다는 뜻이다. 예시와 함께 살펴보자.

LMD 예시, https://medium.com/r?url=https%3A%2F%2Farxiv.org%2Fpdf%2F2003.03052.pdf

특정 시점에 비콘 체인이 포크 되어 있고 가장 최근의 M개의 입증(attestation)들이 제기되어 있다. M개의 입증은 각각 특정한 포크가 올바르다고 지지하고 있다. 태초 블록(genesis block)부터 시작하여 블록의 자식 블록 중 무게(weight)가 더 높은 쪽을 선택하며 내려온다. 이때 무게란, 그 블록을 지지하는 입증에 그 입증을 일으킨 검증자의 지분(stake)을 곱한 값을 모두 더한 값이다. 위 예시에서는 편의상 M개의 입증을 일으킨 M명의 검증자들이 모두 동일하게 1만큼의 지분을 갖고 있다고 가정하고 있다. 이때 무게가 더 높은 쪽을 선택하여 내려오다가 잎 노드(leaf node)에 다다르게 되면, 그동안 선택해온 블록들이 LMD GHOST가 선택하는 체인이 된다.

2. Casper FFG

Casper FFG는 Casper the Friendly Finality Gadget의 약자로 지분 증명(PoS, Proof Of Sate) 기반의 합의 알고리즘이다. Casper FFG에서는 우선 확인점(checkpoint)을 정의한다. 어떤 상수 H를 기준으로 할 때 태초 블록부터 현재 블록까지의 거리가 H의 자연수 n배인 블록들이 모두 확인점들이 된다. 편의상 태초 블록으로부터 n*H의 거리를 가진 점 X가 높이(height) h(X)을 가진다고 정의하자.(n+1)*H의 거리를 가진 점 Y에 대해서 h(Y) = h(X) + 1이 성립한다.

Gasper 알고리즘에서는 H를 1 에포크의 길이로 잡아 매 에포크의 마지막 블록이 확인점이 되도록 한다. 확인점이 정의되었으면 다음으로 입증(attestation)을 정의한다. 입증이란 새로운 확인점 기준을 기존의 확인점인 A 블록에서 새로운 확인점인 B 블록으로 옮기는데 찬성한다는 검증자의 투표(vote) 행위이다. 각각의 입증은 그 입증을 한 검증자의 지분이 곱해져 입증마다 서로 다른 무게(weight)를 갖는다. 이때 항상 h(B) = h(A) + 1일 필요는 없다. 즉 만약 선의의 검증자가 어떠한 이유로 B 블록을 놓쳤다면 h(C) = h(A) + k인 C 블록 등으로 입증을 하는 것도 가능하다.

  • 검증(justification)과 완결(finalisation)

전체 지분의 2/3 이상이 확인점 기준을 A 블록에서 B 블록으로 옮기는데 동의한다면 확인점 B 블록이 검증(justified)되었다고 정의한다. 또한 이 경우 (A, B)에 압도적 다수의 연결(supermajority link)이 생겼다고 정의한다. 만약 A 블록이 검증된 상태에서 h(B) = h(A) + 1인 B 블록에 대하여 압도적 다수의 연결 (A, B)가 존재한다면 A가 완결(finalised)되었다고 정의한다. 이 경우 물론 B는 검증된다.

  • 삭감 조건(slashing conditions)

Casper FFG의 삭감 조건은 2가지이다.

  1. h(Y) = h(Z)인 블록 Y, Z에 대하여 X → Y, X → Z의 입증을 동시에 하는 경우
  2. h(K) < h(L) < h(M) < h(N)인 블록 K, L, M, N에 대하여 K → N, L→M의 입증을 동시에 하는 경우

비탈릭 부테린의 논문에 따르면, LMD GHOST와 Capser FFG를 결합한 Gasper 알고리즘은 전체 검증자 중 악의적 검증자가 1/3 이하일 때, 즉 1/3-slashable 할 때, safety와 liveliness를 보장한다는 것이 증명되어 있다. 구체적인 증명은 논문을 참조하면 좋다.

4. 코드로 이해하는 비콘 체인

현재 phase0 비콘 체인의 클라이언트는 비콘스캔에 따르면 5가지가 있다. 소괄호에는 해당 클라이언트를 구현하는 데 사용된 주요 언어다. 5가지의 클라이언트는 모두 이더리움 POS 명세서를 참고해서 구현되었다. 이 글에서는 필자에게 가장 익숙한 Typescript로 구현되어 있는 Lodestar를 선택해서 분석했다. 이 글에서는 v0.42.0-rc.0 버전의 코드를 분석했다.

Lodestar의 구조

lodestar의 설계 문서에 따르면, lodestar의 비콘 노드를 구성하는 모듈은 크게 6가지로 아래와 같다.

  • api : 비콘 노드가 제공하는 api를 미리 정의된 명세서에 따라 구현 한 모듈. 이 api를 이용하면 비콘의 상태를 조회하거나, 새로운 블록을 제안하는 등의 기능을 제공한다.
  • chain : 블록을 처리하거나, 데이터를 증명하는 모듈
  • db : 비콘 체인의 모든 영구적 데이터를 처리하고 저장하는 모듈
  • eth1 : eth1 블록체인과의 상호적용을 처리하는 모듈
  • network : Phase 0의 이더리움 네트워크가 구현된 모듈
  • sync : 비콘 노드가 동기화 할 때 사용하는 모듈

아래는 설계문서에서 가져온, 데이터 흐름도이다. 예를 들면, 검증자가 만약 블록을 제안하게 된다면 lodestar-validator → api → network, chain 순으로 데이터가 이동하는 것을 확인할 수 있다. 회색은 위에서 정의한 모듈들이고, 노란색 바탕인 블록들은 패키지다. 구현해야 할 것이 더 많은 것 들은 따로 패키지로 만들어 둔 것 같다. lodestar-validator는 검증자 클라이언트로 사용하는 패키지이고, lodestar-fork-choice는 phase0에서 사용하는 fork-choice를 구현한 패키지이고, lodestar-db는 db 모듈이 사용하는 db관련 패키지다.

모듈간 데이터 흐름도, https://chainsafe.github.io/lodestar/design/architecture/

Lodestar의 코드를 전부 살펴보면 좋겠지만, 핵심만 살펴보고자 한다. 비콘체인을 구성하는 핵심인 검증자가 어떻게 실행되는지, 어떤 방식으로 블록을 제안하는지에 대해서 알아보자.

검증자 코드 살펴보기

검증자를 실행하기 위해서는, lodestar-cli의 validator-handler를 실행하게 된다. 해당 handler는 많은 일을 하지만 그중에서도 주목할만한 코드는 아래와 같다. 아래의 validator는 위에서 lodestar-validator 패키지의 Validator를 BeaconNode로 부터 초기화하도록 initializeFromBeaconNode를 호출한다.

validator-handler가 Validator를 초기화하는 방법

lodestar-validator : Validator

Validator Class를 초기화할 때 어떤 논리들이 들어있는지 가볍게 살펴보자. 아래의 코드는 Validator를 초기화할 때 사용하는 코드 중에서 중요한 부분들만 남겨 놓은 코드다.

Validator에서 몇가지 비즈니스 로직을 초기화하는 방법

검증자는 블록을 제안하는 제안자가 될 수도(BlockProposingService), 제안된 블록을 증명하는 위원회(AttestationService)가 될 수도 있는데 딱 위의 코드를 보면 검증자의 역할을 한 번에 알 수 있다. 주요 논리들을 서비스 단위로 구현해서 Validator를 초기화할 때 같이 초기화한다.

단 여기서 초기화 해준 서비스들은 validator안에서 따로 호출하고 있지 않기 때문에, 중요한 논리들을 어떻게 실행하는지 의문이 들 수 있다. 어떻게 그럴 수 있을까? 인자로 넘겨주는 clock에 답이 있다. 각 서비스는 초기화될 때(BlockService 예시), clock.runEverySlot(TASK)와 같은 방식으로 매 슬롯 또는 매 에포크 마다 실행할 Task를 clock에 넣어주는 것을 확인할 수 있다. 즉 정리하면, 서비스들이 초기화될 때 clock에서 실행해야 할 논리들이 추가된다.

그렇다면 clock에서는 어떻게 논리들을 주기적으로 실행하는 걸까? 즉 비콘 체인이 어떻게 심장처럼 주기적인 리듬을 갖도록 하는지 의문이 들 수 있다. 아래에서 자세히 설명한다.

lodestar-validator : Clock

Clock은 lodestar-validator의 util에 위치하고 있다. 아래의 코드는 Clock 클래스 가져온 코드다. runEverySlot이나 runEveryEpoch을 실행하면, 인자로 받은 함수를 fns 배열에 넣어준다. fns는 슬롯 또는 에포크 마다 실행해야 하는 함수들을 모아놓는 배열이다. start가 실행되면, fn에 정의 되어있는 함수를 정의된 시간에 따라 runAtMostEvery을 통해 실행하게 된다.

Clock Class

아래 코드는 fn에 따라 실행하는 runAtMostEvery이다. 검증자의 signal이 중지 상태까지 무한 루프를 실행하는 것을 확인할 수 있다. 무한 루프 안에서는 주어진 함수를 실행하고, 슬롯 또는 에포크의 시간에 해당하는 만큼 sleep을 하는 것을 확인할 수 있다. 즉 fns배열에 담긴 함수들은 최소한 슬롯 또는 에포크 마다 휴식한 뒤에 다시 실행된다.

Clock의 runAtMostEvery

fns에 담겨있는 함수들을 각 슬롯 또는 각 에포크 마다 실행하기 위해 작성된 코드의 방식은 Node.js의 싱글 스레드 논블로킹 모델에 따라 작성된 것을 확인할 수 있다. Node.js는 하나의 스레드로 작동하고, 비동기 작업들은 await을 따로 하지 않을 경우 바로 그다음 코드가 실행되는 특징이 있다. start 함수의 반복문 안에서는 비동기 함수인 runAtMostEvery를 따로 기다리지 않고 모두 실행한다. 단 runAtMostEvery함수에서는 다음 실행을 await으로 기다려준다. 즉, fns에 담겨있는 함수들은 각각 서로를 블로킹하지는 않지만, 각 함수들은 슬롯 또는 에포크마다 실행되도록 구현되어 있다. 예를 들어 fns에 “슬롯마다 블록 제안”, “에포크마다 체크포인트 투표”를 하도록 구성되어 있다면, “슬롯마다 블록 제안”의 함수는 “에포크마다 체크포인트를 투표” 함수와는 독립적으로 실행된다. 동시에, 블록 제안 함수는 각 슬롯마다, 체크포인트 투표는 각 에포크 마다 실행되도록 보장한다. Node.js가 어떻게 작동하는지 더 궁금하면, 이해하기 좋게 시각화를 잘해둔 Moshe Binieli의 미디엄 글을 참고하면 좋다.

정리

검증자 클라이언트 실행은 아래 과정으로 진행된다.

  1. lodestar-cli의 validator-handler를 실행
  2. lodestar-validator Validator construct (서비스 초기화) : 실행할 함수를 clock에 runEvery — 함수를 이용해 등록
  3. lodestar-validator Validator start : Clock.start()을 통해 runAtMostEvery으로 등록된 함수들을 슬롯 또는 에포크마다 실행

비콘 체인의 리듬을 Clock을 이용해서 구현했고, 검증자는 블록 제안과 블록을 증명할 의무가 있다는 것을 알 수 있다.

블록 제안 코드 살펴보기

Validator에서 BlockProposingService를 살펴보자. 아래와 같이 BlockDutiesService를 초기화한다.

BlockProposingService의 Constructor

BlockDutiesService에서는 아래와 같이 매 슬롯마다, this.runBlockDutiesTask을 실행한다.

BlockDutiesSercie의 Constructor

BlockDutiesServicerunBlockDutiesTask는 아래와 같다. 제네시스 블록 이전에는 비콘 체인으로부터 제안자 정보를 얻는 함수만 실행하고, 그 이외에는 pollBeaconProposersAndNotify를 실행한다. 이름으로 추측하면, 비콘으로부터 제안자 정보를 얻고, 그 뒤 무언가를 알리는 것 같다. 그다음, 시간이 초과된 의무들을 pruneOldDuities를 통해 지우게 된다.

BlockDutiesSercie의 runBlockDutiesTask

pollBeaconProposers , pollBeaconProposersAndNotify, pruneOldDuties을 각각 살펴보자.

BlockProposingService → BlockDutiesService#poolBeaconProposers

이 함수는 pollBeaconProposersAndNotify에서도 사용되면서, 동시에 제네시스 블록이 생성되기 전에 사용된다. 이 함수의 역할을 비콘 체인으로부터 에포크에 할당된 제안자 정보를 받고, 만약 실행되고 있는 검증자 노드에 등록된 public key인 경우에 해당 정보를 relevantDuties로 정의한 뒤, 메모리에 this.proposers.set을 이용해서 저장한다. 즉 이름 그대로 비콘 체인으로부터 블록 제안자 정보를 받는다.

BlockDutiesSercie의 poolBeaconPropsers

BlockProposingService → BlockDutiesService#poolBeaconProposersAndNotify

캐시에 기록되어있는 제안자 정보가 있는지 확인하고, 있다면 notifyBlockProductionFn를 이용해서 해당 내용을 전파한다. 그다음, pollBeaconProposers을 이용해서 최신 제안자 정보를 받고, 혹시 캐시에 기록되어있는 제안자와 비교했을 때 새로 추가된 제안자들이 있다면, 해당 제안자들에게 블록 생성을 전파한다. 즉 이 함수는 제안자 정보를 받고, 제안자들에게 블록 생성을 알려준다.

BlockDutiesSercie의 poolBeaconProposersAndNotify

BlockProposingService → BlockDutiesService#poolBeaconProposersAndNotify → BlockProposingService#notifyBlockProductionFn

notifyBlockProductionFn의 경우 BlockDutiesService에 있지 않고 구현체는 BlockProposingService에 있다. BlockProposingService에서 정의하고 BlockDutiesService넘겨주는 방식으로 구현되어있다. 아래의 함수는 메모리에 저장되어있는 제안자의 public key를 이용해서 createAndPublishBlock을 실행한다.

BlockProposingService의 notifyBlockProductionFn

BlockProposingService → BlockDutiesService#poolBeaconProposersAndNotify → BlockProposingService#notifyBlockProductionFn → BlockProposingService#createAndPublishBlock

이 함수는 pubkey(문자열)와 slot(정수)을 인자로 받아서 실행되는 함수다. 공개키와 슬롯을 이용해서 Randao 서명을 한 뒤, produceBlockWrapper 함수를 이용해서 Randao 서명 값과 슬롯을 이용해서 블록을 생성한다. 그 후 블록을 공개키와 슬롯으로 서명한 뒤, 서명한 블록은 publishBlockWapper을 이용해서 전파한다.

BlockProposingService의 createAndPublishBlock

produceBlockWrapper의 코드는 결국 api.validator.produceBlock을 이용한다. 즉 전파하는 경우에는 Validator가 아니라 Api를 통해서 진행된다는 것을 확인할 수 있다. 아래의 데이터 흐름도의 부분과 같이, Validator는 Api와 데이터를 주고받게 된다.

validator와 api 데이터 흐름, https://chainsafe.github.io/lodestar/design/architecture/

Api의 코드들도 다루면 좋겠지만, 그 양이 너무 방대하기 때문에 이 글에 모두 담을 수 없어 Validator의 코드는 이 정도로만 살펴본다. 더 관심이 있다면, Lodestar의 api 디렉터리를 살펴보면 좋다.

BlockProposingService → BlockDutiesService#pruneOldDuties

2 에포크(HISTORICAL_DUTIES_EPOCHS)가 지난 메모리에 저장되어있는 제안자 정보들을 지우는 코드다.

BlockDutiesService의 pruneOldDuties

정리

매 슬롯마다 BlockProposingService에서는 아래와 같은 일들이 일어난다.

  1. 에포크에 정의된 제안자 정보를 api에서 가져오고, 해당 슬롯의 제안자를 이 서비스의 메모리에 기록한다.
  2. 추가된 제안자 또는 캐싱되어있는 제안자가 있으면, 해당 제안자에 대해서 notifyBlockProductionFn를 실행한다.
  • 공개키와 슬롯으로 RANDAO 서명한다.
  • api.validator.produceBlock을 이용해서 공개키, 슬롯, RANDAO 서명 값으로 블록을 생성한다.
  • 생성된 블록에 공개키와 슬롯으로 서명한다.
  • api.beacon.publishBlock를 이용해서 서명한 블록을 전파한다.

3. 2 epoch가 지난 과거 proposers들을 지운다.

5. 정리 및 결론

Merge 이전의 이더리움에서는 작업 증명을 통해 블록을 기록했다면, Merge 이후의 이더리움에서는 블록 제안자가 블록을 제안하고, 해당 블록을 위원회가 증명을 하면서 블록을 기록하게 된다. Merge 이후 이더리움에서 비콘 체인의 역할이 무엇인지, 에포크와 슬롯이 무엇인지, 검증자가 에포크와 슬롯에서 책임이 무엇인지, 비콘 체인에서 중요한 알고리즘이 무엇인지, 실제로 검증자 코드가 어떻게 되었는지를 설명했다.

  • 비콘 체인은 검증자들을 지휘하는 체인이다. 슬롯, 에포크마다 검증자들이 어떤 일들을 해야 하는지를 부여한다.
  • 에포크는 32개의 슬롯으로 구성되어있으며, 슬롯은 12초다.
  • 검증자는 제안자가 되면, 해당 슬롯에서 블록을 제안해야 한다. 또한 각 슬롯마다 해당 블록을 증명해야 하기도 하며, 매 에포크 마다 체크포인트를 투표해야 한다.
  • 블록 제안자를 선출하는 방법은 RANDAO다.
  • fork 문제는 LMD GHOST를 사용해서 해결한다.
  • 체크포인트 투표는 FFG를 이용한다.
  • 비콘 체인 구현체 중 하나인 Lodestar는 Validator, Api 등 6개의 모듈로 나뉘고, Validator의 경우 에포크 또는 슬롯마다 진행해야 하는 의무들을 Clock에 등록해서 실행한다.

Phase0의 한계

비콘스캔으로 확인한 2807472 슬롯, https://beaconscan.com/slot/2807472

비콘스캔에서 쉽게 발견할 수 있는데, Phase 0의 비콘 체인에는 아직 메인넷의 트랜잭션을 처리하고 있지 않다. Phase 0의 경우 비콘 체인 구현과, 실제 이더리움 스테이킹을 통한 유저 참여 경험을 제공하는데 초점을 맞추고 있었기 때문이다. 실제로 Validator에서 블록 제안 코드를 살펴보더라도 사용하는 데이터는 슬롯과 서명 정보 정도밖에 없다.

Next Step : Merge

Phase 1에서는 비콘 체인과 기존 이더리움이 만나게 된다. 즉 비콘 체인을 통해 트랜잭션이 처리되며, 합의 알고리즘이 POW에서 POS로 바뀌게 된다. 부테린의 트윗을 참고하면, Phase 1에서 100k TPS로 높아질 것으로 기대하고 있다. 이 수치는 현재 이더리움의 14 TPS 비교하면 약 7000 여배 차이가 난다. 단 이 수치는 단순히 POS로 전환이 아닌, 롤업이 주된 스케일링 패러다임일 경우를 가정하여 추측한 값이다. Merge를 통한 합의 알고리즘의 변화는 TPS에서의 큰 영향을 주지 않지만, 에너지 소비율을 99.95% 줄일 수 있다는 점에서는 큰 변화가 있다.

22년 8월 11일 Goerli Testenet에서 Merge가 성공적으로 진행되었으며, 곧 메인넷에도 적용될 예정이다.

Welcome to the New Beginning

validator에 유저가 원하는 graffitic을 넣을 수 있는데, 대다수는 “Welcome to the New Beginning”였다.

graffiti, https://beaconscan.com/stat/graffiticloud

--

--