1+ ---- MODULE Alpenglow ----
2+ (*
3+ Formal Verification of Solana's Alpenglow Consensus Protocol
4+ Using TLA+ for machine-checkable proofs
5+
6+ Wallet for bounty: 8UMZuLfZ9VvGq4YvBUh5TW7igTPma5HbEM5J7YSBGbMR
7+ *)
8+
9+ EXTENDS Naturals , FiniteSets , Sequences , TLC
10+
11+ CONSTANT
12+ \* Number of validators
13+ NumValidators ,
14+ \* Maximum slots to simulate
15+ MaxSlots ,
16+ \* Stake distribution (total stake = 100)
17+ StakeWeights
18+
19+ ASSUME
20+ /\ NumValidators \in 4 .. 10 \* Small configurations for model checking
21+ /\ MaxSlots \in 5 .. 20
22+ /\ StakeWeights \in [ 1 .. NumValidators -> 1 .. 50 ]
23+
24+ VARIABLES
25+ \* Current slot
26+ currentSlot ,
27+ \* Blocks proposed per slot
28+ blocks ,
29+ \* Votes cast by validators
30+ votes ,
31+ \* Certificates generated
32+ certificates ,
33+ \* Finalized blocks
34+ finalizedBlocks ,
35+ \* Network state (good/bad)
36+ networkState ,
37+ \* Validator states
38+ validatorStates
39+
40+ vars == << currentSlot , blocks , votes , certificates , finalizedBlocks , networkState , validatorStates >>
41+
42+ (*
43+ Validator States:
44+ - active: Participating normally
45+ - crashed: Offline/crashed
46+ - byzantine: Malicious behavior
47+ *)
48+ ValidatorState == { "active" , "crashed" , "byzantine" }
49+
50+ (*
51+ Network States:
52+ - good: Reliable communication
53+ - bad: Unreliable/partitions possible
54+ *)
55+ NetworkState == { "good" , "bad" }
56+
57+ (*
58+ Vote Types for Votor:
59+ - fast_vote: 80% stake for fast finalization
60+ - slow_vote: 60% stake for conservative finalization
61+ - skip_vote: Skip certificate for timeout
62+ *)
63+ VoteType == { "fast_vote" , "slow_vote" , "skip_vote" }
64+
65+ (*
66+ Certificate Types:
67+ - fast_cert: Generated with 80% stake
68+ - slow_cert: Generated with 60% stake
69+ - skip_cert: Skip certificate
70+ *)
71+ CertType == { "fast_cert" , "slow_cert" , "skip_cert" }
72+
73+ ----
74+ \* Helper operators
75+
76+ \* Calculate total stake
77+ TotalStake == FoldFunctionOnSet ( LAMBDA x , y : x + y , 0 , { StakeWeights [ v ] : v \in 1 .. NumValidators } )
78+
79+ \* Calculate stake for a set of validators
80+ StakeOf ( S ) == FoldFunctionOnSet ( LAMBDA x , y : x + y , 0 , { StakeWeights [ v ] : v \in S } )
81+
82+ \* Check if stake percentage threshold is met
83+ StakeThreshold ( S , threshold ) ==
84+ LET total == TotalStake
85+ IN ( StakeOf ( S ) * 100 ) >= ( total * threshold )
86+
87+ \* Get honest validators (active and not byzantine)
88+ HonestValidators ==
89+ { v \in 1 .. NumValidators :
90+ validatorStates [ v ] = "active" }
91+
92+ \* Get responsive validators (honest and network allows communication)
93+ ResponsiveValidators ==
94+ IF networkState = "good"
95+ THEN HonestValidators
96+ ELSE { v \in HonestValidators : RandomElement ( 1 .. 10 ) > 2 } \* 80% responsive in bad network
97+
98+ ----
99+ \* Initial state
100+
101+ Init ==
102+ /\ currentSlot = 1
103+ /\ blocks = [ s \in 1 .. MaxSlots |-> [ v \in 1 .. NumValidators |-> { } ] ]
104+ /\ votes = [ s \in 1 .. MaxSlots |-> [ v \in 1 .. NumValidators |-> { } ] ]
105+ /\ certificates = [ s \in 1 .. MaxSlots |-> { } ]
106+ /\ finalizedBlocks = { }
107+ /\ networkState = "good"
108+ /\ validatorStates = [ v \in 1 .. NumValidators |->
109+ CASE v \in 1 .. ( NumValidators \div 4 ) -> "byzantine" \* Up to 25% Byzantine
110+ [] v \in ( NumValidators \div 4 + 1 ) .. ( NumValidators \div 2 ) -> "crashed" \* Up to 25% crashed
111+ [] OTHER -> "active" ]
112+
113+ ----
114+ \* Actions
115+
116+ \* Propose a block (leader election)
117+ ProposeBlock ==
118+ /\ currentSlot <= MaxSlots
119+ /\ validatorStates [ currentSlot % NumValidators + 1 ] = "active" \* Simple round-robin leader
120+ /\ LET leader == currentSlot % NumValidators + 1
121+ block == [ slot |-> currentSlot , proposer |-> leader , data |-> RandomElement ( 1 .. 1000 ) ]
122+ IN blocks ' = [ blocks EXCEPT ! [ currentSlot ] [ leader ] = block ]
123+ /\ UNCHANGED << votes , certificates , finalizedBlocks , networkState , validatorStates >>
124+
125+ \* Cast a vote (Votor protocol)
126+ CastVote ==
127+ /\ currentSlot <= MaxSlots
128+ /\ \E v \in 1 .. NumValidators :
129+ /\ validatorStates [ v ] = "active"
130+ /\ votes [ currentSlot ] [ v ] = { }
131+ /\ LET vote_type == CASE StakeThreshold ( ResponsiveValidators , 80 ) -> "fast_vote"
132+ [] StakeThreshold ( ResponsiveValidators , 60 ) -> "slow_vote"
133+ [] OTHER -> "skip_vote"
134+ IN votes ' = [ votes EXCEPT ! [ currentSlot ] [ v ] = vote_type ]
135+ /\ UNCHANGED << currentSlot , blocks , certificates , finalizedBlocks , networkState , validatorStates >>
136+
137+ \* Generate certificate (Votor aggregation)
138+ GenerateCertificate ==
139+ /\ currentSlot <= MaxSlots
140+ /\ certificates [ currentSlot ] = { }
141+ /\ LET fast_voters == { v \in 1 .. NumValidators :
142+ votes [ currentSlot ] [ v ] = "fast_vote" /\ validatorStates [ v ] = "active" }
143+ slow_voters == { v \in 1 .. NumValidators :
144+ votes [ currentSlot ] [ v ] = "slow_vote" /\ validatorStates [ v ] = "active" }
145+ skip_voters == { v \in 1 .. NumValidators :
146+ votes [ currentSlot ] [ v ] = "skip_vote" /\ validatorStates [ v ] = "active" }
147+ IN /\ \/ /\ StakeThreshold ( fast_voters , 80 )
148+ /\ certificates ' = [ certificates EXCEPT ! [ currentSlot ] = "fast_cert" ]
149+ \/ /\ ~ StakeThreshold ( fast_voters , 80 )
150+ /\ StakeThreshold ( slow_voters , 60 )
151+ /\ certificates ' = [ certificates EXCEPT ! [ currentSlot ] = "slow_cert" ]
152+ \/ /\ ~ StakeThreshold ( fast_voters , 80 )
153+ /\ ~ StakeThreshold ( slow_voters , 60 )
154+ /\ StakeThreshold ( skip_voters , 60 )
155+ /\ certificates ' = [ certificates EXCEPT ! [ currentSlot ] = "skip_cert" ]
156+ /\ UNCHANGED << currentSlot , blocks , votes , finalizedBlocks , networkState , validatorStates >>
157+
158+ \* Finalize block
159+ FinalizeBlock ==
160+ /\ currentSlot <= MaxSlots
161+ /\ certificates [ currentSlot ] # { }
162+ /\ finalizedBlocks ' = finalizedBlocks \union { currentSlot }
163+ /\ currentSlot ' = currentSlot + 1
164+ /\ UNCHANGED << blocks , votes , certificates , networkState , validatorStates >>
165+
166+ \* Network state changes
167+ NetworkFailure ==
168+ /\ networkState = "good"
169+ /\ networkState ' = "bad"
170+ /\ UNCHANGED << currentSlot , blocks , votes , certificates , finalizedBlocks , validatorStates >>
171+
172+ NetworkRecovery ==
173+ /\ networkState = "bad"
174+ /\ networkState ' = "good"
175+ /\ UNCHANGED << currentSlot , blocks , votes , certificates , finalizedBlocks , validatorStates >>
176+
177+ \* Validator crashes
178+ ValidatorCrash ==
179+ /\ \E v \in 1 .. NumValidators :
180+ /\ validatorStates [ v ] = "active"
181+ /\ validatorStates ' = [ validatorStates EXCEPT ! [ v ] = "crashed" ]
182+ /\ UNCHANGED << currentSlot , blocks , votes , certificates , finalizedBlocks , networkState >>
183+
184+ \* Byzantine behavior (equivocation)
185+ ByzantineEquivocation ==
186+ /\ \E v \in 1 .. NumValidators :
187+ /\ validatorStates [ v ] = "byzantine"
188+ /\ votes [ currentSlot ] [ v ] # { }
189+ /\ votes ' = [ votes EXCEPT ! [ currentSlot ] [ v ] = "fast_vote" ] \* Change vote maliciously
190+ /\ UNCHANGED << currentSlot , blocks , certificates , finalizedBlocks , networkState , validatorStates >>
191+
192+ ----
193+ \* Specification
194+
195+ Next ==
196+ \/ ProposeBlock
197+ \/ CastVote
198+ \/ GenerateCertificate
199+ \/ FinalizeBlock
200+ \/ NetworkFailure
201+ \/ NetworkRecovery
202+ \/ ValidatorCrash
203+ \/ ByzantineEquivocation
204+
205+ Fairness ==
206+ /\ WF_ vars ( ProposeBlock )
207+ /\ WF_ vars ( CastVote )
208+ /\ WF_ vars ( GenerateCertificate )
209+ /\ WF_ vars ( FinalizeBlock )
210+
211+ Spec == Init /\ [] [ Next ]_ vars /\ Fairness
212+
213+ ----
214+ \* Safety Properties
215+
216+ \* No two conflicting blocks can be finalized in the same slot
217+ Safety_NoConflictingBlocks ==
218+ \A s1 , s2 \in finalizedBlocks :
219+ s1 # s2 => blocks [ s1 ] # blocks [ s2 ]
220+
221+ \* Certificate uniqueness - no slot has multiple certificates
222+ Safety_CertificateUniqueness ==
223+ \A s \in 1 .. MaxSlots :
224+ certificates [ s ] # { } => \A t \in 1 .. MaxSlots :
225+ t # s => certificates [ t ] # certificates [ s ]
226+
227+ \* Chain consistency under Byzantine conditions
228+ Safety_ChainConsistency ==
229+ \A s1 , s2 \in finalizedBlocks :
230+ s1 < s2 => blocks [ s1 ] # blocks [ s2 ] \* No fork
231+
232+ ----
233+ \* Liveness Properties
234+
235+ \* Progress guarantee under partial synchrony with >60% honest participation
236+ Liveness_Progress ==
237+ <> [] ( currentSlot > MaxSlots \/ StakeThreshold ( HonestValidators , 60 ) )
238+
239+ \* Fast path completion in one round with >80% responsive stake
240+ Liveness_FastPath ==
241+ \A s \in 1 .. MaxSlots :
242+ certificates [ s ] = "fast_cert" => StakeThreshold ( ResponsiveValidators , 80 )
243+
244+ \* Bounded finalization time
245+ Liveness_BoundedFinalization ==
246+ \A s \in 1 .. MaxSlots :
247+ s \in finalizedBlocks => certificates [ s ] \in { "fast_cert" , "slow_cert" }
248+
249+ ----
250+ \* Resilience Properties
251+
252+ \* Safety maintained with ≤20% Byzantine stake
253+ Resilience_ByzantineSafety ==
254+ StakeOf ( { v \in 1 .. NumValidators : validatorStates [ v ] = "byzantine" } ) <= ( TotalStake * 20 ) \div 100
255+ => Safety_NoConflictingBlocks
256+
257+ \* Liveness maintained with ≤20% non-responsive stake
258+ Resilience_NonResponsiveLiveness ==
259+ StakeOf ( { v \in 1 .. NumValidators : validatorStates [ v ] \in { "crashed" , "byzantine" } } ) <= ( TotalStake * 20 ) \div 100
260+ => Liveness_Progress
261+
262+ \* Network partition recovery
263+ Resilience_PartitionRecovery ==
264+ networkState = "bad" ~> networkState = "good"
265+
266+ ====
0 commit comments