Alpenglow Verifier
TLA+ Specification
Formal model of the Alpenglow consensus protocol.
Alpenglow.tla
This TLA+ specification models the core components of the Alpenglow protocol, including Votor, Rotor, and certificate logic.
---------------- MODULE AlpenglowConsensus ----------------
EXTENDS Naturals, FiniteSets, Sequences, TLC
* Hackathon Submission: Alpenglow Formal Verification System
* Complete TLA+ specification covering all hackathon requirements
CONSTANTS
Nodes, * Set of all nodes in the network
TotalStake, * Total stake in the network
Quorum80, * 80% stake threshold for fast path
Quorum60, * 60% stake threshold for slow path
ByzantineNodes, * Set of Byzantine (malicious) nodes
MaxSlot, * Maximum slot number for model checking
Delta * Network delay bound
VARIABLES
* Votor State - Dual-path voting mechanism
votes, * votes[slot][node] = set of blocks voted for
finalized, * finalized[slot] = finalized block
* Rotor State - Erasure-coded block propagation
block_proposals, * block_proposals[slot] = set of proposed blocks
received_blocks, * received_blocks[node] = set of received blocks
relay_graph, * Stake-weighted sampling for block relay
* Certificate State - Aggregation and uniqueness
certificates, * certificates[slot] = certificate if exists
skip_certificates, * skip_certificates[slot] = skip cert if exists
* Network and Timing State
leader, * Current leader for the slot
slot, * Current slot number
network_time, * Global network time
node_clocks, * Local clocks for each node
partitioned_nodes * Set of nodes in network partition
* Stake distribution with Byzantine tolerance
stake == [n \in Nodes |->
IF n \in ByzantineNodes
THEN TotalStake * 0.15 / Cardinality(ByzantineNodes) \\ Max 20% Byzantine
ELSE TotalStake * 0.85 / Cardinality(Nodes \\ ByzantineNodes)]
vars == << votes, finalized, block_proposals, received_blocks, relay_graph,
certificates, skip_certificates, leader, slot, network_time,
node_clocks, partitioned_nodes >>
* ============================================================================
* INITIALIZATION
* ============================================================================
Init ==
/\\ Votor initialization
/\\ votes = [sl \in 1..MaxSlot |-> [n \in Nodes |-> {}]]
/\\ finalized = [sl \in {} |-> ""]
/\\ Rotor initialization
/\\ block_proposals = [sl \in 1..MaxSlot |-> {}]
/\\ received_blocks = [n \in Nodes |-> {}]
/\\ relay_graph = [n \in Nodes |-> {}]
/\\ Certificate initialization
/\\ certificates = [sl \in {} |-> {}]
/\\ skip_certificates = [sl \in {} |-> {}]
/\\ Network initialization
/\\ leader = CHOOSE n \in Nodes : n \notin ByzantineNodes
/\\ slot = 1
/\\ network_time = 0
/\\ node_clocks = [n \in Nodes |-> 0]
/\\ partitioned_nodes = {}
* ============================================================================
* VOTOR: DUAL-PATH VOTING MECHANISM
* ============================================================================
* Honest node voting with equivocation prevention
VotorVote(node, block, sl) ==
/\\ Preconditions
/\\ node \notin ByzantineNodes
/\\ sl <= MaxSlot
/\\ votes[sl][node] = {} \\ No double voting
/\\ block \in received_blocks[node] \\ Must have received block
/\\ State updates
/\\ votes' = [votes EXCEPT ![sl][node] = {block}]
/\\ UNCHANGED << finalized, block_proposals, received_blocks, relay_graph,
certificates, skip_certificates, leader, slot,
network_time, node_clocks, partitioned_nodes >>
* Byzantine node equivocation (double voting)
ByzantineDoubleVote(node, block1, block2, sl) ==
/\\ Preconditions
/\\ node \in ByzantineNodes
/\\ sl <= MaxSlot
/\\ block1 /= block2
/\\ {block1, block2} \subseteq received_blocks[node]
/\\ Byzantine behavior: vote for multiple blocks
/\\ votes' = [votes EXCEPT ![sl][node] = {block1, block2}]
/\\ UNCHANGED << finalized, block_proposals, received_blocks, relay_graph,
certificates, skip_certificates, leader, slot,
network_time, node_clocks, partitioned_nodes >>
* Fast path finalization (80% stake)
FastPathFinalize(block, sl) ==
/\\ Preconditions
/\\ sl \notin DOMAIN finalized
/\\ LET voters == {n \in Nodes : block \in votes[sl][n]}
total_stake == \sum_{n \in voters} stake[n]
IN total_stake >= Quorum80
/\\ State updates
/\\ finalized' = finalized @@ (sl :> block)
/\\ certificates' = certificates @@ (sl :> [block |-> block, quorum |-> "Fast80",
voters |-> voters, timestamp |-> network_time])
/\\ UNCHANGED << votes, block_proposals, received_blocks, relay_graph,
skip_certificates, leader, slot, network_time,
node_clocks, partitioned_nodes >>
* Slow path finalization (60% stake)
SlowPathFinalize(block, sl) ==
/\\ Preconditions
/\\ sl \notin DOMAIN finalized
/\\ sl \notin DOMAIN certificates \\ No fast path certificate
/\\ network_time >= node_clocks[leader] + 2 * Delta \\ Timeout condition
/\\ LET voters == {n \in Nodes : block \in votes[sl][n]}
total_stake == \sum_{n \in voters} stake[n]
IN total_stake >= Quorum60
/\\ State updates
/\\ finalized' = finalized @@ (sl :> block)
/\\ certificates' = certificates @@ (sl :> [block |-> block, quorum |-> "Slow60",
voters |-> voters, timestamp |-> network_time])
/\\ UNCHANGED << votes, block_proposals, received_blocks, relay_graph,
skip_certificates, leader, slot, network_time,
node_clocks, partitioned_nodes >>
* ============================================================================
* ROTOR: ERASURE-CODED BLOCK PROPAGATION
* ============================================================================
* Leader proposes block for current slot
ProposeBlock(node, block, sl) ==
/\\ Preconditions
/\\ node = leader
/\\ sl = slot
/\\ \lnot \E prop \in block_proposals[sl] : prop.proposer = node
/\\ State updates
/\\ block_proposals' = [block_proposals EXCEPT ![sl] =
block_proposals[sl] \cup {[proposer |-> node, block |-> block]}]
/\\ UNCHANGED << votes, finalized, received_blocks, relay_graph,
certificates, skip_certificates, leader, slot,
network_time, node_clocks, partitioned_nodes >>
* Stake-weighted relay sampling for erasure coding
StakeWeightedRelay(sender, receiver, block, sl) ==
/\\ Preconditions
/\\ sender \in Nodes
/\\ receiver \in Nodes
/\\ sender /= receiver
/\\ block \in received_blocks[sender]
/\\ receiver \notin partitioned_nodes \\ Not partitioned
/\\ Probabilistic relay based on stake weight
/\\ LET relay_prob == stake[receiver] / TotalStake
IN relay_prob > 0.1 \\ Simplified sampling condition
/\\ State updates
/\\ received_blocks' = [received_blocks EXCEPT ![receiver] =
received_blocks[receiver] \cup {block}]
/\\ relay_graph' = [relay_graph EXCEPT ![sender] =
relay_graph[sender] \cup {receiver}]
/\\ UNCHANGED << votes, finalized, block_proposals, certificates,
skip_certificates, leader, slot, network_time,
node_clocks, partitioned_nodes >>
* ============================================================================
* CERTIFICATE GENERATION AND SKIP LOGIC
* ============================================================================
* Generate skip certificate when no block can be finalized
GenerateSkipCertificate(sl) ==
/\\ Preconditions
/\\ sl \notin DOMAIN finalized
/\\ sl \notin DOMAIN certificates
/\\ network_time >= node_clocks[leader] + 3 * Delta \\ Extended timeout
/\\ \lnot \E block \in UNION {block_proposals[sl]} :
LET voters == {n \in Nodes : block \in votes[sl][n]}
IN \sum_{n \in voters} stake[n] >= Quorum60
/\\ State updates
/\\ skip_certificates' = skip_certificates @@ (sl :> [reason |-> "timeout",
timestamp |-> network_time])
/\\ UNCHANGED << votes, finalized, block_proposals, received_blocks,
relay_graph, certificates, leader, slot, network_time,
node_clocks, partitioned_nodes >>
* ============================================================================
* LEADER ROTATION AND TIMING
* ============================================================================
* Advance to next slot with leader rotation
AdvanceSlot ==
/\\ Preconditions
/\\ slot < MaxSlot
/\\ \/ slot \in DOMAIN finalized
\/ slot \in DOMAIN skip_certificates
\/ network_time >= node_clocks[leader] + 4 * Delta
/\\ State updates
/\\ slot' = slot + 1
/\\ leader' = CHOOSE n \in Nodes : n \notin ByzantineNodes \\ Simplified rotation
/\\ network_time' = network_time + Delta
/\\ node_clocks' = [n \in Nodes |-> network_time + Delta]
/\\ UNCHANGED << votes, finalized, block_proposals, received_blocks,
relay_graph, certificates, skip_certificates, partitioned_nodes >>
* Network partition simulation
NetworkPartition ==
/\\ Preconditions
/\\ Cardinality(partitioned_nodes) = 0 \\ No existing partition
/\\ \E partition \subseteq Nodes :
/\\ Cardinality(partition) <= Cardinality(Nodes) / 3
/\\ \sum_{n \in partition} stake[n] <= TotalStake * 0.2
/\\ State updates
/\\ partitioned_nodes' = CHOOSE partition \subseteq Nodes :
Cardinality(partition) <= Cardinality(Nodes) / 3
/\\ UNCHANGED << votes, finalized, block_proposals, received_blocks,
relay_graph, certificates, skip_certificates, leader,
slot, network_time, node_clocks >>
* ============================================================================
* TRANSITION RELATION
* ============================================================================
Next ==
\/ \E n \in Nodes, b \in {"block1", "block2", "block3"}, sl \in 1..MaxSlot :
VotorVote(n, b, sl)
\/ \E n \in ByzantineNodes, b1, b2 \in {"block1", "block2"}, sl \in 1..MaxSlot :
ByzantineDoubleVote(n, b1, b2, sl)
\/ \E b \in {"block1", "block2", "block3"}, sl \in 1..MaxSlot :
FastPathFinalize(b, sl)
\/ \E b \in {"block1", "block2", "block3"}, sl \in 1..MaxSlot :
SlowPathFinalize(b, sl)
\/ \E n \in Nodes, b \in {"block1", "block2", "block3"}, sl \in 1..MaxSlot :
ProposeBlock(n, b, sl)
\/ \E s, r \in Nodes, b \in {"block1", "block2", "block3"}, sl \in 1..MaxSlot :
StakeWeightedRelay(s, r, b, sl)
\/ \E sl \in 1..MaxSlot : GenerateSkipCertificate(sl)
\/ AdvanceSlot
\/ NetworkPartition
* ============================================================================
* SAFETY PROPERTIES (Hackathon Requirement 1)
* ============================================================================
* No two conflicting blocks finalized in same slot
NoConflictingBlocksFinalized ==
\A sl \in DOMAIN finalized :
Cardinality({finalized[sl]}) = 1
* Chain consistency under Byzantine faults
ChainConsistencyUnderByzantineFaults ==
\A sl1, sl2 \in DOMAIN finalized :
(sl1 < sl2) => (finalized[sl1] /= finalized[sl2] \/ sl1 = sl2)
* Certificate uniqueness
CertificateUniqueness ==
\A sl \in DOMAIN certificates :
Cardinality({certificates[sl]}) <= 1
* No equivocation by honest nodes
NoEquivocation ==
\A n \in Nodes \\ ByzantineNodes :
\A sl \in 1..MaxSlot :
Cardinality(votes[sl][n]) <= 1
* ============================================================================
* LIVENESS PROPERTIES (Hackathon Requirement 2)
* ============================================================================
* Progress with honest supermajority
ProgressWithHonestSupermajority ==
LET honest_stake == \sum_{n \in Nodes \\ ByzantineNodes} stake[n]
IN (honest_stake >= TotalStake * 0.6) =>
<>[]\E sl \in 1..MaxSlot : sl \in DOMAIN finalized
* Fast path completion guarantee
FastPathCompletion ==
LET responsive_nodes == Nodes \\ partitioned_nodes
responsive_stake == \sum_{n \in responsive_nodes} stake[n]
IN (responsive_stake >= TotalStake * 0.8) =>
<>\E sl \in 1..MaxSlot :
sl \in DOMAIN certificates /\\ certificates[sl].quorum = "Fast80"
* Bounded finalization time
BoundedFinalizationTime ==
\A sl \in 1..MaxSlot :
(sl \in DOMAIN finalized) =>
(certificates[sl].timestamp <= node_clocks[leader] + 2 * Delta)
* ============================================================================
* RESILIENCE PROPERTIES (Hackathon Requirement 3)
* ============================================================================
* Safety with up to 20% Byzantine stake
SafetyWithByzantineStake ==
LET byzantine_stake == \sum_{n \in ByzantineNodes} stake[n]
IN (byzantine_stake <= TotalStake * 0.2) => NoConflictingBlocksFinalized
* Liveness with up to 20% offline stake
LivenessWithOfflineStake ==
LET offline_stake == \sum_{n \in partitioned_nodes} stake[n]
IN (offline_stake <= TotalStake * 0.2) => ProgressWithHonestSupermajority
* Network partition recovery
RecoveryFromPartition ==
[](Cardinality(partitioned_nodes) > 0) =>
<>((partitioned_nodes = {}) /\\ <>\E sl \in 1..MaxSlot : sl \in DOMAIN finalized)
* ============================================================================
* SPECIFICATION
* ============================================================================
Spec == Init /\\ [][Next]_vars /\\ WF_vars(AdvanceSlot)
* Fairness constraints for liveness
Fairness ==
/\\ WF_vars(AdvanceSlot)
/\\ \A n \in Nodes \\ ByzantineNodes : WF_vars(\E b, sl : VotorVote(n, b, sl))
/\\ WF_vars(\E b, sl : FastPathFinalize(b, sl))
/\\ WF_vars(\E b, sl : SlowPathFinalize(b, sl))
=======================================================