|
| 1 | +/- |
| 2 | +Strictly-forward checkpoint replacement. |
| 3 | +
|
| 4 | +Mirrors `src/lean_spec/spec/forks/lstar/state_transition.py` in leanSpec: |
| 5 | + - `Checkpoint.advance_to` replaces a checkpoint only when the candidate's |
| 6 | + slot is strictly higher (`containers/checkpoint.py`; modeled inline as |
| 7 | + the strict comparison in `applyJustification`). |
| 8 | + - The finalization arm of `process_attestations` replaces |
| 9 | + `latest_finalized` only under the `finalized < source.slot` guard. |
| 10 | +
|
| 11 | +ST-3/ST-6 bound only the checkpoint *slots*: they admit a transition that |
| 12 | +swaps a checkpoint to a different root at the same slot. This file closes |
| 13 | +that gap: across a successful transition each of `latest_justified` / |
| 14 | +`latest_finalized` is either unchanged as a whole value — root included — |
| 15 | +or replaced by a checkpoint at a strictly higher slot. The one designed |
| 16 | +exception is genesis anchoring (the first block fills in its parent root |
| 17 | +at slot 0), excluded by the `latestBlockHeader.slot ≠ 0` hypothesis. |
| 18 | +
|
| 19 | +Cross-branch root *ancestry* is deliberately out of the STF's reach: |
| 20 | +leanEthereum/leanSpec#1182 documents on `Checkpoint.advance_to` that |
| 21 | +"selection is by slot only" and on `Store.latest_finalized` that the |
| 22 | +ancestry is a separate store invariant. |
| 23 | +
|
| 24 | +Proves ST-7 from `docs/lean4-proof-propositions.md`: |
| 25 | + - ST-7: `State.transition s b = .ok s'` with a non-genesis latest header |
| 26 | + implies each checkpoint is unchanged or strictly slot-advanced |
| 27 | + (`checkpoint_forward`). |
| 28 | +-/ |
| 29 | + |
| 30 | +import LeanSpec.Forks.Lstar.StateTransition |
| 31 | + |
| 32 | +namespace LeanSpec.Forks.Lstar |
| 33 | +namespace State |
| 34 | + |
| 35 | +/-- "Unchanged or strictly forward" composes: stepping `a → b → c` where |
| 36 | +each step keeps the checkpoint or strictly raises its slot yields the same |
| 37 | +disjunction end to end. -/ |
| 38 | +private theorem forward_trans {a b c : Checkpoint} |
| 39 | + (h₁ : b = a ∨ a.slot < b.slot) (h₂ : c = b ∨ b.slot < c.slot) : |
| 40 | + c = a ∨ a.slot < c.slot := by |
| 41 | + cases h₁ with |
| 42 | + | inl hba => |
| 43 | + cases h₂ with |
| 44 | + | inl hcb => exact .inl (hcb.trans hba) |
| 45 | + | inr hlt => exact .inr (by rw [← hba]; exact hlt) |
| 46 | + | inr hlt₁ => |
| 47 | + cases h₂ with |
| 48 | + | inl hcb => exact .inr (by rw [hcb]; exact hlt₁) |
| 49 | + | inr hlt₂ => |
| 50 | + exact .inr (UInt64.lt_iff_toNat_lt.mpr |
| 51 | + (Nat.lt_trans (UInt64.lt_iff_toNat_lt.mp hlt₁) |
| 52 | + (UInt64.lt_iff_toNat_lt.mp hlt₂))) |
| 53 | + |
| 54 | +/-- `applyJustification` replaces each checkpoint only strictly forward: |
| 55 | +the justified checkpoint moves only to a strictly later target, the |
| 56 | +finalized checkpoint only to a source strictly past the old finalized |
| 57 | +slot; otherwise both are returned unchanged, root included. -/ |
| 58 | +theorem applyJustification_forward (rootSlot : Root → Option Nat) |
| 59 | + (acc : JFAcc) (src tgt : Checkpoint) : |
| 60 | + ((applyJustification rootSlot acc src tgt).latestJustified |
| 61 | + = acc.latestJustified ∨ |
| 62 | + acc.latestJustified.slot < |
| 63 | + (applyJustification rootSlot acc src tgt).latestJustified.slot) ∧ |
| 64 | + ((applyJustification rootSlot acc src tgt).latestFinalized |
| 65 | + = acc.latestFinalized ∨ |
| 66 | + acc.latestFinalized.slot < |
| 67 | + (applyJustification rootSlot acc src tgt).latestFinalized.slot) := by |
| 68 | + unfold applyJustification |
| 69 | + dsimp only |
| 70 | + split |
| 71 | + · next hfin => |
| 72 | + refine ⟨?_, .inr hfin.1⟩ |
| 73 | + split |
| 74 | + · next hlt => exact .inr hlt |
| 75 | + · exact .inl rfl |
| 76 | + · refine ⟨?_, .inl rfl⟩ |
| 77 | + split |
| 78 | + · next hlt => exact .inr hlt |
| 79 | + · exact .inl rfl |
| 80 | + |
| 81 | +/-- One attestation step keeps each checkpoint or strictly advances its |
| 82 | +slot: the vote filters and a stored tally leave both untouched, and the |
| 83 | +supermajority path is `applyJustification`. -/ |
| 84 | +theorem processAttestation_forward (validatorCount : Nat) (hist : Array Root) |
| 85 | + (rootSlot : Root → Option Nat) (acc acc' : JFAcc) |
| 86 | + (att : AggregatedAttestation) |
| 87 | + (h : processAttestation validatorCount hist rootSlot acc att = .ok acc') : |
| 88 | + (acc'.latestJustified = acc.latestJustified ∨ |
| 89 | + acc.latestJustified.slot < acc'.latestJustified.slot) ∧ |
| 90 | + (acc'.latestFinalized = acc.latestFinalized ∨ |
| 91 | + acc.latestFinalized.slot < acc'.latestFinalized.slot) := by |
| 92 | + unfold processAttestation at h |
| 93 | + dsimp only at h |
| 94 | + split at h |
| 95 | + · simp at h |
| 96 | + · injection h with h' |
| 97 | + subst h' |
| 98 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 99 | + · split at h |
| 100 | + · simp at h |
| 101 | + · injection h with h' |
| 102 | + subst h' |
| 103 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 104 | + · split at h |
| 105 | + · injection h with h' |
| 106 | + subst h' |
| 107 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 108 | + · split at h |
| 109 | + · injection h with h' |
| 110 | + subst h' |
| 111 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 112 | + · split at h |
| 113 | + · injection h with h' |
| 114 | + subst h' |
| 115 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 116 | + · split at h |
| 117 | + · simp at h |
| 118 | + · split at h |
| 119 | + · simp at h |
| 120 | + · split at h |
| 121 | + · injection h with h' |
| 122 | + subst h' |
| 123 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 124 | + · injection h with h' |
| 125 | + subst h' |
| 126 | + exact applyJustification_forward rootSlot acc |
| 127 | + att.data.source att.data.target |
| 128 | + |
| 129 | +/-- Folding attestation steps preserves strictly-forward replacement. -/ |
| 130 | +theorem foldlM_processAttestation_forward (validatorCount : Nat) |
| 131 | + (hist : Array Root) (rootSlot : Root → Option Nat) : |
| 132 | + ∀ (atts : List AggregatedAttestation) (acc acc' : JFAcc), |
| 133 | + List.foldlM (processAttestation validatorCount hist rootSlot) acc atts |
| 134 | + = .ok acc' → |
| 135 | + (acc'.latestJustified = acc.latestJustified ∨ |
| 136 | + acc.latestJustified.slot < acc'.latestJustified.slot) ∧ |
| 137 | + (acc'.latestFinalized = acc.latestFinalized ∨ |
| 138 | + acc.latestFinalized.slot < acc'.latestFinalized.slot) |
| 139 | + | [], acc, acc', h => by |
| 140 | + injection h with h' |
| 141 | + subst h' |
| 142 | + exact ⟨.inl rfl, .inl rfl⟩ |
| 143 | + | att :: atts, acc, acc', h => by |
| 144 | + rw [List.foldlM_cons] at h |
| 145 | + cases hstep : processAttestation validatorCount hist rootSlot acc att with |
| 146 | + | error e => |
| 147 | + rw [hstep] at h |
| 148 | + injection h |
| 149 | + | ok acc₁ => |
| 150 | + rw [hstep] at h |
| 151 | + have hrest : |
| 152 | + List.foldlM (processAttestation validatorCount hist rootSlot) acc₁ |
| 153 | + atts = .ok acc' := h |
| 154 | + have h1 := processAttestation_forward validatorCount hist rootSlot acc |
| 155 | + acc₁ att hstep |
| 156 | + have h2 := foldlM_processAttestation_forward validatorCount hist |
| 157 | + rootSlot atts acc₁ acc' hrest |
| 158 | + exact ⟨forward_trans h1.1 h2.1, forward_trans h1.2 h2.2⟩ |
| 159 | + |
| 160 | +/-- `processAttestations` keeps each checkpoint or strictly advances its |
| 161 | +slot — never a same-slot root swap. -/ |
| 162 | +theorem processAttestations_forward (s s' : State) |
| 163 | + (atts : List AggregatedAttestation) |
| 164 | + (h : processAttestations s atts = .ok s') : |
| 165 | + (s'.latestJustified = s.latestJustified ∨ |
| 166 | + s.latestJustified.slot < s'.latestJustified.slot) ∧ |
| 167 | + (s'.latestFinalized = s.latestFinalized ∨ |
| 168 | + s.latestFinalized.slot < s'.latestFinalized.slot) := by |
| 169 | + unfold processAttestations at h |
| 170 | + dsimp only at h |
| 171 | + split at h |
| 172 | + · simp at h |
| 173 | + · split at h |
| 174 | + · simp at h |
| 175 | + · split at h |
| 176 | + · simp at h |
| 177 | + · split at h |
| 178 | + · simp at h |
| 179 | + · split at h |
| 180 | + · simp at h |
| 181 | + · next acc heq => |
| 182 | + injection h with h' |
| 183 | + subst h' |
| 184 | + exact foldlM_processAttestation_forward _ _ _ atts _ acc heq |
| 185 | + |
| 186 | +/-- Past genesis anchoring, `processBlockHeader` leaves both checkpoints |
| 187 | +untouched: the anchor branch fires only when the latest header still sits |
| 188 | +at slot 0. -/ |
| 189 | +theorem processBlockHeader_checkpoints_of_ne_zero (s s' : State) (b : Block) |
| 190 | + (hnz : s.latestBlockHeader.slot ≠ 0) |
| 191 | + (h : processBlockHeader s b = .ok s') : |
| 192 | + s'.latestJustified = s.latestJustified ∧ |
| 193 | + s'.latestFinalized = s.latestFinalized := by |
| 194 | + unfold processBlockHeader at h |
| 195 | + dsimp only at h |
| 196 | + split at h |
| 197 | + · simp at h |
| 198 | + · split at h |
| 199 | + · simp at h |
| 200 | + · split at h |
| 201 | + · simp at h |
| 202 | + · split at h |
| 203 | + · simp at h |
| 204 | + · injection h with h' |
| 205 | + subst h' |
| 206 | + exact ⟨rfl, rfl⟩ |
| 207 | + |
| 208 | +/-- ST-7: checkpoint replacement is strictly forward across a successful |
| 209 | +transition on a post-anchoring state — each of `latestJustified` / |
| 210 | +`latestFinalized` is unchanged as a whole checkpoint (root included) or |
| 211 | +moves to a strictly higher slot. A same-slot root swap is impossible. -/ |
| 212 | +theorem checkpoint_forward (s s' : State) (b : Block) |
| 213 | + (hnz : s.latestBlockHeader.slot ≠ 0) |
| 214 | + (h : transition s b = .ok s') : |
| 215 | + (s'.latestJustified = s.latestJustified ∨ |
| 216 | + s.latestJustified.slot < s'.latestJustified.slot) ∧ |
| 217 | + (s'.latestFinalized = s.latestFinalized ∨ |
| 218 | + s.latestFinalized.slot < s'.latestFinalized.slot) := by |
| 219 | + unfold transition at h |
| 220 | + split at h |
| 221 | + · simp at h |
| 222 | + · unfold processBlock at h |
| 223 | + split at h |
| 224 | + · simp at h |
| 225 | + · next s₁ hh => |
| 226 | + have hps := processSlots_checkpoints s b.slot |
| 227 | + have hnz' : (processSlots s b.slot).latestBlockHeader.slot ≠ 0 := by |
| 228 | + rw [hps.2.2]; exact hnz |
| 229 | + have hhdr := |
| 230 | + processBlockHeader_checkpoints_of_ne_zero _ _ b hnz' hh |
| 231 | + rw [hps.1] at hhdr |
| 232 | + rw [hps.2.1] at hhdr |
| 233 | + have hatt := processAttestations_forward _ _ _ h |
| 234 | + rw [hhdr.1, hhdr.2] at hatt |
| 235 | + exact hatt |
| 236 | + |
| 237 | +end State |
| 238 | +end LeanSpec.Forks.Lstar |
0 commit comments