Workspace IndexAlgorithms › Day 59

Casper FFG + LMD-GHOST — The Conditions for Reorganization TODO

Algorithms · Day 59 / 100 · D. Distributed Systems & Consensus (Day 52-68)

Concept

Ethereum's consensus splits into two parts: LMD-GHOST is the fork-choice rule that decides which chain to follow, and Casper FFG is the rule layered on top that grants finality. LMD-GHOST counts only each validator's most recent vote and picks the head by descending into the subtree with the greatest weight. Casper FFG votes on checkpoints at epoch boundaries: once two-thirds or more of stake supports a given link, that checkpoint becomes justified, and once a chain of justifications holds, it becomes finalized. Reorganizations naturally occur only within the not-yet-finalized region, arising from network delay, validator dropout, or voting timing when the majority weight shows up late. Reverting a finalized checkpoint requires contradictory votes, which is punished by slashing — this is what's known as accountable safety.

This is where the answer to "how many blocks do I need to wait before it's safe" comes from, and treating pre-finality data as if it were already final leaves state out of sync when a reorg happens.

Code & Formula

# Casper FFG + LMD-GHOST — 체크포인트가 스테이크 2/3 이상의 지지를 받으면 justified,
# 연속된 justification이 성립하면 finalized. finalize 전 구간에서만 reorg가 일어난다.

class Checkpoint:
    def __init__(self, epoch, parent=None):
        self.epoch = epoch
        self.parent = parent
        self.justified = False
        self.finalized = False

def cast_votes(checkpoint, stake_fraction_voting):
    """스테이크의 stake_fraction_voting 만큼이 이 체크포인트로의 링크에 투표"""
    checkpoint.justified = stake_fraction_voting >= (2 / 3)
    if checkpoint.justified and checkpoint.parent and checkpoint.parent.justified:
        # 부모도 justified고, 부모->자신 링크가 justified면 부모가 finalized 됨
        checkpoint.parent.finalized = True

genesis = Checkpoint(0)
genesis.justified = True  # genesis는 항상 justified로 취급

epoch1 = Checkpoint(1, parent=genesis)
epoch2 = Checkpoint(2, parent=epoch1)
epoch3 = Checkpoint(3, parent=epoch2)

# 정상 케이스: 매 에포크 2/3 이상 투표
cast_votes(epoch1, stake_fraction_voting=0.90)
cast_votes(epoch2, stake_fraction_voting=0.85)

print("정상 진행:")
for cp in [genesis, epoch1, epoch2]:
    print(f"  epoch {cp.epoch}: justified={cp.justified}, finalized={cp.finalized}")

# 문제 케이스: epoch3는 네트워크 지연으로 2/3 미달 -> justified 실패
cast_votes(epoch3, stake_fraction_voting=0.55)
print(f"\nepoch 3: justified={epoch3.justified}, finalized={epoch3.finalized} "
      f"(2/3 미달 -> 아직 확정 안 됨, 이 구간은 reorg 가능)")

# LMD-GHOST 헤드 선택: justified 여부와 무관하게 최근 투표 가중치가 큰 서브트리를 따라간다
votes_on_epoch2_children = {"blockA(epoch3)": 0.55, "blockB(epoch3, competing)": 0.30}
head = max(votes_on_epoch2_children, key=votes_on_epoch2_children.get)
print(f"\nLMD-GHOST 헤드 선택 (epoch2 이후 미확정 구간): "
      f"{votes_on_epoch2_children} -> head = {head}")
print("-> finalized 체크포인트(epoch1, epoch2)는 슬래싱 없이는 뒤집을 수 없지만,")
print("   미확정 헤드는 투표 가중치가 바뀌면 자연스럽게 reorg 될 수 있다.")

Exercise

Query the beacon chain API for the justified and finalized checkpoints of a recent epoch along with the current head slot, and convert the gap between head and finality into a time unit.

Practical Connection

A system like Verex that combines on-chain settlement with off-chain indexing needs to decide when to treat an event as final, and that threshold is exactly what bounds the scope of rollback logic when a reorg occurs.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

Casper FFG + LMD-GHOST TODO

Algorithms · Day 59 / 100 · D. 분산시스템·합의 (Day 52–68)

재조직(reorg)의 조건

개념

