Light Clients
This document traces the complete light client flow from initialization through state verification, including finality proofs, validator set transitions, state proofs, and historical block verification.
Overview
Section titled “Overview”Light clients verify blockchain state without executing transactions. They rely on:
- Finality proofs - BLS threshold signatures proving block finalization
- Merkle proofs - Cryptographic proofs for state values
- MMR proofs - Historical block inclusion proofs
- DAS - Data availability sampling (optional)
Initialize with trusted context --> Verify finality proofs -->Track validator transitions --> Query state with proofs --> Verify historical blocksLight Client State Machine
Section titled “Light Client State Machine”State Structure
Section titled “State Structure”pub struct LightClientState { pub context: LightClientContext, // Current trusted validator set pub pending_update: Option<PendingUpdate>, // Announced validator set change pub current_epoch: u64, pub latest_height: u64,}
pub struct LightClientContext { pub validator_set: ValidatorSet, pub aggregate_key_proof: ValidatorProof, // Merkle proof for aggregate key pub version: u32,}Initialization
Section titled “Initialization”let context = LightClientContext { validator_set: trusted_validator_set, aggregate_key_proof: build_aggregate_key_proof(&validator_set), version: validator_set_version,};
let state = LightClientState::new(context, initial_epoch, initial_height);Finality Proof Verification
Section titled “Finality Proof Verification”Three-Step Verification
Section titled “Three-Step Verification”pub fn verify_finality_proof( proof: &FinalityProof, ctx: &LightClientContext,) -> Result<(), LightClientError> { // Step 1: Validator set commitment let root = ctx.commitment_root(); if root != proof.header.validator_set_id.id { return Err(LightClientError::ValidatorSetIdMismatch); }
// Step 2: Aggregate key Merkle proof if ctx.aggregate_key_proof.leaf_index != 0 { return Err(LightClientError::InvalidMembershipProof); } if !ctx.aggregate_key_proof.verify(&root) { return Err(LightClientError::InvalidMembershipProof); }
// Step 3: BLS signature verification if !proof.verify(&ctx.validator_set.aggregate_bls_pubkey) { return Err(LightClientError::InvalidSignature); }
Ok(())}FinalityProof Structure
Section titled “FinalityProof Structure”pub struct FinalityProof { pub header: BlockHeader, pub epoch: u64, pub view: u64, pub parent_view: u64, pub key_version: u32, pub certificate: FinalityCertificateBytes, // Threshold BLS signature}Validator Set Commitment
Section titled “Validator Set Commitment”Merkle Tree Structure
Section titled “Merkle Tree Structure” [Root] / \ [Internal] [Internal] / \ / \[AggKey Leaf] [Val 0] [Val 1] [Val 2]Commitment Root Computation
Section titled “Commitment Root Computation”pub fn commitment_root(&self) -> Hash { let mut leaves = Vec::with_capacity(self.validators.len() + 1);
// Leaf 0: aggregate BLS key leaves.push(self.aggregate_key_leaf_hash());
// Leaves 1..n: individual validators for i in 0..self.validators.len() { leaves.push(self.validator_leaf_hash(i)?); }
merkle_root(&leaves)}Validator Set Transitions
Section titled “Validator Set Transitions”Announcement Phase
Section titled “Announcement Phase”When a block announces a validator set change:
pub fn process_finality_proof(&mut self, proof: &FinalityProof) -> Result<()> { // Verify against current context verify_finality_proof(proof, &self.context)?;
// Check for validator set change announcement if proof.header.next_validator_set_id != proof.header.validator_set_id { self.pending_update = Some(PendingUpdate { next_validator_set_id: proof.header.next_validator_set_id.clone(), announced_at_epoch: proof.header.epoch, announced_at_height: proof.header.height, }); }
self.current_epoch = proof.header.epoch; self.latest_height = proof.header.height; Ok(())}State Proof Verification
Section titled “State Proof Verification”Proof Types
Section titled “Proof Types”| Proof Type | Purpose |
|---|---|
SlotProof | Storage slot existence/non-existence |
PositionProof | Merkle position in state tree |
NonMembershipProof | Key doesn’t exist (left/right neighbors) |
ContractQmdbProof | Full contract state proof |
Verification Flow
Section titled “Verification Flow”- Validate QMDB ops sequence
- Recompute state root from ops
- Verify bounded proof
- Verify slot proof (if present)
MMR for Historical Proofs
Section titled “MMR for Historical Proofs”Structure
Section titled “Structure”pub struct FinalizedMMR { leaves: Vec<FinalizedEntry>, height_index: BTreeMap<u64, u64>, // height -> position}
pub struct FinalizedMmrProof { pub entry: FinalizedEntry, pub leaf_position: u64, pub leaf_count: u64, pub siblings: Vec<([u8; 32], bool)>, // (hash, is_left_of_path)}Proof Verification
Section titled “Proof Verification”pub fn verify(&self, expected_root: &[u8; 32]) -> bool { let mut current = leaf_hash(&self.entry);
for (sibling, is_left) in &self.siblings { current = if *is_left { sha256(concat![b"mmr_internal_v1", sibling, ¤t]) } else { sha256(concat![b"mmr_internal_v1", ¤t, sibling]) }; }
current == *expected_root}RPC Endpoints
Section titled “RPC Endpoints”| Method | Purpose |
|---|---|
NodeRpcV1.finality_proof | Get finality proof for a height |
NodeRpcV1.light_client_context | Get validator set context |
NodeRpcV1.state_proof | Get state proof for address/slot |
NodeRpcV1.finalized_history_root | Get current MMR root |
NodeRpcV1.finalized_history_proof | Get MMR inclusion proof |
CLI Verification
Section titled “CLI Verification”# Verify a block is in the finalized historynode verify --height 100
# Verify with a specific RPC endpointnode verify --height 100 --rpc-url http://localhost:3030
# Output as JSON (for scripting)node verify --height 100 --format jsonVerification Invariants
Section titled “Verification Invariants”| Invariant | Check |
|---|---|
| Validator Set | commitment_root() == header.validator_set_id.id |
| Aggregate Key | Leaf index 0, correct hash, valid Merkle path |
| BLS Signature | Certificate verifies with aggregate key |
| Epoch Boundary | Transitions only at epoch > announced_epoch |
| Version Monotonic | New version >= announced version |
| QMDB Ordering | Positions 0 to last_loc in sequence |
| Leaf Digests | Preimage hash matches proof leaf |
| MMR Path | Siblings form valid path to root |
Related
Section titled “Related”- Finality - Finality guarantees
- Consensus - BLS threshold signing
- Storage - Merkle tree structure
- Data Availability - DAS for light clients
- RPC API - Proof endpoints