π» LMD-GHOST fork choice algorithm
A deep dive into how the LMD-GHOST (Latest Message Driven, Greedy Heaviest Observed SubTree) fork choice algorithm works. LMD-GHOST is the fork choice rule used by Ethereumβs consensus layer and its derivatives. Each validatorβs latest attestation is their single active vote, and the algorithm follows the heaviest branch at every fork.
This document is implementation-agnostic, with ethlambda-specific details called out in blockquotes marked βIn ethlambdaβ.
Much of the conceptual framing in this document is inspired by Ben Edgingtonβs Eth2 Book, particularly the LMD GHOST chapter. Highly recommended reading for anyone interested in Ethereum consensus.
Background & History
The GHOST protocol was introduced by Sompolinsky and Zohar in a 2013 paper. Its core idea: instead of choosing the heaviest chain, we choose the heaviest subtree, counting orphaned blocks as evidence of support for their ancestors.
The βLMDβ in LMD-GHOST stands for Latest Message Driven: only each validatorβs most recent attestation counts, preventing vote amplification. LMD-GHOST is the fork choice rule used by the Ethereum Beacon Chain and Lean Ethereum.
Why Fork Choice?
In a distributed system where validators propose blocks concurrently, the blockchain can fork: two valid blocks may appear at the same slot, creating competing chains. The fork choice rule answers a critical question:
Which chain tip should I follow?
ββββββββββββ
ββββββΆβ Block C β β Chain tip 1
β β slot 5 β
ββββββββββββ β ββββββββββββ
β Block A βββ€
β slot 3 β β ββββββββββββ
ββββββββββββ ββββββΆβ Block D β β Chain tip 2
β slot 5 β
ββββββββββββ
Which tip should validators follow?
Every node in the network must be able to independently arrive at the same answer using only its local view of blocks and attestations. The fork choice rule is what makes this possible. It is a deterministic function from a nodeβs observed state to a single chain tip.
From Heaviest Chain to Heaviest Subtree
The simplest fork choice rule is heaviest chain: follow the chain tip with the most accumulated weight. This works when fork rates are low, but breaks down when honest validators fork within a common branch:
HEAVIEST CHAIN vs HEAVIEST SUBTREE
ββββββββββββββββββββββββββββββββββ
An attacker with 40% of stake forks at A.
The honest majority (60%) builds on B but forks into C and D:
ββββBβββ¬ββC V0, V1, V2 vote for C (30%)
A βββββ€ βββD V3, V4, V5 vote for D (30%)
β
ββββXββYββZ V6, V7, V8, V9 vote for Z (40%)
Heaviest chain:
Z has 40% of votes, C and D each have 30%.
Attacker wins! β
Heaviest subtree (LMD-GHOST):
At A: B subtree has 60% (C + D), X subtree has 40%.
Pick B. Then at B: C has 30%, D has 30% (tiebreaker).
Honest majority wins. β
LMD-GHOST is strictly better when honest validators fork within a common subtree. Instead of requiring all honest validators to agree on a single chain tip (which is impossible under network delay), it aggregates their support at each level of the tree.
How Subtree Weight Works (the βGHOSTβ Part)
The key insight behind the βHeaviest Observed SubTreeβ part of LMD-GHOST: a vote for a block is implicitly a vote for all its ancestors.
When a validator attests to block F as their head, they are also expressing support for every block on the path from the root to F:
Validator attests: head = F
A ββ B ββ C ββ D ββ E ββ F
β² β² β² β² β² β²
β β β β β β
ββββββ΄βββββ΄βββββ΄βββββ΄βββββ
All ancestors implicitly supported
This is why LMD-GHOST counts the subtree weight: a blockβs weight includes every attestation for any of its descendants, because those attestations implicitly endorse the ancestor too. The algorithm exploits this by walking backward from each attested head and incrementing every block along the path.
LMD: Why Only the Latest Message?
The βLMDβ in LMD-GHOST stands for Latest Message Driven. Each validatorβs most recent attestation is their only vote. All previous attestations are discarded.
Validator 7's attestation history:
Slot 10: attests to head = B β discarded
Slot 11: attests to head = C β discarded
Slot 12: attests to head = E β THIS is the active vote
Only the slot 12 attestation counts for fork choice.
Why only the latest? Two reasons:
-
Prevents double-voting. If all messages counted, a validator could cast many attestations and amplify their influence. With LMD, each validator gets exactly one active vote regardless of how many attestations theyβve broadcast.
-
Reflects current knowledge. A validatorβs latest attestation reflects their most recent view of the chain. Older attestations may reference blocks that are no longer on the best chain. Keeping only the latest ensures fork choice uses the most up-to-date information.
The fork choice store maintains a mapping of validator_index β latest attestation.
When a new attestation arrives from a validator, it replaces their previous entry:
Fork choice store (latest messages):
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β Validator β Latest Attestation β
ββββββββββββββββΌβββββββββββββββββββββββββββββββ€
β 0 β head=E, target=C, source=A β
β 1 β head=D, target=C, source=A β
β 2 β head=E, target=C, source=A β
β 3 β head=F, target=D, source=A β
β ... β ... β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββ
One row per validator. New attestation β overwrite row.
LMD-GHOST Step by Step
The algorithm takes a set of inputs and produces a single block root: the head of the chain.
Inputs
| Input | Purpose |
|---|---|
| Start root | The justified checkpoint (root of the subtree to search) |
| Block tree | The set of known blocks: root β (slot, parent) |
| Attestations | Latest message per validator: validator_index β attestation |
| Min score | Minimum weight for a branch to be considered (0 = follow any branch; higher = conservative) |
In ethlambda: The function is
compute_lmd_ghost_head()incrates/blockchain/fork_choice/src/lib.rs. The block tree comes from theLiveChainstorage index, andmin_scoreis 0 for head selection or β2V/3β for safe target computation.
The Algorithm
First, accumulate weights. Each attestation βpaintsβ the path from its head back to the start root. In the simplest form (equal-weight validators), this adds +1 to every block on the path. In systems with balance-weighted voting, the validatorβs effective balance is added instead.
Validator 0 attests to head = F
J β A β B β C β D β E β F (J = justified root)
+1 +1 +1 +1 +1 +1 J is at start_slot, not counted
Validator 1 attests to head = D
J β A β B β C β D
+1 +1 +1 +1
Accumulated weights:
Block: J A B C D E F
Weight: β 2 2 2 2 1 1
β
β start_root (not weighted, used as the descent origin)
In ethlambda: All validators have equal weight (+1 per vote). The Ethereum Beacon Chain instead weights votes by effective balance (up to 2048 ETH).
Then, greedily descend. Starting from the start root, at each node pick the child with the most weight. Repeat until reaching a leaf:
J βββ¬ββ B (5) β pick B (higher weight)
βββ G (2)
B βββ¬ββ C (3) β pick C (higher weight)
βββ H (2)
C ββββ D (3) β only child, continue
D ββ (no children) β HEAD = D!
Children below min_score are ignored during the descent. With min_score = 0
(normal head selection) all children are visible. With a higher threshold, only
branches with strong support are followed. This is used for
safe target selection.
The Tiebreaker
When two children have exactly equal weight, a deterministic tiebreaker is needed. Without one, different nodes could pick different heads from the same data, breaking consensus. The tiebreaker is lexicographically higher block root hash, i.e., higher hash value wins.
Equal weight scenario:
Parent
β
βββββ΄ββββ
B (3) C (3) β Equal weight!
root: root:
0x3a.. 0x7f.. β 0x7f > 0x3a, so pick C
The choice of βhigher hash winsβ is a convention. Any deterministic rule would work; what matters is that all nodes apply the same one.
Worked Example: Head Selection
Consider a network with 5 validators (indices 0β4) and the following block tree
rooted at the justified checkpoint J at slot 10:
BLOCK TREE
ββββββββββ
Slot 10 ββββββββ
(justified) β J β β Justified checkpoint (start_root)
ββββ¬ββββ
β
Slot 11 ββββ΄ββββ
β A β
ββββ¬ββββ
ββββ΄βββββββββ
β β
Slot 12 ββββ΄ββββ ββββ΄ββββ
β B β β C β
ββββ¬ββββ ββββ¬ββββ
β β
Slot 13 ββββ΄ββββ ββββ΄ββββ
β D β β E β
ββββββββ ββββββββ
Latest attestations (one per validator):
| Validator | Attested Head | Path back from head to J |
|---|---|---|
| 0 | D | D β B β A β (J) |
| 1 | D | D β B β A β (J) |
| 2 | E | E β C β A β (J) |
| 3 | E | E β C β A β (J) |
| 4 | E | E β C β A β (J) |
Accumulate weights by walking backward from each attested head, adding +1 per block (stopping at Jβs slot):
V0 (head=D): D+1 B+1 A+1
V1 (head=D): D+1 B+1 A+1
V2 (head=E): E+1 C+1 A+1
V3 (head=E): E+1 C+1 A+1
V4 (head=E): E+1 C+1 A+1
| Block | Weight | Explanation |
|---|---|---|
| A | 5 | On path of all 5 validators |
| B | 2 | On path of V0, V1 |
| C | 3 | On path of V2, V3, V4 |
| D | 2 | Head of V0, V1 |
| E | 3 | Head of V2, V3, V4 |
Greedily descend from J, always picking the heaviest child:
Start at J
βββΆ A (only child, weight 5)
βββ B (weight 2)
βββ C (weight 3) β Pick C (3 > 2)
βββΆ E (only child, weight 3)
βββΆ No children β HEAD = E β
Result: The canonical head is Block E. Even though both branches have the same depth, the CβE branch has 3 votes vs BβDβs 2 votes.
RESOLVED HEAD
βββββββββββββ
Slot 10 ββββββββ
β J β
ββββ¬ββββ
β
Slot 11 ββββ΄ββββ
β A β β canonical
ββββ¬ββββ
ββββ΄βββββββββ
β β
Slot 12 ββββ΄ββββ ββββ΄ββββ
β B β β C β β canonical (weight 3 > 2)
ββββ¬ββββ ββββ¬ββββ
β β
Slot 13 ββββ΄ββββ ββββ΄ββββ
β D β β E β β
HEAD
ββββββββ ββββββββ
What If a Vote Changes?
Suppose validator 1 now sees block E and switches their attestation from D to E:
Before: V0=D, V1=D, V2=E, V3=E, V4=E β Head = E (3 vs 2)
After: V0=D, V1=E, V2=E, V3=E, V4=E β Head = E (4 vs 1)
The head didn't change, but the margin increased from 1 to 3.
If instead V2 and V3 had switched to D:
After: V0=D, V1=D, V2=D, V3=D, V4=E β Head = D (4 vs 1)
The head reorgs from E to D.
Fork Choice vs Finality
An important conceptual distinction: LMD-GHOST provides fork choice, not finality.
LMD-GHOST gives the network a way to agree on the current head of the chain at any moment, but the head can change. A block selected by fork choice today could be reorged away tomorrow if attestations shift. LMD-GHOST alone provides no guarantee that any block is permanent.
Finality, the guarantee that a block can never be reverted, comes from a separate mechanism called a finality gadget. LMD-GHOST is designed to compose with any finality gadget (e.g., Casper FFG in the Ethereum Beacon Chain, or 3SF-mini in Lean Ethereum).
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CONSENSUS = TWO LAYERS β
β β
β βββββββββββββββ ββββββββββββββββββββββββ β
β β LMD-GHOST β β Finality Gadget β β
β β β β β β
β β "Which tip β β "Which blocks are β β
β β is best β β permanent and can β β
β β right now?"β β never be reverted?" β β
β β β β β β
β β Dynamic, β β Monotonic, only β β
β β can reorg β β moves forward β β
β ββββββββ¬βββββββ ββββββββββββ¬ββββββββββββ β
β β β β
β ββββββββββββ¬ββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββ β
β β Full Consensus β β
β ββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
In ethlambda: The finality gadget is 3SF-mini, which operates at the slot level rather than epoch boundaries.
The two layers interact: LMD-GHOST runs its greedy descent starting from the latest justified checkpoint (not genesis). This means finality constrains fork choice: once a checkpoint is finalized, no fork choice run will ever consider blocks before it.
βββββββββββ βββββββββββ βββββ ...
βFINALIZEDββββββββββΆβJUSTIFIEDββββββββββΆβ fork choice
β slot 50 β β slot 55 β β runs here
βββββββββββ βββββββββββ βββββ ...
β β
β βββ start_root for LMD-GHOST
β
βββ everything before this is permanent
This has a major practical benefit: finality allows aggressive pruning of the block tree. Without finality, fork choice would need to consider every block since genesis, and the tree would grow without bound. With finality, all blocks at or before the finalized checkpoint can be discarded from the fork choiceβs working set.
In ethlambda: The
LiveChainindex (the in-memory block tree used by fork choice) is pruned every time finalization advances, keeping it bounded to only the non-finalized portion of the chain.
Attestation Pipeline
In a naive implementation, every attestation would influence fork choice the instant it arrives. This creates problems: validators with faster network connections see different heads than slower ones, and the proposerβs view of the chain could shift mid-block-construction.
Lean Ethereum solves this with a staged promotion pipeline: attestations are collected into a pending set and only promoted to the active fork choice set at designated moments. This ensures all validators operate on a consistent view.
ATTESTATION LIFECYCLE
βββββββββββββββββββββ
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Network β β Pending β β Active β
β (gossip) ββββββββΆβ Attestations ββββββββΆβ Attestations β
β β β β β β
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β β
NOT used for Used for fork choice
fork choice weight calculations
β β
Promoted at ββββββββββββββΆ designated intervals
fixed points
In ethlambda: The two stages are called βnewβ and βknownβ attestations, stored in
LatestNewAttestationsandLatestKnownAttestationstables respectively. Promotion happens at tick intervals 0 (if proposing) and 3 (end of slot).
Why Staged Promotion?
The staged design serves two purposes:
-
Consistency: All validators promote attestations at the same moments, reducing divergence in head selection. Without batching, validators with faster network connections would see different heads than slower ones.
-
Proposer fairness: The proposer computes the block against a known, fixed set of attestations. If new attestations could influence fork choice mid-computation, different validators might disagree on the head.
On-Chain vs Off-Chain Attestations
Attestations arrive from two sources, and how they enter the pipeline matters:
| Source | Enters As | Reason |
|---|---|---|
| Network gossip | Pending | Must wait for promotion window |
| Block body (on-chain) | Active | Already consensus-validated |
| Proposerβs own attestation | Pending | Prevents proposer weight advantage |
The proposerβs own attestation enters as pending (not active) deliberately. If it were immediately active, the proposer would gain an unfair weight advantage for their own block, a circular dependency where proposing a block gives you an extra vote toward making that block canonical.
Safe Target Selection
The safe target is a conservative head computed with a high weight threshold.
It constrains the target field in attestations, which feeds into
3SF-mini for justification and finalization decisions. Validators
still vote for the newest head they see (regular LMD-GHOST with min_score = 0)
in the head field. The safe target only affects which blocks can progress
toward finality. It is computed by running the same LMD-GHOST algorithm but with
a non-zero min_score in the filtering phase.
SAFE TARGET vs HEAD
ββββββββββββββββββββ
Regular head (min_score = 0):
Follow heaviest branch, even with a slim margin
βββ B (3 votes) β HEAD (3 > 2)
J ββ A βββ€
βββ C (2 votes)
Safe target (min_score = β2V/3β):
Only follow branches with supermajority support
V = 5 validators, threshold = β10/3β = 4
βββ B (3 votes) β Below threshold (3 < 4), pruned
J ββ A βββ€
βββ C (2 votes) β Below threshold (2 < 4), pruned
Safe target = A (no children pass threshold)
This means the safe target lags behind the head. It only advances when a branch accumulates overwhelming support, making it resistant to temporary fluctuations:
Timeline of safe target vs head:
Slot: 10 11 12 13 14 15 16
Head: J A B D D E F
Safe: J J J A A A D
β
Safe target is always βββββ
at or behind the head
The safe target prevents 3SF-mini from finalizing unstable branches: without it, a slim-majority fork could reach justification and finalization before the network converges. By requiring supermajority support for the target, only branches with strong consensus can progress toward finality, even though validatorsβ head votes freely follow the newest chain tip.
Reorgs
A reorg (reorganization) occurs when the fork choice head switches from one branch to another. This happens when a competing branch accumulates more attestation weight than the current headβs branch.
REORG SCENARIO
ββββββββββββββ
Before (head = D):
βββ B ββ D β
HEAD (weight 4)
J ββ A βββ€
βββ C ββ E (weight 3)
New attestations arrive, 3 validators switch to E:
βββ B ββ D (weight 2)
J ββ A βββ€
βββ C ββ E β
HEAD (weight 5) β REORG!
The canonical chain changed from JβAβBβD to JβAβCβE
Blocks B and D are no longer canonical (but remain in the block tree).
Reorgs are normal during transient network conditions but should be rare in stable operation. They cannot cross a finalization boundary: once a block is finalized, it is permanently part of the canonical chain.
In ethlambda: Reorgs are detected by checking whether the old and new heads share a common prefix, and tracked via Prometheus metrics (
lean_fork_choice_reorgs_total).
LMD-GHOST Variants
LMD-GHOST is one of several variants that have been proposed and studied. Understanding the design space helps explain why LMD was chosen.
| Variant | Full Name | What Counts | Trade-off |
|---|---|---|---|
| IMD | Immediate Message Driven | All attestations ever | Maximizes data but creates unbounded storage and is vulnerable to long-range rewriting |
| LMD | Latest Message Driven | Only each validatorβs most recent attestation | Good balance: one vote per validator, reflects current view, bounded storage |
| FMD | Fresh Message Driven | Only attestations from current/previous epoch | Prevents very old attestations from influencing fork choice, but validators who go offline lose influence immediately |
| RLMD | Recent Latest Message Driven | Latest attestation, but only if within N epochs | Parameterized compromise between LMD and FMD; tunable staleness threshold |
The Ethereum consensus mini-spec originally used IMD-GHOST but switched to LMD in November 2018 due to superior stability properties.
IMD: All attestations count LMD: Only latest counts
V0: slot 5 β head B V0: slot 5 β head B (overwritten)
V0: slot 8 β head C V0: slot 8 β head C β active
V0: slot 11 β head E V0: slot 11 β head E β active
V0 contributes 3 votes! V0 contributes 1 vote.
Validators who attest more Equal influence regardless
often have outsized influence. of attestation frequency.
ethlambda Implementation Reference
This section covers ethlambda-specific details: scheduling, Beacon Chain differences, source code locations, and performance.
Tick-Based Scheduling
ethlambda divides time into 4-second slots, each split into 4 intervals (1 second each). Fork choice operations are scheduled at specific intervals:
ONE SLOT (4 seconds)
ββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ
β Interval 0 β Interval 1 β Interval 2 β Interval 3 β
β (t+0s) β (t+1s) β (t+2s) β (t+3s) β
ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββ€
β β β β β
β IF PROPOSER: β NON-PROPOSER:β update_safe β accept_new β
β accept new β produce β _target() β _attestationsβ
β attestationsβ attestation β β () β
β + propose β β (2/3 vote β β
β block β β threshold) β update_head()β
β β β β β
β update_head()β β β β
β β β β β
ββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ
ββββββββββββββββ Slot N βββββββββββββββββββββββββββββββββββββββΊ
Detailed sequence:
Interval 0 β Slot boundary
β
βββ Am I the proposer for this slot?
β βββ YES: promote new β known attestations
β β run fork choice β update_head()
β β build block using known attestations
β β publish block to network
β βββ NO: (wait for block from proposer)
β
Interval 1 β Attestation production
β
βββ Non-proposers:
β βββ Create attestation with:
β β’ head = current fork choice head (newest head)
β β’ target = derived from safe_target (for 3SF-mini)
β β’ source = latest_justified checkpoint
β Publish attestation to gossipsub
β
Interval 2 β Safe target update
β
βββ Recalculate safe_target using 2/3 supermajority threshold
β βββ Only blocks with β₯ β2V/3β attestation weight qualify
β (V = total validators)
β
Interval 3 β End of slot
β
βββ Promote new β known attestations
βββ Run fork choice β update_head()
Differences from the Ethereum Beacon Chain
ethlambda is a lean consensus client with several simplifications compared to the Ethereum Beacon Chain:
| Aspect | ethlambda | Ethereum Beacon Chain |
|---|---|---|
| Vote weight | Equal: 1 vote per validator | Proportional to effective balance (up to 32 ETH) |
| Proposer boost | None | Yes: newly proposed blocks get temporary bonus weight |
| Equivocation handling | Not in fork choice | Equivocating validatorsβ weight excluded |
| Attestation frequency | Every slot | Once per epoch |
| Committee structure | All validators attest each slot | Validators split into per-slot committees |
| Slot duration | 4 seconds | 12 seconds |
No proposer boost. The Beacon Chain adds a βproposer boostβ, a temporary weight bonus given to newly proposed blocks to prevent balancing attacks. ethlambda does not implement this. Instead, proposer fairness is handled through the two-stage attestation pipeline (the proposerβs own attestation enters as βnewβ, not βknownβ).
No balance weighting. In the Beacon Chain, a validator with 32 ETH of effective balance has more fork choice weight than one with 16 ETH. In ethlambda, every validator has exactly equal weight (1 vote = 1 unit of weight), simplifying the algorithm and analysis.
No equivocation discounting. The Beacon Chainβs fork choice detects validators who equivocate (attest to conflicting blocks in the same slot) and excludes their weight. This addresses the βnothing at stakeβ problem where validators can costlessly vote for multiple forks. ethlambda does not implement this in its fork choice.
Key Files
| File | Component |
|---|---|
crates/blockchain/fork_choice/src/lib.rs | Core LMD-GHOST algorithm (compute_lmd_ghost_head) |
crates/blockchain/src/store.rs | Store: head update, safe target, attestation promotion |
crates/blockchain/src/lib.rs | BlockChain actor: tick scheduling, interval dispatch |
crates/common/types/src/attestation.rs | AttestationData type (head, target, source, slot) |
crates/common/types/src/state.rs | Checkpoint (root + slot), State |
crates/storage/src/api/ | LiveChain table, StorageBackend trait |
Data Flow Summary
βββββββββββββ ββββββββββββββββ βββββββββββββββββ
β Gossipsub ββββββββββΆβ New βββ(promote)ββΆβ Known β
β (network) β β Attestations β β Attestations β
βββββββββββββ ββββββββββββββββ βββββββββ¬ββββββββ
β
βββββββββββββ β
β LiveChain βββββ { root β (slot, parent) } ββββββββββββββββ€
β (index) β β
βββββββββββββ β
βΌ
βββββββββββββββββββ
βββββββββββββ β compute_lmd_ β
β Justified βββββ start_root ββββββββββββββββΆβ ghost_head() β
βCheckpoint β β β
βββββββββββββ ββββββββββ¬βββββββββ
β
ββββββββ΄βββββββ
β β
βΌ βΌ
ββββββββββββ βββββββββββββ
β HEAD β β SAFE β
β (min=0) β β TARGET β
ββββββββββββ β (min=2V/3)β
βββββββββββββ
Performance Characteristics
| Operation | Time Complexity | Description |
|---|---|---|
| Weight accumulation | O(A Γ D) | A = attestations, D = max chain depth from justified root |
| Greedy descent | O(D Γ B) | D = depth, B = max branching factor |
| Attestation promotion | O(V) | V = total validators |
| LiveChain lookup | O(B) | B = non-finalized blocks |
In practice with a small validator set and bounded non-finalized chain length,
all operations complete in sub-millisecond time. The // TODO: add proto-array implementation comment in the source indicates a future optimization path:
proto-array is an O(1) amortized fork choice algorithm used by most Beacon Chain
clients.