이더리움의 합의는 두 요소로 나뉘는데, LMD-GHOST는 어느 체인을 따를지 고르는 포크 선택 규칙이고 Casper FFG는 그 위에서 최종성을 부여하는 규칙이다. LMD-GHOST는 각 검증자의 가장 최근 투표만 세어 가중치가 가장 큰 서브트리를 따라 내려가며 헤드를 정한다. Casper FFG는 에포크 경계의 체크포인트를 대상으로 투표하며, 스테이크의 3분의 2 이상이 어떤 링크를 지지하면 그 체크포인트가 justified 되고, 연속된 justification이 성립하면 finalized 된다. 재조직은 아직 finalize되지 않은 구간에서만 자연스럽게 일어나며, 네트워크 지연·검증자 이탈·투표 타이밍 때문에 다수 가중치가 늦게 드러날 때 발생한다. finalized 체크포인트를 뒤집으려면 서로 모순되는 투표가 필요하고, 이는 슬래싱으로 처벌되는 책임 있는 안전성(accountable safety) 구조이다.

"몇 블록 기다려야 안전한가"라는 질문의 답이 여기서 나오고, 확정 전 데이터를 확정된 것처럼 다루면 reorg 때 상태가 어긋난다.

코드 · 수식

# Casper FFG + LMD-GHOST — 체크포인트가 스테이크 2/3 이상의 지지를 받으면 justified,
# 연속된 justification이 성립하면 finalized. finalize 전 구간에서만 reorg가 일어난다.

class Checkpoint:
    def __init__(self, epoch, parent=None):
        self.epoch = epoch
        self.parent = parent
        self.justified = False
        self.finalized = False

def cast_votes(checkpoint, stake_fraction_voting):
    """스테이크의 stake_fraction_voting 만큼이 이 체크포인트로의 링크에 투표"""
    checkpoint.justified = stake_fraction_voting >= (2 / 3)
    if checkpoint.justified and checkpoint.parent and checkpoint.parent.justified:
        # 부모도 justified고, 부모->자신 링크가 justified면 부모가 finalized 됨
        checkpoint.parent.finalized = True

genesis = Checkpoint(0)
genesis.justified = True  # genesis는 항상 justified로 취급

epoch1 = Checkpoint(1, parent=genesis)
epoch2 = Checkpoint(2, parent=epoch1)
epoch3 = Checkpoint(3, parent=epoch2)

# 정상 케이스: 매 에포크 2/3 이상 투표
cast_votes(epoch1, stake_fraction_voting=0.90)
cast_votes(epoch2, stake_fraction_voting=0.85)

print("정상 진행:")
for cp in [genesis, epoch1, epoch2]:
    print(f"  epoch {cp.epoch}: justified={cp.justified}, finalized={cp.finalized}")

# 문제 케이스: epoch3는 네트워크 지연으로 2/3 미달 -> justified 실패
cast_votes(epoch3, stake_fraction_voting=0.55)
print(f"\nepoch 3: justified={epoch3.justified}, finalized={epoch3.finalized} "
      f"(2/3 미달 -> 아직 확정 안 됨, 이 구간은 reorg 가능)")

# LMD-GHOST 헤드 선택: justified 여부와 무관하게 최근 투표 가중치가 큰 서브트리를 따라간다
votes_on_epoch2_children = {"blockA(epoch3)": 0.55, "blockB(epoch3, competing)": 0.30}
head = max(votes_on_epoch2_children, key=votes_on_epoch2_children.get)
print(f"\nLMD-GHOST 헤드 선택 (epoch2 이후 미확정 구간): "
      f"{votes_on_epoch2_children} -> head = {head}")
print("-> finalized 체크포인트(epoch1, epoch2)는 슬래싱 없이는 뒤집을 수 없지만,")
print("   미확정 헤드는 투표 가중치가 바뀌면 자연스럽게 reorg 될 수 있다.")

연습

비콘 체인 API로 최근 에포크의 justified·finalized 체크포인트와 현재 헤드 슬롯을 조회해, 헤드와 최종성 사이의 거리를 시간 단위로 환산해 보기.

실무 · Verex 연결

Verex처럼 온체인 정산과 오프체인 인덱싱을 함께 쓰는 시스템은 이벤트를 언제 확정으로 간주할지 정해야 하고, 그 기준선이 곧 reorg 발생 시 롤백 로직의 범위가 된다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 58. 나카모토 합의의 확률적 최종성과 selfish mining60. 싱글슬롯 파이널리티와 서명 집계 병목 →