--- title: "Eventual DAG Synchrony: a machine-checked account of safety and liveness for uncertified DAG consensus" author: "Code and prose in this project were co-written with heavy LLM assistance. The Lean proofs are machine-checked — the kernel verifies every theorem against its stated form — but whether the definitions and theorem statements capture their *intended* meaning, and whether the surrounding prose is faithful to what is proved, has only human-plus-LLM review behind it. Read critically." --- ## Abstract DAG-based Byzantine fault-tolerant protocols are widely deployed, yet their liveness arguments are conventionally stated in terms of per-message delivery bounds, which obliges the entire proof to carry a time model that the commit rule itself never mentions. We propose **eventual DAG synchrony** — a structural condition on the DAG: beyond some round, every correct block references every correct block of the round below — and give a machine-checked development, in Lean 4, of safety and liveness for a Mysticeti-style commit rule above this condition, in which no liveness theorem mentions time. Safety assumes nothing about the network at all. The structural condition is derived rather than assumed, and the whole of what the network must supply reduces to a single clause of **view convergence** — after stabilisation, whatever one correct validator holds reaches every correct validator within Δ — from which both coverage and block production follow by the protocol's own build rules. The quantitative form states that a correct leader is committed once correct validators wait `2Δ`; a catch-up clause collapses any start spread between validators in a single post-stabilisation round, so the threshold `2Δ + proc` holds with no assumption about how the deployment began. We further prove what the committed ledger *contains*: every commit carries, at every round below it, blocks from at least half of the correct validators — with no synchrony assumption — and every correct block enters the agreed ledger within a schedule-window of its creation once a slot its author leads is certified, while a six-validator counterexample shows the same correct validator can be censored for ever under asynchrony, so aggregate coverage does not by itself give individual inclusion. On the same foundation, unchanged, we develop eight further machine-checked accounts, and then compose them. First, **denial-of-service resistance**: safety is shown independent of any anti-equivocation condition, and a correct validator's storage is bounded under an enforceable, author-blind *novelty budget* — with a matching construction showing that any bound from reference-validity conditions alone carries a constant exponential in the fault bound `f`. Second, **garbage collection**: a per-validator horizon below which nothing is retained, with commit verdicts invariant across the cut, storage constant at a lag, and bootstrap by an `(f+1)`-sampled attested base — no consensus on the cut anywhere. Third, the two-round protocol **Odontoceti**: safety and liveness of its commit rule, generalized from the published `n = 5f+1` to `n ≥ 5f+1`, where the formalization surfaces a gap in the published agreement argument — two equivocating candidates can both pass the indirect commit test at one anchor, realized on a concrete six-validator counterexample — and repairs it with a canonical-candidate rule. Fourth, **reactive schedules**: both commit rules remain live when validators wait only until they hold the leader's block — or, under Mysticeti, until they can certify — with the timeout as a fallback; latency is bounded by actual propagation with the timeout appearing nowhere, and when delivery undercuts the timeout no timeout ever fires. Fifth, **safe skip**: a crashed validator rejoins with one constant-size message denoting a block for every missed round — a donor's references plus a forced self reference — with production restored, no commit conjured for slots the network already passed, and every verdict reached before the fill re-derived and agreed after it. Sixth, **adaptive leader schedules**: Hammerhead-style reassignment of the leaders ahead, computed from the agreed prefix, is proved safe unconditionally — the schedule-and-verdict fixpoint is unique under no synchrony assumption, for arbitrary adapted policies — and live exactly when the policy keeps placing runs of reliable leaders. Seventh, **hybrid fault tolerance**: separating `fb` Byzantine from `fc` crash-prone validators, the two-round rule is proved safe and live at `n ≥ 5fb + 3fc + 1` — four validators suffice for two-round finality under a single crash — with the committee bound shown to *be* the existence of a working indirect threshold, and necessary: one validator short, agreement fails on data at every threshold. Eighth, **crash-fault consensus**: the same commit rule at a bare majority quorum — the protocol Nemo-Nemo, `n ≥ 2f + 1` with `f` halting faults — proved safe with *no* fault bound and no side conditions, and live at the classical bound under a fairness clause the mechanisation sharpens: a lone committed leader settles almost nothing below it, and progress requires committed leaders at adjacent rounds. Finally we ask whether all of this composes, and answer it once rather than pairwise. A small set of **properties of a commit rule** — a band the verdicts read, agreement, candidacy, the indirect rule, and a *support* saying what a commit counts — is enough for every mechanism to be proved against, and every rule with a carrier shows them: the core and its reactive execution, Odontoceti, Nemo-Nemo, Orcaella, Mahi-Mahi, FinWhale, Hydrozoan and Optimal-Hydrozoan. Two headline theorems then say what a rule gets — safety across any stack of cuts, fills and re-genesis, and liveness with certification as the only antecedent, so that a timed and a reactive execution share it — and synchrony is kept out of the properties by construction. What is visible only from the composition is a set of deployment constraints no single account can state — a garbage-collection lag bounds the outage a one-message recovery can span, a horizon must fall on an epoch boundary of an adaptive schedule, and a validator pruned past its own history is a reader until it re-genesises, counting against the fault budget meanwhile. The development comprises roughly 83,000 lines of Lean 4 over Mathlib, of which 21,000 are witnesses. Every principal result depends on exactly Lean's three standard axioms; every definition is exercised on concrete models by `decide` before anything is proved from it. All displayed Lean in this report is drawn from the source and type-checks against the built library. --- ## 1. Introduction Byzantine fault-tolerant consensus built over block DAGs has moved from research prototypes to production blockchains: validators exchange round-indexed blocks, each referencing a quorum of the previous round, and read commitment out of the resulting graph. The latest generation of these protocols — Mysticeti and its descendants — is *uncertified*: no certificate is ever constructed or sent, and every consensus-relevant fact is a counting pattern in the graph itself. This makes the protocols unusually well suited to mechanised verification — the commit rule is finite-set arithmetic — and unusually exposed to subtle error, because equivocation and availability, which certification used to discharge, become the reader's problem in every proof. This report is a machine-checked account, in Lean 4 over Mathlib, of this protocol family: a core development of safety and liveness organised around a structural liveness condition we call *eventual DAG synchrony*, a chain-quality account of what the committed ledger contains, and, on that unchanged foundation, eight further developments, seven of them composed at the end — storage bounds under adversarial equivocation; garbage collection without consensus on the cut; the safety and liveness of the two-round protocol Odontoceti, including a repair its published argument requires; reactive schedules under which consensus proceeds at network speed, with the timeout as a fallback that a fast network never triggers; and Safe Skip, by which a crashed validator rejoins production with a single message, with every prior verdict proved to survive the recovery; adaptive leader schedules, with the reassignment fixpoint proved unique — safety needing no synchrony at all — and live under a run-placing policy; and hybrid fault tolerance, separating Byzantine from crash faults, with the tight two-round committee bound machine-checked in both directions; and an integration account in which the arcs are shown to compose, together with the deployment conditions that only their composition reveals — including a recovery path for a validator whose whole history has been pruned, and a choice of message target that makes recovery transmit nothing at all. ### 1.1 DAG-based consensus Validators broadcast round-indexed blocks, each referencing a quorum of blocks from the round below. The resulting structure is a directed acyclic graph, and consensus is obtained by reading that graph rather than by running a separate voting protocol. The variant treated here is *uncertified* in the sense of Mysticeti: there is no explicit certificate round and no certificate message. A block two rounds above a leader constitutes a certificate for it precisely when the block's own references happen to contain a quorum of blocks that reference the leader. Certification is therefore a property of the graph, discovered by the reader, rather than an action taken by a writer. This is what permits the entire commit rule to be expressed as cardinality comparisons over finite sets, and it is what makes the development below possible with no cryptographic layer. ### 1.2 The synchrony assumption in liveness arguments Liveness for protocols of this family is conventionally established under a partial-synchrony assumption stated per message: after a global stabilisation time, a message between correct parties arrives within a bound Δ. Such an assumption obliges the argument to reason about the mechanics by which the DAG is transmitted, in terms of individual messages, when the object being reasoned about is the DAG itself. Two costs follow. First, a message-level assumption obliges every subsequent statement to be quantified over instants, and every lemma to re-establish that enough has arrived. Second, and more fundamentally, the commit rule does not mention time at all: it counts references. The resulting mismatch is substantial proof effort with no corresponding proof content. ### 1.3 Contributions 1. **Eventual DAG synchrony**, formulated structurally as `SynchronisedOn`: beyond round `R`, every correct block references every correct block of the round below. The statement mentions no clock, no message and no Δ, and it is a *derived* property of executions rather than an assumption (§4.4). 2. A machine-checked **safety** development — agreement, uniqueness of the committed leader sequence, and non-retraction of the ledger — which assumes nothing whatsoever about the network, not even eventual delivery. 3. A machine-checked **liveness** development above the structural condition, in which no theorem mentions time. Priority is not claimed: Qiu, Xiao and Shao [QXS26] give machine-checked safety and liveness for Mysticeti in Rocq, by refinement into LiDO-DAG. What is claimed is the *form* of the account — theirs is operational, quantified over traces and instants; here liveness is stated as a condition on the DAG, and the dependence on time is confined below a `Prop`-valued interface (§6.7, §27). 4. **A derivation** of the structural property from **view convergence** (§6.9), together with the protocol's build rules, and nothing beyond standard partial synchrony — the network's contribution stated in a form containing no clause about what validators do. On the same foundation, production is derived too, from round `0` with genesis the only seed (`ViewPace.populatedOn` (V17)), so the entire liveness account rests on one view-shaped assumption, over a build schedule in which *stuck* is expressible. 5. A precise account of the **trust boundary** (§4). What is assumed reduces to the fault bound and a single network condition — view convergence — serving coverage and production alike; every other condition is a clause of the protocol, which a designer controls. In particular reference coverage is derived rather than assumed, and the one point at which a network parameter constrains the specification is the wait threshold of §26.1. 6. **Quantitative forms** (§6.10): the round from which coverage holds, given explicitly; a bound on the slot at which the next commit occurs; and an operational statement — a correct leader is committed once every correct validator waits `2Δ + proc`, a constant of the network and the implementation. No start-spread or deployment quantity appears anywhere: the pacemaker's catch-up rule collapses drift to `Δ + proc` at the first fully-post-GST round whatever the start spread (§6.11), and at instantaneous entry (`proc = 0`) the threshold is the deployed `2Δ` timer. 7. **Chain quality** (§7): every commit's flush carries, at every round below it, blocks from at least half of the correct validators — proved with no synchrony assumption of any kind — and, once the DAG is synchronous, every correct block enters the agreed ledger within a schedule-window of its creation (`chain_quality`); a counterexample shows the aggregate guarantee provably does not imply the individual one without synchrony. 8. **Denial-of-service resistance** (§8): with safety shown independent of any anti-equivocation condition, storage is bounded twice over — a general per-cone bound under an exposure condition on references, with a matching construction showing its exponential constant is essentially forced, and a **novelty budget** under which a correct validator's store grows linearly forever, stated under enforceable, author-blind conditions only (`dos_resistance`). 9. **Garbage collection without consensus** (§9): a horizon below which stores retain nothing, with commit verdicts proved invariant across the cut under a single premise, storage made *constant* at a lag, bootstrap by an `f+1`-sampled attested base rather than any agreement on the cut, and the lag envelope pinned theorem by theorem. 10. **Odontoceti, formalized and repaired** (§10): safety and liveness of the two-round commit rule, generalized from the paper's fixed `n = 5f+1` to `n ≥ 5f+1`, on the unmodified DAG layer — together with four findings about the paper's safety argument, one of which (agreement among indirect commits resting on candidate-iteration order) is refutable on data without a canonicity repair the formalisation supplies. 11. **Reactive liveness** (§11): both commit rules proved live under a schedule that waits only as long as it must — react on holding the leader (or, under Mysticeti, a certificate), fall back to the timeout otherwise. The fast path is quantified: round latency is bounded by drift, delivery and processing with the timeout absent, and below the timeout the fallback branch is never taken. 12. **Safe Skip** (§12): crash recovery in one message. A recovering validator's fill — one denoted block per missed round, the donor's references plus a self reference the validity clause P3′ forces — is proved a block universe; production is restored at every missed round, a filled leader candidate is directly skipped, and every verdict reached before the fill re-derives and agrees after it (`decided_fill_agree_of_properties` (SS6)). The fill is moreover *derived, not transmitted*: the donor line is the self-parent chain below its tip, so the whole denotation is a function of two block names that every receiver elaborates locally (`JumpMsg.denote_eq_of_core` (SS10)) — round jumping at one message of constant size (§12.7). 13. **Adaptive leaders** (§13): a Hammerhead-style schedule — the leaders ahead recomputed from the agreed prefix — proved safe and live for both commit rules. Safety is uniqueness of the schedule-and-verdict fixpoint, with no synchrony or fairness hypothesis of any kind (`run_agree` (AL3)); liveness is its existence under the one clause that prices the policy (`run_exists_of_support` (AL5)); and the layer is rule-agnostic, its two-round mirror consuming the policy objects unchanged (AL7). 14. **Hybrid fault tolerance** (§14): the two-round rule proved safe and live under `fb` Byzantine and `fc` crash-prone validators at `n ≥ 5·fb + 3·fc + 1` (Orcaella's bound [KS26]), for every indirect threshold in an admissible interval whose nonemptiness *is* the committee bound — and the bound proved necessary: one validator short, one view derives conflicting verdicts at every threshold (`hybrid_bound_necessary` (H10)). Crash-proneness costs no new behavioural clause: a crash is absence, and the class enters through the cardinality arithmetic and one strengthened non-equivocation clause alone. 15. **Crash-fault consensus** (§15): Nemo-Nemo's majority-quorum rule [Ker+26] — outside the base fault model, whose committees every other arc only tightens — restated over a self-contained crash universe and proved safe with no fault bound and no side conditions (`Nemo.nemoLaws` (NN5)): universal non-equivocation retires the twin machinery, and agreement consumes the quorum exactly once. Liveness holds at `n ≥ 2f + 1` (`Nemo.all_decided_below_of_fairRun` (NN8)) under a fairness clause the mechanisation sharpens: with no failure detector the indirect rule cannot settle past an undecided slot, a lone committed leader settles only the slot two rounds down, and progress requires committed leaders at *adjacent* rounds — which round-robin over `2f + 1` supplies by counting. 16. **Integration, through the properties** (§16): the arcs compose because every mechanism is proved once against a small set of properties of the commit rule — a band, agreement, candidacy, the indirect rule, and a support with two laws — and every rule shows them (§16.1). Two headline theorems state what a rule gets: `Properties.Safe`, across any stack of cuts, fills and re-genesis, and `Support.Lives`, progress and inclusion at the rule's support with certification as the only antecedent, so a timed and a reactive execution share it. What the composition found on its way remains: coverage is refuted under the fill, with an exact boundary and for the same reason the fill is safe (I4); three conditions constrain where a horizon may fall (I5, I6); a **re-genesis** provision restores a validator whose history was pruned entirely — needing no exemption from P3′, since truncation makes the retained layer genesis, and no agreement on the cut (I10–I12). 17. **Mahi-Mahi** (§17): the asynchronous protocol's rule — the core's at a wave of `w` rounds, votes through the cone — proved safe at every `w ≥ 3` and collapsing onto the core at `w = 3` (`MahiMahi.Safety.holds` (MM1)), and live with **no synchrony hypothesis** under one clause on the schedule and the DAG (`MahiMahi.Liveness.holds` (MM3)): the late-revealed leader keeps landing among the candidates the DAG commits, a set the core's own common-core lemma bounds below at every wave (`MahiMahi.Counting.holds` (MM2)) and synchrony derives from fairness (`MahiMahi.Synchrony.holds` (MM5)). The five-round count holds for non-equivocating authors only — `1/3` per wave, not the paper's `2/3` — and the core's per-candidate skip rule is weaker than the implementation's slot blame. 18. **Black Marlin** (§18): the three-round rule of a partially synchronous protocol with an anchor in every round and no certificate round, at the core's committee `n ≥ 3f + 1` — and **refuted**. §18.5 exhibits an execution, machine-checked at `n = 4`, `f = 1`, in which two reliable validators output different blocks of one author and round and neither ever outputs the other's, which is Definition 1's Agreement refuted for the protocol as specified. Its Total order fails on the same execution, and on blocks of *reliable* authors, for a reason that does not turn on the twins at all (§18.6). §18.7 locates both failures in a named step of the paper's own argument. The arc is scoped to those refutations: a scheme this development has refuted is not one to integrate with §16's properties, so its safety, liveness, view-relative order and repair developments are not carried, and it is the one rule of ten with no carrier. **Minnow's minimal commit rule fails in two ways** (§19). `crs*`, the rule proposed for eventual synchrony, decides a leader slot from the round immediately above it: a quorum of `2f + 1` processes pointing commits it, and `2f + 1` not pointing skips it. Two of its clauses are written in a way their own sentences do not support — a slot with no vertex resolves nothing, and the skip clause counts vertices where the quorum clause counts processes, which would make one vertex committed and skipped at once. A phrase repairs each, and §19.2 settles both. **Both defects come from the same sentence.** An earlier slot counts as resolved when *some* vertex of it lies in the candidate's causal past, which is not the same as that vertex being decided. Read one way the clause is too generous: where the slot's process equivocates, one twin carries a later leader past the slot while the other is undecided, and the other then acquires its quorum and demands a place before what is already output — Safe-Commit, and with it Total-order and Agreement. The step of the paper's own safety proof that permits it is located: Lemma 10's case for concurrent leaders reads an existential over a slot as though the slot held one vertex (§19.3). Read the other way the rule cannot reach far enough: the two thresholds leave a gap — a vertex pointed to by between `f + 1` and `2f` processes is neither committable nor skippable — which a Byzantine process can occupy every round it leads, and `2f + 1` is the least threshold the skip clause can safely take, so the gap is forced (§19.4). What that costs turns on the leader schedule. A dead slot lies in every causal past two rounds up, so a schedule offering two consecutive rounds of correct leaders commits at the second: round robin with one leader a round always offers such a pair, and round robin with two or more can be denied it for every `f`. So Live-Commit fails for the rule paired with a multi-leader round robin rather than for the rule alone, and the escape that rescues it is the disjunct that costs safety (§19.5). Every finding is exhibited on four processes at `f = 1`, machine-checked, with the model held to the paper's own validity rule and its own reading of each clause (§19.1). **Barnacle adapts the number of leaders per round** (§21), a control loop that measures on the agreed DAG the fraction of leader slots the base protocol decided directly and drives the count with an additive-increase, multiplicative-decrease rule. It is proved safe and live over an explicit interface rendering the paper's assumptions A1–A4, instantiated on Mysticeti, Odontoceti, Nemo-Nemo and Orcaella (§14's hybrid rule, at every admissible threshold). Safety — agreement of the configuration sequence and of the ledger — holds for **any** update rule, with no synchrony or fairness hypothesis (`Agreement.holds` (BN3), `Ledger.holds` (BN5)), because the algorithm decides under the count in force and only then switches, so each configuration's verdicts are derivations against one fixed schedule. There is no total run: a finite universe closes finitely many configurations, and the paper's sequence of configurations is what every prefix of it agrees on. Liveness is the paper's Configuration Progress at the run's own count and runs of every height under a horizon (`Progress.holds` (BN8)), from a clause on a schedule that the paper assumes of its base protocols — and that its own rotation does not meet by the route this development takes, a run of correct-led slots, which has no instance at two leaders and four validators. The clause holds by a descent through the *heads* of rounds and a pigeonhole on residues (`Heads.holds` (BN9)), so each of the four rules is live under round-robin at **every** leader count (`MysticetiLive.holds`, `Odontoceti.holds`, `Nemo.holds`, `Orcaella.holds` (BN10)) — the paper's A4 for its schedule, proved. Findings for the paper are listed in §21.5. **Hydrozoan commits by two paths under hybrid faults** (§22): at `n ≥ 3f + 2c + k + 1`, a leader is committed in two message delays on `n − p` votes, `p = ⌊(c + k)/2⌋`, or in three on `2f + c + 1` certificates, skipped on `n − p` blames, and otherwise decided from the nearest committed anchor by a graded rule. Safety is agreement of any two verdicts across views and routes (`Hydrozoan.SlotAgreement.holds` (HZ3)) from six threshold inequalities that hold for every fault configuration the class admits — the Hydrangea paper's cap on the slack is not needed — and prefix consistency of the committed sequences (HZ4). Liveness above a structural rendering of synchrony routes through the slow path, the only one a quorum of correct replicas is sure to reach (HZ5–HZ7), and the hypotheses are grounded by exhibition: the wave-aligned rotation is fair with no premise where per-slot rotation is starved inside the hybrid bound, and the synchrony package is realizable at every horizon (`Hydrozoan.Grounding.holds` (HZ8)). **Optimal-Hydrozoan takes the fast path to Hydrangea's bound** (§23): one more fault in two rounds, `pOpt = ⌊(c + k)/2⌋ + 1`, at the same committee, by FinWhale's device — a decision-round block that has seen the leader equivocate must not reference the leader's block, and quorums of decision-round blocks that are fast evidence for a candidate replace the weak quorum of votes. The seam consumes the validity rule exactly once, so the evidence rung is unique without a tie-break (`OptimalHydrozoan.SlotAgreement.holds` (OH3)); a candidate-less slot is skipped by the guaranteed quorum alone, a liveness claim where Hydrozoan's skip is opportunistic, and not otherwise (`OptimalHydrozoan.DirectLiveness.holds` (OH5)); and realizability exhibits a universe satisfying the rule, which the good case implies (OH8). ### 1.4 Scope and non-goals The development is deliberately bounded in four respects; one feature that is not a restriction — pipelining — is recorded first because it is often assumed to be one. - **Pipelining and multiple leaders enter through the schedule, not the rule.** The schedule class constrains only monotonicity, unboundedness and keying (§3.4); anchoring is governed by per-pair eligibility. Mysticeti's every-round pipelining and its multi-leader rounds are instances, with the interleaving of simultaneously undecided slots handled by the committed-run results (`decided_of_committed_above` (L8d), `decided_below_of_committed_run` (L8e), `all_decided_below_of_fairRun` (L10)); `pipelining-and-multi-leader.md` is the companion document. A Cordial-Miners-like three-round spacing is the special case in which every later slot is eligible (`eligibleAt_of_lt_of_spacing`). - **No cryptography.** Signatures, authentication and equivocation detection are outside the model. Non-equivocation of correct validators is a clause of the protocol (§4.1), recorded structurally (§2.3) and not enforced by a mechanism. - **No executions.** The object of study is a DAG together with invariants, not a transition system with traces. What an operational model would establish as a reachability invariant of the protocol is here recorded as a structural condition. - **No intra-flush ordering.** The committed-leader sequence and the ledger *set* are shown agreed; totally ordering the blocks released by a single commit requires a tie-break which the development declines to assume (§5.6). - **No wall-clock latency.** The wait bound of §6.11 is a duration, but the total elapsed time to a commit is not derived (§26.6). ### 1.5 Organisation §2 gives the system model and §3 the commit rule; §4 draws the trust boundary, separating what is assumed from what the protocol enforces. §5 develops safety (culminating in agreement, `decided_agree`, and the agreed ledger) and §6 liveness, grounded on view convergence (culminating in recurring commits, `commits_recur_on`, the three derivations of eventual DAG synchrony, and the quantitative wait bound). §7 proves the chain-quality account — coverage with no hypothesis, inclusion under certification (`chain_quality`, `committed_of_correct_block`). §§8–15 present eight further developments on that foundation: denial-of-service resistance (`dos_resistance`); garbage collection (`decided_agree_chop`, `card_retained_le`, `bootstrap_agree`); Odontoceti (`Odontoceti.odontocetiLaws`, `Odontoceti.all_decided_below_of_fairRun`); the reactive schedule (`ReactiveM.decided` (RS2), `Odontoceti.reactive_decided` (RS3), `ReactivePace.no_timeout_of_fast` (RS4)); safe-skip recovery (`decided_fill_agree_of_properties` (SS6)); adaptive leader schedules (`run_agree` (AL3), `run_exists_of_support` (AL5)); and hybrid fault tolerance (`Hybrid.hybridLaws` (H6), `hybrid_bound_necessary` (H10)); and crash-fault consensus (`Nemo.nemoLaws` (NN5), `Nemo.all_decided_below_of_fairRun` (NN8)). §16 composes all of them through the properties (`MysticetiProperties.safety`, `MysticetiProperties.liveness` (I7)) and collects the deployment conditions their composition reveals. §§17–23 analyse seven protocols of the family against this development: Mahi-Mahi's asynchronous rule at wave `w` (`MahiMahi.Safety.holds` (MM1)), Black Marlin's three-round rule, refuted on data (BMO11, BMT4), Minnow's minimal rule, FinWhale's two-round fast path (`lemma4` (FW1), `Run.agreement` (FW12)), and Barnacle's adaptive leader count (`Barnacle.Agreement.holds` (BN3), `Barnacle.Heads.holds` (BN9)), and Hydrozoan's dual-path rule under hybrid faults (`Hydrozoan.SlotAgreement.holds` (HZ3), `Hydrozoan.Grounding.holds` (HZ8)), and its Optimal variant's fast path at Hydrangea's bound (`OptimalHydrozoan.SlotAgreement.holds` (OH3), `OptimalHydrozoan.DirectLiveness.holds` (OH5)). **The development has four kinds of arc, and the source says which each is.** A **commit rule** carries a universe, a decision relation, the properties it shows and the mechanisms it earns: the core, Odontoceti, Nemo, Hybrid, Mahi-Mahi, Hydrozoan, Optimal-Hydrozoan, FinWhale, and the two refuted ones, Black Marlin and Minnow. A **universe transform** rewrites the DAG and owes a witness that it does so lawfully — garbage collection's cut, Safe Skip's fill, re-genesis. A **schedule mechanism** rewrites the `Slots` a rule runs on and touches no universe at all: Barnacle varies how many leaders a round has, the adaptive arc which validators lead, the reactive arc when a validator builds, and the timed model is the full-timeout baseline the other two are read against. An **analysis** measures a DAG rather than deciding on one: denial-of-service resistance, chain quality, and the composed network capstones. The properties of §16 are the contract the first kind meets and the other three consume, and what pairs two kinds at once — a schedule over a rule, or two transforms composed — is the integration layer. §24 exhibits the witness models. §25 describes the mechanisation, §26 discusses the formulation, the lessons of the extensions, and the limitations, §27 surveys related work, and §28 concludes. Appendix A indexes every principal statement against its Lean name and module. Throughout, displayed Lean is drawn from the source; binders are occasionally elided for layout, and `…` marks an elision. --- ## 2. System model ### 2.1 Validators and the fault model ```lean class Faults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where f : ℕ byzantine : Finset Validator card_validators : 3 * f + 1 ≤ Fintype.card Validator card_byzantine : byzantine.card ≤ f def Correct : Finset Validator := (F.byzantine)ᶜ ``` A quorum is `n − f` for `n ≥ 3f+1` validators — the familiar `2f+1` at the boundary `n = 3f+1`, where every witness sits. The derived fact `card_correct : n − f ≤ Correct.card` records that the correct validators themselves form a quorum. The liveness development consumes it wherever a `T`-relative result is specialised to `T := Correct`, and in the production inductions of §6.3 and §6.9. As a set, `Correct` is a complement and carries no behavioural content. Membership is satisfied, in particular, by a validator that crashes at round 0 and never speaks again. This is deliberate: it allows every safety result to hold of crashed validators. The behaviour of a correct validator is supplied separately, by the protocol clauses of §4.1, and the reader should keep the two apart — `Correct` names *which* validators execute the algorithm, while §4.1 says *what* the algorithm is. Since a crashed validator satisfies the first and not the second, the liveness development must invoke the protocol clauses explicitly (§6.0). ### 2.2 Blocks and validity ```lean structure Block (Validator BlockId Payload : Type*) where round : ℕ creator : Validator refs : Finset BlockId payload : Payload ``` Blocks are addressed by identifier: `refs : Finset BlockId` rather than `Finset Block`. A view and the universe consequently share a single interpretation function `U.block`, so two validators may disagree about which blocks they hold but never about what an identifier denotes. `Payload` is opaque and plays no role. ```lean structure ValidWrt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card self_parent : 0 < b.round → ∃ i ∈ b.refs, (blk i).creator = b.creator ``` Four aspects of the formulation are consequential. The predecessor condition is stated additively rather than as `(blk i).round = b.round - 1`. Besides avoiding truncated subtraction on `ℕ`, this makes the genesis case derivable rather than a separate branch: at round 0 the equation `(blk i).round + 1 = 0` is unsatisfiable, so `refs = ∅` follows (`ValidWrt.refs_empty_of_round_zero`). The quorum condition is stated on the *creator set*, not on `refs.card`. This is the faithful reading of "references a quorum of blocks of the previous round", which means `n−f` distinct *validators*; `ValidWrt.card_creators` and `ValidWrt.card_refs` relate the two under the distinctness condition. `distinct_creators` is consumed by certificate uniqueness (§5.4) and, in the two-round setting, by twin uniqueness (§10). `self_parent` — a non-genesis block references *some* block by its own creator, not a unique one: an equivocator's blocks form a forest of predecessor chains, and the condition does not collapse it. Mysticeti and Odontoceti both mandate the clause. The safety development never consumes it; on the liveness side it is the backbone of individual inclusion — chain quality's CQ5–CQ7 (§7) and the reactive schedule's RS5 (§11.5) — whose whole argument is the self-parent chain. It is indispensable to §8, where that same chain is what turns per-acceptance budgets into per-round rates and a correct block's cone into a complete record of its author's acceptances. In §12 the clause is consumed in the other direction: the safe-skip fill's added self reference exists to satisfy it. ### 2.3 The block record, the universe and views Every universe type in this report has one shape: a set of identifiers, a block map, closure under references, validity of every block against the rule's own predicate, and one block per author per round for the authors the fault model constrains. That shape is the **block record**, parametrised by a validity predicate and an honest set. ```lean structure BlockRecord (Validator BlockId Payload : Type*) (P : Validity Validator BlockId Payload) (honest : Finset Validator) where ids : Finset BlockId block : BlockId → Block Validator BlockId Payload complete : ∀ i ∈ ids, ∀ j ∈ (block i).refs, j ∈ ids valid : ∀ i ∈ ids, P block (block i) no_equivocation : ∀ i ∈ ids, ∀ j ∈ ids, (block i).creator ∈ honest → (block i).creator = (block j).creator → (block i).round = (block j).round → i = j ``` The core's universe is the record at `ValidWrt`, with non-equivocation asked of the correct validators: ```lean abbrev BlockUniverse (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] := BlockRecord Validator BlockId Payload ValidWrt (Correct : Finset Validator) ``` Nemo's universe (§15) is the record at its majority validity with every validator honest, FinWhale's DAG (§20) is it at `ValidHere`, and Hydrozoan's universe (§22) is it at Hydrozoan's validity with non-equivocation asked of the non-Byzantine replicas, its block the shared block with no payload. The mechanisms of §9, §12 and §16 are built once at the record, against four facts a validity predicate owes them: ```lean class Mechanised : Prop where pred : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), P blk b → ∀ j ∈ b.refs, (blk j).round + 1 = b.round reads : ∀ (blk blk' : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (b : Block Validator BlockId Payload), (∀ i ∈ ids, ∀ j ∈ (blk i).refs, j ∈ ids) → (∀ j ∈ b.refs, j ∈ ids) → (∀ j ∈ ids, blk' j = blk j) → P blk b → P blk' b base : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), b.round = 0 → b.refs = ∅ → P blk b chops : ∀ (blk : BlockId → Block Validator BlockId Payload) (G : ℕ) (b : Block Validator BlockId Payload), P blk b → G < b.round → P (chopBlk blk G) { b with round := b.round - G } ``` References sit one round below; validity reads only referenced blocks; a reference-free round-zero block is valid; and validity survives the cut strictly above the horizon. A predicate that does not read the author (`CopyStable`) also gets the copy fill's validity without a proof of its own. None of the four predicates proves these itself. Every validity predicate in the development has one shape — references sit one round below, a non-genesis block references a quorum of distinct creators at the rule's threshold, and one further clause — and that shape is stated once as the family `ValidAt`: ```lean structure ValidAt [DecidableEq Validator] (q : ℕ) (C : Clause Validator BlockId Payload) (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round quorum : 0 < b.round → q ≤ (creators blk b).card clause : C blk b ``` A clause owes the three facts of `Mechanised` that concern it, the predecessor fact being the family's; `distinct`, `selfParent`, the empty clause `none` and the conjunction `and` are the clauses the four rules use, each with its obligations discharged in `Common/BlockRecord.lean`, and the family is `Mechanised` at any clause that is (`ValidAt.mechanised`) and `CopyStable` at any clause that does not read the author. A rule's own predicate then inherits both along its equivalence with the family (`Mechanised.of_iff`): ```lean theorem ValidWrt.iff_validAt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : ValidWrt blk b ↔ ValidAt (quorumCard Validator) (Clause.distinct.and Clause.selfParent) blk b ``` The core is the family at its quorum with distinct creators and the self-parent clause; Nemo is it at the majority with no clause; FinWhale (§20.2) with distinct creators and its leader clause, `leaderClause`, the one clause proved outside `Common/BlockRecord.lean`; Hydrozoan (§22) with distinct creators alone. The self-parent clause is the one that reads the author, which is why the core's fill adds a self reference where the other three take the copy fill. **What else is stated once at the record.** The full view `View.full` and a view caught up to a round, `View.CoversUpto`, live in `Common/BlockRecord.lean`; the two liveness hypotheses `PopulatedOn` and `SynchronisedOn` in `Common/Participation.lean`; the supporters and blamers a view holds, `supportersIn` and `blamesIn`, in `Common/Support.lean`, each equal to the record's count at the view read as a record (`supportersIn_eq_toRecord`); and the ledger in `Common/Ledger.lean` (§5.6). Every rule's direct rules, liveness hypotheses and ledger are these at its record, with no copy of its own, and the core's `chop`, `addGenesis` and their lemmas are the record's names, exported rather than restated. **What a view holds.** Every direct rule of every protocol has one shape: the view holds blocks of some set from at least `t` distinct authors, the supporters, certifiers or blamers the validator has actually seen. That count is `heldAuthors U V s` and the predicate `HoldsAtLeast U V t s` (`Common/Support.lean`). The core's `DirectCommitIn` is `HoldsAtLeast` at `n − f` over `certificates U L r`, its slot-level skip the same over `slotBlamers U k`; Nemo's commit is the majority over the votes `votesFor U L (r + 1)`, Hybrid's the hybrid quorum, and Hydrozoan's fast, slow and skip rules are `qFast`, `qSlow` and `qFast` over votes, certificates and blamers. The rules are abbreviations of the predicate, so that the count only grows with the view (`HoldsAtLeast.mono`), that a view can only under-report the record (`HoldsAtLeast.le`), that the full view holds everything (`HoldsAtLeast.full`) and that a view covering the set's rounds holds all of it (`HoldsAtLeast.of_coversUpto`) are each proved once and read at every rule: every `commit_mono` and `skip_mono` law of the eight rules is `HoldsAtLeast.mono`. Non-equivocation is stated at the level of the universe, and must be. A per-view formulation is strictly weaker: two views could each satisfy "at most one block per correct author per round" while holding *different* such blocks, which is precisely a correct validator equivocating, with both views well-formed. Every cross-view result requires the two blocks to be identified as a single identifier, and only a universe-level statement delivers this. Views inherit the property, which is why `View` carries no corresponding field. The condition is independent of the others: duplicating a correct-authored block under a fresh identifier preserves `complete` and `valid` and violates only `no_equivocation`. It is also the sole point at which the behaviour of a correct validator is recorded on the safety side, and it is a clause of the protocol rather than an assumption (P5): a correct validator produces one block per round because the algorithm so directs. Since the object of study is a DAG rather than an execution (§1.4), the clause appears as a structural condition on the universe; in an operational model it would be established as an invariant of reachable states. ```lean structure View (U : BlockRecord Validator BlockId Payload P honest) where ids : Finset BlockId subset_ids : ids ⊆ U.ids complete : ∀ i ∈ ids, ∀ j ∈ (U.block i).refs, j ∈ ids ``` A view is a downward-closed subset of the universe sharing its interpretation function, and inherits validity and non-equivocation. Both `ids` fields are finite sets. Finiteness is not incidental: it is what gives `authorsAt` a cardinality, and every quorum argument in the development counts one. It also determines the shape of the growth clause P8 (§6.3). ### 2.4 Causal history ```lean def RefStepFrom (blk) (i j : BlockId) : Prop := j ∈ (blk i).refs def ReachesFrom (blk) : BlockId → BlockId → Prop := Relation.ReflTransGen (RefStepFrom blk) def Reaches (U) : BlockId → BlockId → Prop := ReachesFrom U.block ``` The walk is stated over the block assignment rather than over a universe type, because it consumes nothing else: `Common/Causality.lean` proves it once, and both the Byzantine universe here and the crash universe of §15 read it at their own data. What each supplies is a `CausalStructure` — references stay inside the population, and a reference sits one round below — which is everything reachability and the finite cone need. The same hoist serves production and coverage (§6.3). ### 2.5 Notation, and the labelling scheme Global symbols, fixed for the whole report: | Symbol | Meaning | |:---|:---| | `n`, `f` | committee size and fault bound; `n ≥ 3f+1` throughout, `n ≥ 5f+1` in §10 | | `n − f` | the quorum size; `2f+1` at the boundary `n = 3f+1`; spelled `quorumCard Validator` in the Lean (§4.2) | | `Correct` | the complement of the Byzantine set (§2.1) | | `r`, `k` | a round; a slot index (§3.4) | | `U`, `V`, `D` | the block universe, a view, a delivery layer (§2.3, §6.2) | | `H(b)`, `history U b` | the causal history (cone) of block `b` (§2.4) | | `Δ`, GST | the post-stabilisation delivery bound and stabilisation time of partial synchrony (§6.8) | | `R` | the round from which eventual DAG synchrony holds (§6.4) | | `N` | the growth horizon: production is demanded up to it (§6.3) | | `κ`, `T` | the novelty budget: analysis-side (guarded) and mechanism-side (author-blind) constants (§8.4) | | `G`, `Λ` | a garbage-collection horizon round, and its lag behind the current round (§9) | Results carry alphanumeric labels by area: **T** (structural theorems, §2 and §5.1–§5.2), **M** (the commit rule, §5.3–§5.6), **L** (liveness, §6), **P** and **N** and **R** (protocol, network and rate clauses of the trust boundary, §4), **CQ** (chain quality, §7), **D**/**C**/**B** (the denial-of-service development, §8), **G** (garbage collection, §9), **O** (Odontoceti, §10), **RS** (the reactive schedule, §11), **SS** (safe skip, §12), and **AL** (adaptive leaders, §13), **H** (hybrid fault tolerance, §14), **NN** (crash-fault consensus, §15), **I** (integration, §16), and **MM** (Mahi-Mahi, §17). The labels match the companion documents and the source comments; Appendix A maps each to its Lean name and module. Counting vocabulary: | Notation | Meaning | |:---|:---| | `blocksAt U n` | the identifiers in `U` at round `n` | | `authorsAt U n` | their creators | | `creatorsOf U.block s` | the creators of an arbitrary set of identifiers | | `supporters U b n` | round-`n` authors whose blocks reference `b` | | `correctSupporters U b n` | those among them that are correct | | `blames U L n` | round-`n` authors whose blocks omit `L` | `creatorsOf` is defined on an arbitrary `Finset BlockId` rather than on a block's references, since the persistence theorem, the commit rule and the block-level intersection lemma all quantify over identifier sets that are not any block's references. Such sets carry no distinctness invariant of their own, which is why every quorum hypothesis in the development is stated on the creator set: a Byzantine author could otherwise inflate a set with equivocating blocks. --- ## 3. The commit rule Validators emit a single kind of object, the block of §2.2. Every role described below is assigned by the reader of the DAG, not by its writer. ### 3.1 Certificates ```lean abbrev votesIn (U) (C L : BlockId) : Finset BlockId := carriedVotes U (IsVote U) C L abbrev Certifies (U) (C L : BlockId) : Prop := CarriesVotes U (IsVote U) (quorumCard Validator) C L abbrev certificates (U) (L : BlockId) (r : ℕ) : Finset BlockId := certificatesAt U (IsVote U) (quorumCard Validator) L (r + 2) ``` These are the record's certificate stack (`Common/Support.lean`) at the plain vote and the core's quorum: `carriedVotes U Vote C L` is the references of `C` that vote for `L`, `CarriesVotes U Vote t C L` says `C` carries votes for `L` from `t` distinct authors, and `certificatesAt U Vote t L n` is the round-`n` blocks that do. Every rule with a certificate — Nemo's vote, Odontoceti's and Hybrid's cone supporters, Hydrozoan's `q_cert` certificate, Mahi-Mahi's with its own vote — is the same stack at its vote, threshold and round. A round-`(r+2)` block certifies a round-`r` block `L` exactly when its own references contain blocks by a quorum of distinct validators, each of which references `L`. ### 3.2 The direct rules ```lean def DirectCommit (U) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (creatorsOf U.block (certificates U L r)).card def DirectSkip (U) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (blames U L (r + 1)).card ``` A validator applies these to what it holds. The view-relative forms `DirectCommitIn` and `DirectSkipIn` intersect the relevant sets with `V.ids`; `directCommit_of_directCommitIn` and `directSkip_of_directSkipIn` record that a view can only under-report, so that a local judgement is always a genuine universe-level one and the universe-level theorems apply without recounting. ### 3.3 The indirect rule A slot which the direct rules leave undecided is settled by examining the causal history of a later, committed *anchor*. ```lean abbrev CertifiedIn (U) (A L : BlockId) (r : ℕ) : Prop := certifiedLink IsVote (quorumCard Validator) 2 U A L r ``` `certifiedLink` (§3.2) asks that some certificate for `L` — a block two rounds up carrying a quorum of votes for it — lie in `A`'s causal history, through `LinkedVia U A s` (`Common/History.lean`), which is what every indirect rung asks of an anchor. This test is universe-level by design. `certifiedIn_iff_of_view` establishes that restricting the search to a view holding the anchor yields the same answer, so nothing is lost; and the universe-level formulation is what makes decisions monotone under view growth (§6.5). Were the test view-relative, the premise of the indirect *skip* rule would be anti-monotone, and enlarging a view could convert a skip into a commit. ### 3.4 The slot schedule ```lean class Slots (Validator : Type*) where slotRound : ℕ → ℕ leader : ℕ → Validator mono : Monotone slotRound unbounded : ∀ n, ∃ k, n ≤ slotRound k keyed : Function.Injective (fun k => (slotRound k, leader k)) def EligibleAt (wave k j : ℕ) : Prop := S.slotRound k + wave < S.slotRound j def IsLeaderBlock (U) (k : ℕ) (L : BlockId) : Prop := L ∈ U.ids ∧ (U.block L).round = S.slotRound k ∧ (U.block L).creator = S.leader k ``` The class constrains the schedule only to be monotone, unbounded in round, and *keyed* — distinct slots differ in round or in leader. What safety actually requires of anchoring is per-pair **eligibility**: an anchor's proposal must clear the slot's decision round, `slotRound k + wave` for the rule's wave — two for the core, so `decisionRound k = slotRound k + 2` and the test is Algorithm 3's filter `r_decision < s.round`. Under a three-round spacing, every later slot is eligible (`eligibleAt_of_lt_of_spacing`), so the general relation is conservative over it; and pipelined and multi-leader schedules — Mysticeti as published — are instances (`Slots.uniform p m`, `Slots.uniformSingle`), though a backlog of undecided slots is cleared by a *run* of consecutive commits rather than any single one (`decided_below_of_committed_run` (L8e), `all_decided_below_of_fairRun` (L10), the latter an instance of `Timed.decidedBelow_of_fairRun`; the companion document is `pipelining-and-multi-leader.md`). `IsLeaderBlock` characterises the *candidates* for a slot rather than selecting one. A Byzantine leader may have several; a correct leader has at most one, by non-equivocation. The commit rule therefore quantifies over candidates; the skip rule does not, and §3.5 records why. ### 3.5 The decision relation Every rule in this development decides a slot the same way: a direct rule fires on the validator's view, or the slot is read off the nearest **eligible** committed slot above it through a link from that anchor. What varies between the rules is the data, and the relation takes it as a record (`LeanDag/Common/Anchored.lean`): the wave, the two direct predicates as a view evaluates them — the commit decidable, so that a validator computes it — a number of graded rungs, each a link from the anchor to a candidate, and a tie at each rung: ```lean structure AnchoredRule (Validator BlockId Payload) (P : Validity …) (honest : Finset Validator) where wave : ℕ Commit : (U : BlockRecord …) → U.View → BlockId → ℕ → Prop decCommit : ∀ (U : BlockRecord …) (V : U.View) (L : BlockId) (r : ℕ), Decidable (Commit U V L r) Skip : (U : BlockRecord …) → U.View → Slots Validator → ℕ → Prop rungs : ℕ Link : ℕ → (U : BlockRecord …) → BlockId → BlockId → Slots Validator → ℕ → Prop tie : ℕ → BlockId → BlockId → Prop ``` The core is the instance at wave two with one rung, the certificate in the anchor's history, and no tie — a certified candidate is unique: ```lean def coreAnchored : AnchoredRule Validator BlockId Payload ValidWrt Correct where wave := 2 Commit := fun U V L r => DirectCommitIn U V L r Skip := fun U V S k => DirectSkipSlotIn (S := S) U V k rungs := 1 Link := fun _ U A L S k => CertifiedIn U A L (S.slotRound k) tie := fun _ _ _ => False ``` Each field is one of the shapes of `Common/Rules.lean`, a threshold on a set the record defines, so that a rule reads as a card of thresholds: ```lean abbrev supportCommit (t : ℕ) (U) (V : U.View) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V t (votesFor U L (r + 1)) abbrev certCommit (Vote) (t t' off : ℕ) (U) (V : U.View) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V t (certificatesAt U (Vote U) t' L (r + off)) abbrev blameSkip (t : ℕ) (U) (V : U.View) (k : ℕ) : Prop := HoldsAtLeast U V t (slotBlamers U k) abbrev certifiedLink (Vote) (t' off : ℕ) (U) (A L : BlockId) (r : ℕ) : Prop := LinkedVia U A (certificatesAt U (Vote U) t' L (r + off)) abbrev coneLink (t : ℕ) (U) (A L : BlockId) (r : ℕ) : Prop := t ≤ (coneSupporters U A L (r + 1)).card ``` The core's `DirectCommitIn`, `DirectSkipSlotIn` and `CertifiedIn` are `certCommit IsVote q q 2`, `blameSkip q` and `certifiedLink IsVote q 2` at the quorum `q = n − f`; Odontoceti and Orcaella are `supportCommit`, `blameSkip` and `coneLink` at theirs; Nemo is `supportCommit` at a majority with no skip; Hydrozoan's two commit routes are `supportCommit` and `certCommit`. What is not a shape is the rule's own: Mahi-Mahi's per-candidate skip, FinWhale's evidence rules, the second rungs of Hydrozoan and Optimal-Hydrozoan. The relation itself is stated once, over any such record. `R.Eligible k j` is `EligibleAt R.wave k j`; `R.RungEmpty U A i k` says no candidate of `k` is linked at rung `i` from `A`; `R.Least U A i k L` says no linked candidate is preferred to `L` by the rung's tie: ```lean inductive Decided (U : BlockRecord …) (V : U.View) : ℕ → Option BlockId → Prop | directCommit {k L} : IsLeaderBlock U k L → R.Commit U V L (S.slotRound k) → Decided U V k (some L) | directSkip {k} : R.Skip U V S k → Decided U V k none | indirectCommit {k j A L i} : k < j → R.Eligible k j → Decided U V j (some A) → (∀ m, k < m → m < j → R.Eligible k m → Decided U V m none) → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → IsLeaderBlock U k L → R.Link i U A L S k → R.Least U A i k L → Decided U V k (some L) | indirectSkip {k j A} : k < j → R.Eligible k j → Decided U V j (some A) → (∀ m, k < m → m < j → R.Eligible k m → Decided U V m none) → (∀ i, i < R.rungs → R.RungEmpty U A i k) → Decided U V k none ``` Here `some L` records a commitment and `none` a skip; the core's `Decided U V k v` is this relation at `coreAnchored`. At one rung the indirect commit is the certified candidate and the indirect skip is "no candidate certified" (`Decided.indirectCommit_single`, `Decided.indirectSkip_single`). Hydrozoan and Optimal-Hydrozoan (§22, §23) are the two-rung instances, where the second rung fires only when the first is empty for every candidate. **A skip is a count, not a quantifier.** The rule above asks for `DirectSkipSlotIn`: a quorum of distinct validators whose voting-round block, in view, references no candidate of the slot. The alternative — every candidate the universe holds blamed by a quorum — is the same condition wherever a candidate exists and no condition at all where none does, and it is unsound across universes, which is where a deployment lives: a validator cannot tell "the leader published nothing" from "the leader's block has not reached me", and a skip taken on absence can be overturned by a block that arrives afterwards, the two verdicts then sitting in different universes where §5's uniqueness theorem does not compare them (`ugrow_commits_recur`, §24, exhibits the pattern). Where a candidate exists the two forms agree, since a block referencing no candidate references not that one (`directSkipIn_of_directSkipSlotIn`), so M1 and M3 and the whole of §5 apply to the counted form; where none exists the count still asks for a quorum, which is exactly the reference implementation's `enough_leader_blame`, and the form Hydrozoan (§22) and Mahi-Mahi (§17) use. A skip's blockers are blocks that exist and a later candidate is referenced by none of them, so persistence (`Persist.of_banded`, §16.1) holds unconditionally for the core and SS5 (§12.3) needs no counting hypothesis. The relation is not a function. A decision procedure would recurse upward in slot index with no *a priori* bound, requiring fuel or partiality for no benefit, since nothing in the development needs to compute. The two indirect cases anchor on the nearest **eligible** committed slot above `k` — not simply the nearest one. Under pipelining the slots immediately above `k` sit one and two rounds up, where no certificate for `k` is reachable, and anchoring there would turn one validator's direct commit into another's indirect skip; the anchor must clear `k`'s decision round (§3.4), which is Algorithm 3's own filter. For the same reason the intermediate premise quantifies over the eligible slots between `k` and the anchor only: the ineligible ones are routinely committed under pipelining, and requiring them skipped would leave `k` undecidable forever. The direct reading of "nearest" — that no eligible slot strictly between is committed — is a negative premise, which an inductive definition cannot carry. It is stated positively, as the requirement that every eligible slot strictly between be decided `none`. The two are equivalent, since the sweep decides every slot it passes, and the positive form keeps every recursive occurrence strictly positive; guarding the occurrence behind `Eligible` preserves that, `Eligible` being a predicate on two naturals which does not mention `Decided`. This formulation is consumed directly in the principal case of the agreement proof (§5.5). **What a rule owes.** Agreement, monotonicity and the ledger theorems are proved once, for any rule whose data satisfy `AnchoredRule.Laws`: two direct commits of a slot, from any views, name one block; a direct commit and a direct skip exclude each other; a direct commit is linked at some rung from every candidate of an eligible slot, and is the only block the tie-break can choose there; a direct skip excludes every link; two choices at one rung agree; the direct rules grow with the view; and the skip and the links read the schedule at their own slot only. For the core these are M5′, the cross-view M1, the visibility lemma and M3 (`coreLaws`). ### 3.6 The ledger ```lean def commitSeq (g : ℕ → Option BlockId) (n : ℕ) : List BlockId := (List.range n).filterMap g def ledgerSet (U) (g : ℕ → Option BlockId) (n : ℕ) : Set BlockId := {b | ∃ k, k < n ∧ ∃ L, g k = some L ∧ Reaches U L b} def OutputAt (U) (g : ℕ → Option BlockId) (b : BlockId) (k : ℕ) : Prop := (∃ L, g k = some L ∧ Reaches U L b) ∧ ∀ j, j < k → ∀ L, g j = some L → ¬ Reaches U L b ``` Committing the leader of a slot releases its entire causal history. `OutputAt` identifies the slot at which a block first enters the ledger. Here `g` ranges over a validator's assignment of verdicts to slots. --- ## 4. The trust boundary Three kinds of condition appear in the development, and the report distinguishes them throughout, because they differ in who controls them. **Specification.** A *correct* validator is by definition one that executes the algorithm. The clauses of §4.1 are therefore not hypotheses about an uncertain world; they are the algorithm, and a designer is free to choose them. **Fault model.** How many validators fail to execute the algorithm, and how their number is bounded (§4.2). Assumed. **Network.** Delivery. The network is adversarial and lies outside the trusted computing base (§4.3). Assumed. Logically all of these are antecedents: each is a field of a structure or class, and every theorem quantifying over a block universe or over the relevant instances carries it. None is an axiom in the sense of §25, and their joint satisfiability is a proof obligation discharged by exhibition (§24) rather than something the logic must be trusted for. The distinction drawn here is epistemic, not logical, and it is what determines where the trust boundary of the system actually falls. ### 4.1 The protocol | | Clause | Formalisation | |:---|:---|:---| | P1 | references lie one round below | `ValidWrt.predecessor` | | P2 | no block cites one author twice | `ValidWrt.distinct_creators` | | P3 | non-genesis blocks cite `n−f` distinct authors | `ValidWrt.quorum` | | P3′ | non-genesis blocks cite a block by their own creator | `ValidWrt.self_parent` | | P4 | a block is held only with its causal history | `BlockUniverse.complete` | | P5 | one block per round: correct validators do not equivocate | `BlockUniverse.no_equivocation` | | P6 | the slot schedule is monotone, unbounded and keyed | `Slots.mono`, `Slots.unbounded`, `Slots.keyed` | | P7 | a validator references everything it accepted | `Delivery.includes` (storage side); `ViewPace.references` (liveness side) | | P8 | a validator has a genesis block, and builds on holding a quorum | `ViewPace.built_of_le_top` (genesis, at `n = 0`), `ViewPace.advances` | | P9 | a validator waits a full timeout before building | `ViewPace.waits` | | P10 | the leader schedule names reliable validators arbitrarily far out | `FairScheduleOn` | | P11 | seeing a round is entering it: past GST, a held round-`n` block forces entry within `proc` | `PaceCore.catchup` | The liveness development adds a **store model** — five clauses about a validator's own holdings, which is the third thing (beside the protocol and the network) an implementation must supply. They are not bookkeeping: each excludes a behaviour a deployment could exhibit, and without the last two the model would describe runs no implementation could produce (§6.12). | | Clause | Formalisation | |:---|:---|:---| | S1 | a validator holds only blocks that exist | `PaceCore.holds_sub` | | S2 | holdings are causally closed: a held block's references are held | `PaceCore.holds_closed` | | S3 | a block references only what its author held when it built | `PaceCore.refs_held` | | S4 | a validator holds its own block from the moment it built it | `PaceCore.holds_own` | | S5 | holdings only grow | `PaceCore.holds_mono` | S4 fails for a validator that crashes before persisting its own block, and S5 for one that garbage collects inside the liveness window — which is why §9's results are stated over explicit windows. S2 is P4 as a *store* property: a block whose history is missing can be neither validated (P3 and P3′ read the referenced blocks) nor built upon, so without the clause the model would oblige a validator to advance on evidence no implementation could act on, and adding it therefore *weakens* what is asked. S3 is the converse of P7, and it is what makes the delivery layer of §8 derivable rather than postulated (§6.12). P1–P6 are consumed by the safety development, P7–P11 additionally by liveness; P3′ by safety never, and by liveness through chain quality's inclusion half (CQ5–CQ7, §7) and the reactive backbone (RS5, §11.5) — it is indispensable to §8, and consumed again by the fill of §12. P10 is a joint condition rather than a pure specification: the schedule is the designer's, but which validators are reliable is not. Round-robin discharges it whenever the reliable set is of quorum size, since at most `f` of every `n` consecutive leaders then lie outside it; `rrSlots` witnesses this with a window of `f + 1` (§24). **P8 deserves the most emphasis of any clause here**, and is easily mistaken for a routine one. It states that a correct validator holding a quorum at round `r` *has* a block at round `r+1`; equivalently, that correct validators do not advance past a round without building in it. Qiu, Xiao and Shao [QXS26] show this clause cannot be dropped: with honest validators free to jump over rounds, they exhibit an infinite execution of Mysticeti in which at most `2f` certificates are ever created for any round, so no slot is directly committed and — direct commitment being what the indirect rule rests on — nothing is committed at all. Their fix is a restriction on round-jumping; Starfish [PMV25] adds the same condition as pacemaker rule A2, requiring a validator to have created its round-`(r-1)` block before advancing to round `r`. The clause is also not automatic in practice: [QXS26] audited the Sui implementation and found it susceptible to exactly this attack. So P8 is the point at which the liveness development is conditional on something that deployed code has been observed not to satisfy. It is nonetheless a clause of the protocol in the sense of this section — a designer can implement it, and both cited works tell one how — which is why it appears here rather than in §4.3. The clause is split into its two halves: `built_of_le_top` at `n = 0` is genesis, and `advances` is the progress rule. The shape answers [QXS26] in their own terms, and is worth drawing out. P8 could be stated as *block production at a round* — hold a quorum, and a block exists one round up. `ViewPace` states it instead as *round advancement*: hold a quorum at any time, and the validator gets past the round (`advances`); and every round it reached, it built in (`built_of_le_top`), with none above (`le_top_of_built`). That pair is Starfish's pacemaker rule A2 as a structural invariant rather than a side condition — a validator has its round-`(r−1)` block before advancing to round `r`, because in this model reaching a round *is* having built there. It is what makes round-jumping inexpressible rather than merely excluded: the counterexample of [QXS26] has honest validators skipping rounds, and no `ViewPace` describes such a run. The form assumed here remains stronger than either published fix, which excuse a validator that has already decided round `r'-2` and admit a *global catchup time* before which the rule need not hold; `ViewPace.advances` names no deadline but allows no exception. No minimality is claimed for the clause. P2 is a second place where the model does not simply transcribe the protocol. Mysticeti's validity check requires a block to cite `n−f` *distinct* authors at the round below but does not forbid citing an equivocating author's second block as well; uniqueness of support is recovered instead by defining a supporter to be one that references the *first* leader-slot block among its references. P2 forbids the duplicate citation outright, and uniqueness then follows without a tie-break — this is P2's sole use, in M5′ (§5.4). The two devices agree in effect, but P2 is the stronger requirement, and a reader comparing the model against a deployed implementation will find a validity condition Mysticeti does not impose. P5 deserves emphasis, since it is conventionally described as an assumption. It is a clause of the algorithm — a correct validator produces one block per round because that is what it was told to do — and it is the sole point at which the behaviour of a correct validator is recorded on the safety side. Nothing else in the model constrains it, `Correct` being a set complement (§2.1). P9 is the clause whose *sufficiency* is not under the designer's control: the timeout may be chosen freely, but whether the chosen value is long enough depends on the network. §6.10 determines the threshold it must meet — the constant `2Δ + proc` — and §26.1 discusses the consequences. P11 is the second pacemaker rule, and the counterpart of `advances`: where P8 forces a validator forward on a *quorum*, P11 forces it forward on a *single sighted block*, within the processing bound `proc`. It is the rule real pacemakers run — Starfish's rule B2 requires a validator to broadcast and align on advancing a round, not only on creating a block — and it is what makes drift a derived quantity rather than an assumed one: whatever the spread between validators' clocks, one post-GST round contracts it to `Δ + proc` (§6.11), so no start-spread hypothesis survives anywhere in the development. The clause cannot be exploited by a validator that does not wait: any valid block certifies a reliable validator that paid the full timeout bill for every round below it (CU5, §6.11), so the author-blind form a deployment runs never pulls anyone past the honest schedule. Like `converges`, the clause is asserted only from `gst` — and for the same reason. No validator knows GST or `T`; what it runs is the GST-free clamped rule, *enter a sighted round within `proc`, never before your own floor*. Past GST the floor provably never delays the entry, so the clause holds of the run; before GST it may, so an unconditional clause would over-claim about every real implementation. The gate places the condition where partial synchrony always places it: on the guarantee, not on the validator. ### 4.2 The fault model | | Assumption | Formalisation | |:---|:---|:---| | A1 | there are `n ≥ 3f+1` validators | `Faults.card_validators` | | A2 | at most `f` are Byzantine | `Faults.card_byzantine` | Byzantine validators are unconstrained: they may publish nothing, publish selectively, or equivocate freely. The quorum size `n − f` is written `quorumCard Validator` throughout the development — notation rather than a definition, deliberately: the term *is* `Fintype.card Validator - F.f`, so every lemma and every arithmetic tactic sees the subtraction it always saw, while statements read and print as the quantity they mean. **The combined budget.** The principal liveness argument counts to `n−f` and no higher, so what it requires is a quorum of validators that are both correct and timely, rather than the participation of every correct one. Formulating this as `T ⊆ Correct` with `n−f ≤ T.card` — a hypothesis of L4 and L6 — yields the operative budget: > `actual_byzantine + persistently_slow_correct ≤ f` A correct validator which is persistently slow consumes budget exactly as a Byzantine one does. This is a hybrid condition: correctness is a fault-model matter, timeliness a network one. At `f = 1` there are four validators and `|Correct| = 3 = n−f` exactly, so no margin exists and every correct validator must be timely; margin appears only when fewer than `f` validators are in fact faulty, and the `T`-parameterised statements make it available automatically. The specialisations at `T := Correct` (`directCommit_of_correct_leader`, `decided_of_correct_leader` (L4′), `commits_recur`) recover the conventional statements. ### 4.3 The network The development asks the environment for two *things*, and each admits several *formulations*. Keeping those apart is what this section is for: the roles are fixed, the formulations are a modelling choice, and two of the formulations are not as pure as their names suggest. | Role | What is wanted | Formulation | |:---|:---|:---| | **Production** | what exists is eventually obtained, so the DAG keeps growing | view convergence (`converges`, from GST on), with genesis as the only seed | | **Coverage** | after stabilisation, delivery is prompt enough that blocks reference one another | N2, as `converges` — one sentence about views; `EventuallyDelivers` is the storage arcs' delivery premise only | Safety (§5) uses **none** of them: it holds under arbitrary asynchrony, arbitrary loss and arbitrarily divergent views. #### The vehicles The formulations do not all range over the same object, and the differences matter more than they appear to. | Field | Indexed by | Structure | |:---|:---|:---| | `held v n` | the **round** `v` is building over | `Delivery` (§6.2), the storage model | | `holds v t` | the **instant** `t` | `ViewPace` (§6.9), the liveness structure | `held v n` is what `v` had in hand *at the moment it built its round-`(n+1)` block* — not what it eventually receives. That build-time index is the essential modelling device (§26.1): a block's references are frozen at construction, so what bears on the DAG's shape is what was held when the builder acted. `View.ids` is a finite set of identifiers with no index of either kind, which is why no formulation is stated over it. #### Production Production is derived, not assumed. The liveness results consume it as a `PopulatedOn` hypothesis, and `ViewPace.populatedOn` discharges it from genesis, view convergence and the pacemaker's progress rule (§6.9), at every round from `0`. The network contributes nothing to production beyond the one convergence clause it already supplies for coverage; the only production assumed outright is a round-`0` block, which a validator produces alone. #### Coverage: N2, as view convergence ```lean converges : ∀ v ∈ T, ∀ w ∈ T, ∀ t, gst ≤ t → holds w t ⊆ holds v (t + delay) ``` `converges` is partial synchrony as one would state it in words — *after GST, whatever a correct validator holds reaches every correct validator within Δ* — and mentions no block, no round and no reference. It is the whole of what is assumed of the network. The fused shape — *a block built after GST and early enough is referenced* — is derived from it by applying the protocol's referencing clause P7 (`ViewPace.covers_of_converges` (V1)), which is where the claim that the network's contribution is one sentence about views is discharged. `EventuallyDelivers` concludes about `held v n` — the *build-time* index — and so presupposes that the builder waited; it carries protocol content a network assumption should not, which is why no liveness result is stated over it. The storage arguments of §§8–9 genuinely consume a post-`R` delivery premise, and for a store — unlike for coverage — the build-time index is the honest one. Two further relations, both proved in §6.9. The bound factors out: convergence within Δ is exactly eventual convergence whose lag is uniformly bounded after GST (`convergesWithin_iff_bounded` (V4)), so *view convergence under synchrony = view convergence + a bound*. And eventual convergence **alone** yields nothing: an unbounded lag cannot be compared with a timeout, so no argument places the block in the builder's hands before it builds. The missing ingredient is not a stronger network but the protocol's waiting rule — which is why P9 stands beside N2 in §4.1 rather than inside it. #### What none of them says - **Nothing about Byzantine senders.** A Byzantine validator may deliver to some correct validators and not others, may send equivocating blocks to two of them, or may publish nothing. Every formulation quantifies over *correct* authors only, and safety tolerates the rest unconditionally — a Byzantine block reaching one view and not another is exactly what the cross-view theorems of §5 are stated to survive. - **Nothing about which blocks arrive**, beyond correct ones after `R`. - **Nothing before GST**: arbitrary delay, reordering and loss are permitted there, and every safety result continues to hold. #### Where they are consumed Neither role is discharged where its name suggests, and the extracted support graph (§25) makes the pattern checkable rather than asserted. Production is consumed as a `PopulatedOn` hypothesis: L6, the committed-run results, the quantitative results and the capstones of §§7–10 never reach for the assumption that supplies it, and `ViewPace.populatedOn` discharges it at the point of application. **`EventuallyDelivers` is consumed by stores, not by coverage**: the view-gap constant C3′ and hence the budget sandwich of §8.4; the accepted-quorum lemma (`card_creators_accepted_of_eventuallyDelivers`); the bound placing the attested base inside a correct peer's retained store (G6b, §9.3); and the one-round universalisation of possession (G9, §9.5). All four are facts about **stores**, where coverage is a fact about **references** — and coverage never reads it. #### What is actually trusted Collecting the qualifications, what the environment is trusted with is narrower than the two-role summary suggests. *For coverage*, one condition on the environment: view convergence, bounded after GST. *For production*, the same condition again, and the deployed behaviour it rests on is the ordinary one: hold a quorum of distinct authors, advance — which is precisely `ViewPace`'s `advances`, from which production at every round follows with genesis the only seed (`ViewPace.populatedOn`, §6.9). No delivery premise, no clock condition and no per-round production assumption appears anywhere in it. *And nothing else.* No condition on the environment appears in the development beyond these two roles, in one of the formulations above; the remaining hypotheses of §4.1 are clauses of the algorithm, which a designer controls. Reference coverage itself — the condition all of liveness rests on — is not a further assumption but a consequence, which is the subject of §4.4. ### 4.4 Derived, not assumed Eventual DAG synchrony is not an assumption of the development. It is a property of executions, obtained from the network assumption of §4.3 together with clauses of the protocol: | | From | Result | |:---|:---|:---| | Coverage | N2 (`converges`) with P7, P8, P9 and P11 | `ViewPace.synchronisedOn_of_converges` (L7) | | Production | N2 (`converges`) with P8 and genesis | `ViewPace.populatedOn` (V17) | It is stated as a hypothesis of L4 and L6 in order to keep those arguments free of temporal notions (§6.8), and supplied to them by the results above. §26 discusses the formulation. **What "derived" does and does not mean here.** Coverage is derived from the network assumption *together with clauses of the protocol* — P7 and P9, with the pacemaker rules P8 and P11 supplying the drift bound the race is run against — and is consumed by L4 alongside production, which comes from P8 and genesis. The claim is therefore not that eventual DAG synchrony holds in any execution of any DAG protocol; it is that it need not be *postulated* separately, because the network assumption already standard in this literature, combined with build rules a designer controls, entails it. The distinction matters because the corresponding claims in the source literature have not survived scrutiny. Mysticeti's Lemma 8 and Cordial Miners' Proposition 38 both assert that honest validators are synchronised after GST; [PMV25] reports that both leave gaps, and [QXS26] shows the gap is not merely expositional — with round-jumping unrestricted the conclusion is false. The present development is not exposed to that counterexample, but the reason is P8, which excludes round-jumping outright (§4.1). Properly read, this is the stronger position: it identifies precisely which protocol clause the structural condition depends on, rather than asserting the condition and leaving the price implicit. ### 4.5 Quantitative clauses The results of §6.10 require the following in addition. All four are further specification, strengthening clauses already present; none is an assumption about the world, and no quantity in any of them is set by deployment. | | Clause | Kind | Yields | |:---|:---|:---|:---| | R1 | `Rated timeout`: `∀ n, n ≤ timeout n` | specification | an explicit round `R` | | R2 | `FairWithin T w`: a `T`-leader within every window of `w` slots | specification | a bounded committing slot | | R3 | `BoundedSpacing s`: slots at most `s` rounds apart | specification | that slot's round, and a horizon | | R4 | `∀ n, 2Δ + proc ≤ timeout n` | specification | the wait bound `Delay(Δ)` | Every result of §5 and §6.1–§6.10 stands without them. **No deployment quantity exists in the development.** Were drift *preserved* rather than established by the schedule, a bound `D₀` on the round-`0` spread would have to be supplied from outside and would enter the wait threshold as `Delay(Δ) = D₀ + Δ` — a quantity depending on how validators were started rather than on the network or the specification. The pacemaker's catch-up rule (P11) forecloses it: the spread collapses to `Δ + proc` at the first fully-post-GST round whatever its starting value (§6.11), so the threshold is the constant `2Δ + proc` and nothing anywhere states how the validators started. Starfish [PMV25] obtains the corresponding statement — its Lemma 4, that honest validators enter every round past GST within Δ of each other — from its rule B2 in the same way: a consequence of a pacemaker clause, not a hypothesis about deployment. ### 4.6 What the adversary may do The clauses above say what correct validators do and what the network provides. This section states the complement: the behaviour a Byzantine validator is permitted, which is everything not excluded above. An implementation that defends against less than this is defending against the wrong adversary. **Equivocate.** P5 (`BlockUniverse.no_equivocation`) quantifies over `Correct` alone, so a Byzantine author may publish any number of distinct blocks for one round. Nothing in the safety development limits how many: §8.1 shows the equivocation degree enters no safety statement, and the `Utwin6` (O11) model exhibits two blocks by one author each passing Odontoceti's indirect test against a third (§10.5). **Withhold entirely.** No clause obliges a Byzantine validator to publish anything. A Byzantine *leader* may therefore leave its slot undecided, which is why P10 asks only that reliable leaders recur, and why L5 (skipping) exists. **Send selectively.** A Byzantine author may deliver a block to some correct validators and not others, at any time. Every network formulation in §4.3 is restricted to correct-authored blocks for exactly this reason: `converges` carries the restriction in its statement, quantifying over `T` alone, and a formulation that dropped it would be assuming Byzantine validators behave, which is not an assumption anyone can implement against. **Reveal late.** Delivery bounds apply from GST and to correct authors. A Byzantine validator may release a block long after building it, and the commit rules must — and do — treat a late block as a block. **Lead any slot.** `Slots.leader` is an arbitrary function; the schedule is not assumed to favour correct validators. P10 asks only that reliable leaders appear arbitrarily far out, which a round-robin schedule discharges whenever the reliable set is of quorum size. **What the adversary may not do** is exactly three things, and each is either cryptographic or a counting bound. It may not forge a block under another validator's name — authorship is taken as authenticated, the one cryptographic assumption in the development and the only clause of §4 with no Lean counterpart. It may not exceed `f` in number (`Faults`, §4.2). And under the DoS conditions of §4.7 it may not force a correct validator to store more than the stated bound. **The limits are witnessed, not merely stated.** Three models show that weakening a network hypothesis does not merely block a proof but makes the conclusion false: `bound_is_necessary_pace` (V10) (the delivery bound cannot be dropped for coverage), `gst_is_forced_pace` (V11) (the starting round cannot be dropped), and `reliable_set_is_forced_pace` (V12) (coverage over the reliable set does not extend to `Correct`). §6.9 gives them in full. ### 4.7 The denial-of-service conditions §8 assumes four further conditions, none of them about the network. Two are enforceable by a correct validator acting alone, which is the property that makes them deployable; the other two are structural. ```lean def DoSValid (U : BlockUniverse Validator BlockId Payload) : Prop := ∀ b ∈ U.ids, ∀ i ∈ (U.block b).refs, ¬ ExposedIn U b (U.block i).creator ``` **D (`DoSValid`) — do not build on an exposed author.** A block never references a block whose author is already exposed as an equivocator within the referencing block's own history. Checkable locally, since exposure is a fact about the cone a validator already holds. ```lean def UniformBudget (D : Delivery U) (τ : ℕ) : Prop := ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ D.accepted v (n + 1), (novelty U (viewUpto D v n) b).card ≤ τ ``` **The novelty budget — accept nothing that costs more than `T` new blocks.** `UniformBudget` is the author-blind form and the one an implementation should use: it consults no identity, so a validator can enforce it without knowing who is correct. `ByzBudget κ` is the same bound imposed only on Byzantine-authored blocks; it is what the theory needs, and `uniform_of_byzBudget` (B6) shows the enforceable form implies it. ```lean def RefsAccepted (D : Delivery U) : Prop := ∀ w ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ U.ids, (U.block b).creator = w → (U.block b).round = n + 1 → (U.block b).refs ⊆ D.accepted w n ``` **The reference discipline — reference only what you accepted.** The converse of P7, and equally a clause an implementation executes. It is stated more tightly than the storage bound needs: §16.7 shows the pool argument requires only that a block's references lie inside *some* correct validator's acceptances, its author's or not. The distinction matters for deployment: `DoSValid`, `UniformBudget` and `RefsAccepted` are conduct a validator can follow unilaterally, so the storage bound of §8.5 rests on nothing an operator must trust others to do. `ByzBudget` is not, and appears only as the weaker hypothesis the enforceable one discharges. ### 4.8 Where each assumption is consumed Extracted from the compiled development rather than compiled by hand: a result appears against a clause when its proof reaches that clause, by any path. Read down a column to see what an implementation puts at risk by violating a clause; read across to see what a result depends on. | | Clause | Consumed by | |:---|:---|:---| | P1 | `ValidWrt.predecessor` | T2, T3, T3a, T3c, M1, M2, M3, M4, M5′, M5, M6, L0, CQ3, CQ5, CQ6, CQ7, C2, D15a, C1′, C3′, B4, B, B5, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G10, G11, G12, G8, G9, SS1, SS2, SS3, SS4, SS5, SS6, SS10, AL3, AL5, AL6, I1, I2, I4–I17, I19 | | P2 | `ValidWrt.distinct_creators` | M5′, M5, M6, C1′, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G12, G8, O1′, O4′, O5, O6, SS1, SS2, SS3, SS4, SS5, SS6, SS8, SS9, SS10, AL3, AL5, AL6, AL7, H2, H5, H6, I1, I2, I4–I16 | | P3 | `ValidWrt.quorum` | T3, T3a, T3c, M2, M4, M6, L0, CQ5, CQ6, CQ7, D15a, B5, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G10, G11, G12, G8, SS1, SS2, SS3, SS4, SS5, SS6, AL3, AL5, AL6, I1, I2, I4–I16, I19 | | P3′ | `ValidWrt.self_parent` | RS5, CQ5, CQ6, CQ7; C1′, C3′, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G11, G12, G8, G9, SS1, SS2, SS3, SS4, SS5, SS6, SS8, SS9, SS10, I1, I2, I4–I17 | | P4 | `BlockUniverse.complete` | T2, T3, T3a, T3c, M1, M2, M3, M4, M5′, M5, M6, L0, L3, L6, L8b, CQ3, CQ5, CQ6, CQ7, C2, D15a, C1′, C3′, B4, B, B5, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G10, G11, G12, G8, G9, O7, O10, SS1, SS2, SS3, SS4, SS5, SS6, SS10, AL3, AL5, AL6, AL7, H7, I1, I2, I4–I17, I19 | | P5 | `BlockUniverse.no_equivocation` | T1, T3, T3a, T3c, M1, M2, M3, M4, M5′, M5, M6, RS5, C2, D15a, C1′, B4, B, B5, G1, G2, G3, G4, G5, G13, G14, G6, G6b, G7, G12, G8, O1, O1′, O2, O4′, O5, O6, SS1, SS2, SS3, SS4, SS5, SS6, AL3, AL5, AL6, AL7, I1, I2, I4–I16, I19 | | P7 | `Delivery.includes` | C3′, B5, G6, G6b, G7, G11, G12, G9; on the liveness side `ViewPace.references`, feeding L7 and V17 | | P8 | `ViewPace.advances` | V17, and through it every liveness capstone; L11, through `reached` | | P9 | `ViewPace.waits` | L7, L8a, L9, L11, V17 | | P10 | `FairScheduleOn` | L6, CQ6 | | P11 | `PaceCore.catchup` | L11 (CU2), and through it L7, L8a, L9, RS1–RS3, RS5 | | N2a | `EventuallyDelivers` | C3′, G6b, G7, G9 — store facts only (§4.3) | | N2 | `ViewPace.converges` | L7, V17, and through them every liveness capstone | | S1 | `PaceCore.holds_sub` | V18, V19 | | S2 | `PaceCore.holds_closed` | V18 (`viewAt_ids`) | | S3 | `PaceCore.refs_held` | V19, V20 | | S4 | `PaceCore.holds_own` | L7, V17, V18, and through them every liveness capstone | | S5 | `PaceCore.holds_mono` | L7, V17, V18, and through them every liveness capstone | Three readings are worth drawing out. **P8's consumers are the production derivation**: the liveness results take production as a `PopulatedOn` hypothesis rather than deriving it inline, so the clause is reached only through `ViewPace.populatedOn` (V17). **P3′ is absent from safety entirely**, and on the liveness side feeds chain quality's inclusion half (CQ5–CQ7, §7) and the rotation backbone RS5 (§11.5), beside the DoS, garbage-collection, safe-skip and integration arcs. **P5 is absent from the coverage path**: the route's clauses are stated over any authored block, so non-equivocation identifies nothing there, and its one liveness consumer is likewise RS5, whose self-parent chain lands by T1 on the block it claims. One absence in the integration column is worth reading. **I18 appears in no row**, being a statement about production rather than about validity; a clause table over universe conditions is exactly where it should fail to appear. §16.6 locates the clause's cost as well as its uses: it is what makes a cone a complete record of its author's acceptances in §8, and what obliges the fill of §12 to enlarge a cone past what the donor vouched for. And **P4 and P5 appear almost everywhere**, which is the honest shape of the development: causal closure and non-equivocation are what the DAG is, not conditions imposed on it. --- ## 5. Safety ### 5.1 Quorum intersection and non-equivocation **T0.** ```lean theorem exists_correct_mem_inter {Q₁ Q₂ : Finset Validator} (h₁ : quorumCard Validator ≤ Q₁.card) (h₂ : quorumCard Validator ≤ Q₂.card) : ∃ v ∈ Q₁ ∩ Q₂, v ∈ (Correct : Finset Validator) ``` Its block-level counterpart T0′ (`exists_correct_mem_creators_inter`) states that two identifier sets whose creator sets are quorums share a correct author. **T1.** ```lean theorem BlockRecord.eq_of_creator_eq {v : Validator} {i j : BlockId} (hi : i ∈ U.ids) (hj : j ∈ U.ids) (hv : v ∈ honest) (hic : (U.block i).creator = v) (hjc : (U.block j).creator = v) (hround : (U.block i).round = (U.block j).round) : i = j ``` Stated at any block record, whose honest set is the core's `Correct`. The statement is organised around the author `v` rather than around `(U.block i).creator`, since that is the form in which every use site arrives: a quorum intersection yields a correct validator, and T1 converts two blocks known to be authored by it into a single identifier. The composite `BlockUniverse.exists_common_mem_of_quorums` combines the two: two quorum-backed sets of round-`n` blocks share a *block*. This is the recurring step by which one layer of certification is removed. **T2** (`round_le_of_reaches`) states that causal history is non-increasing in round. **T6a.** ```lean theorem View.mem_of_reaches (hc : c ∈ V.ids) (h : Reaches U c b) : b ∈ V.ids theorem View.exists_reaches_iff (hc : c ∈ V.ids) : (∃ b, b ∈ V.ids ∧ Q b ∧ Reaches U c b) ↔ (∃ b, Q b ∧ Reaches U c b) ``` Causal history does not escape a view. The second form is what makes a view-relative certificate search well defined: two validators holding different views but the same anchor cannot disagree about its outcome. ### 5.2 Persistence **T3.** ```lean theorem reaches_of_quorum_support {b : BlockId} {r : ℕ} {Q : Finset BlockId} (hQ : Q ⊆ U.ids) (hQround : ∀ q ∈ Q, (U.block q).round = r + 1) (hQref : ∀ q ∈ Q, b ∈ (U.block q).refs) (hQquorum : quorumCard Validator ≤ (creatorsOf U.block Q).card) {c : BlockId} (hc : c ∈ U.ids) (hcr : r + 2 ≤ (U.block c).round) : Reaches U c b ``` Once a quorum of round-`(r+1)` blocks references `b`, every block from round `r+2` onwards has `b` in its causal history. Neither membership of `b` in the universe nor its round need be assumed; both follow from the quorum hypothesis (`mem_ids_and_round_of_quorum_support`). The bound `r+2` is tight. At `r+1` there is a counterexample: with `f = 1` and validators `{A,B,C,D}`, let `b` be `A`'s round-`r` block, referenced by the round-`(r+1)` blocks of `A`, `B` and `C`; `D`'s round-`(r+1)` block may reference `{B,C,D}`'s round-`r` blocks instead, and since all its references lie at round `r`, it does not reach `b`. Quorum intersection requires two reference quorums to compare, and `r+2` is the first round that supplies them. Quorum intersection is used exactly once, in the base case; height above that layer is carried by transitivity alone (`reaches_pred_of_round_le`). **T3c.** ```lean theorem exists_common_correct_ancestor {r : ℕ} {c₀ : BlockId} (hc₀ : c₀ ∈ U.ids) (hc₀r : (U.block c₀).round = r + 2) : ∃ bw ∈ U.ids, (U.block bw).round = r ∧ (U.block bw).creator ∈ Correct ∧ ∀ c ∈ U.ids, (U.block c).round = r + 2 → Reaches U c bw ``` The sole premise is that some round-`(r+2)` block exists — a fact about the DAG in hand rather than an assumption that any party makes progress. The proof rests on a double-counting argument (T3a, `exists_correct_common_support`). The counting: with `b ≤ f` the number of Byzantine validators, each correct round-`(r+1)` block names at least `n−f−b` correct round-`r` authors, and there are `l ≥ 1` such blocks spread over `c = n−b` correct authors, so some round-`r` author collects support from at least `l(n−f−b)/c` of them. Were that always below the needed threshold, the arithmetic obligation would reduce to `c² ≤ f(l+c)`, which `l ≤ c` converts to `c ≤ 2f` — contradicting `c ≥ n − f ≥ 2f+1`. ### 5.3 Consistency of the direct rules **M3** (`certificates_eq_empty_of_directSkip`). A directly skipped block has no certificate anywhere in the universe, not merely none within some view. Given `n−f` blamers, and since a correct validator cannot appear on both sides, supporters and blamers together number at most `n + f` (`card_supporters_add_card_blames_le`, the record's bound at the core's fault model), so the supporters number at most `2f`, one short of a quorum. The universe-wide strength is what allows a skip to require no anchor to justify it, and it is what makes the skip half of M4 unconditional. **M1** (`not_directCommit_of_directSkip`) follows immediately. **M2** (`exists_certificate_reaches_of_directCommit`). Once a block is directly committed, its certificate is unavoidable: every block from round `r+3` onwards has one in its causal history. The bound is tight, since a round-`(r+2)` block which is not itself a certificate reaches none. This is the origin of P6. **M4** (`indirect_agrees_with_direct`). Where the direct rule decides, the indirect rule agrees. The two halves are asymmetric: the commit half requires the anchor to lie at round `≥ r+3`, since the certificate must be reachable, whereas the skip half requires no hypothesis at all, since by M3 no certificate exists anywhere to be reached. ### 5.4 Certificate uniqueness **M5′.** ```lean theorem eq_of_certificates_nonempty {L₁ L₂ : BlockId} {r : ℕ} (h₁ : (certificates U L₁ r).Nonempty) (h₂ : (certificates U L₂ r).Nonempty) (hcreator : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` A slot admits at most one certifiable block. This is stronger than the corresponding statement about direct commits, and it is the form the indirect rule requires, since that rule commits on the strength of a single certificate lying in reach rather than of a quorum of them. The proof requires no relationship between the two certificates. Each names `n−f` distinct voters, so the voter sets intersect in a correct validator `w` (T0′); `w`'s unique round-`(r+1)` block votes for both (T1); and P2 forbids one block from referencing two round-`r` blocks by a single author. This is P2's only use in the safety development; §4.8's row records where the arcs consume it. **M5** (`eq_of_directCommit_of_creator_eq`) follows as a corollary. ### 5.5 Agreement **M6.** ```lean theorem decided_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {k : ℕ} {v₁ v₂ : Option BlockId} (h₁ : R.Decided U V₁ k v₁) (h₂ : R.Decided U V₂ k v₂) : v₁ = v₂ ``` No two validators reach conflicting decisions for a slot, whatever views they hold and by whichever route they decided. As is conventional, this is a *no-conflicting-decision* statement: a validator which has not yet decided is not in disagreement. The theorem is the relation's, at any rule's laws; for the core the laws are `coreLaws` and the invariant is trivial. The proof (`decided_unique`) is by structural induction on the first derivation. Of the sixteen pairings of constructors, fifteen close directly by the laws — commitment against commitment by `commit_unique`, the crossings of direct against indirect by `commit_link`, `commit_link_unique` and `skip_link` — and the remaining case, indirect against indirect, is settled by trichotomy on the two anchors (`eq_of_indirect`), after which the rungs are compared: the first nonempty rung is the same on both sides and its two choices agree (`link_unique`). It is here that the positive formulation of "nearest anchor" (§3.5) is consumed: the negative reading supplies no sub-derivation on which the induction could rest. Two corollaries are stated in the form applications require: `eq_of_decided_commit` (M6′) (no two validators commit different blocks for a slot) and `not_decided_skip_of_decided_commit` (no validator skips a slot another has committed). ### 5.6 Ledger stability **M7.** ```lean theorem commitSeq_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {n : ℕ} {g₁ g₂ : ℕ → Option BlockId} (h₁ : ∀ k, k < n → R.Decided U V₁ k (g₁ k)) (h₂ : ∀ k, k < n → R.Decided U V₂ k (g₂ k)) : commitSeq g₁ n = commitSeq g₂ n theorem ledgerSet_mono (h : n ≤ m) : ledgerSet U g n ⊆ ledgerSet U g m theorem ledgerSet_agree … : ledgerSet U g₁ n = ledgerSet U g₂ n theorem outputAt_unique (h₁ : OutputAt U g b k₁) (h₂ : OutputAt U g b k₂) : k₁ = k₂ theorem outputAt_agree … (hk : k < n) (ho : OutputAt U g₁ b k) : OutputAt U g₂ b k ``` Two validators which have settled the first `n` slots read off the same sequence of committed leaders; the ledger grows monotonically; the two validators output the same set of blocks; and each block enters at exactly one slot, on which they agree. `commitSeq`, `ledgerSet` and `OutputAt` are stated once at the block record (`Common/Ledger.lean`), where monotonicity and uniqueness hold of any verdict assignment and agreement of any two assignments that agree below `n` (`ledgerSet_agree_of`, `outputAt_agree_of`). M7–M9 are those forms at M6; Nemo's ledger (§15) and Black Marlin's flush record (§19) are the same statements at their own assignments. None of these statements mentions an order on identifiers. Ordering the blocks released by a single commit requires a tie-break — a linear order on identifiers or an equivalent — which the development deliberately does not assume. Whether and when a block is output requires no order, and it is precisely these properties that retraction would violate. --- ## 6. Liveness ### 6.0 What liveness requires beyond membership of `Correct` Membership of `Correct` says which validators execute the algorithm; it says nothing about what the algorithm does (§2.1). Liveness statements are therefore vacuous until the relevant protocol clauses are invoked, and three are: - **(a)** validators produce blocks (P8); - **(b)** validators wait before building, and enter a round they have seen (P9, P11); - **(c)** the schedule names reliable leaders arbitrarily far out (P10). Clauses (a) and (b) act in opposition, and the pair P9/P11 brackets them precisely: `waits` is a floor, requiring that a validator not build too early; `catchup` is a ceiling, requiring that one which has seen a round enter it within the processing bound. Reference coverage is not among them. It is not a clause a validator could execute, since it refers to `Correct`, which no validator can observe; it is what (a) and (b) *produce* against a synchronous network, and it is derived accordingly (§4.4, §26.2). The chapter is organised around two interface predicates, and every result above them consumes them as hypotheses rather than reaching for a network assumption: **production** (`Populated`, §6.3) and **coverage** (`SynchronisedOn`, §6.4). Both are discharged from a single network clause, view convergence, in §6.7–§6.9 — by either pacing discipline, the full-timeout one through coverage and the reactive one (§11) through the targeted predicates of §6.6. ### 6.1 Density **L0.** ```lean theorem card_authorsAt_of_lt {r n : ℕ} (hn : n < r) {i : BlockId} (hi : i ∈ U.ids) (hir : (U.block i).round = r) : quorumCard Validator ≤ (authorsAt U n).card ``` If any block exists at round `r`, every round below `r` has at least `n−f` distinct authors. The result requires no assumption beyond validity. Its content is not that the DAG grows, but that it cannot grow tall and thin: a single block high in the DAG forces a quorum of authors at every round beneath it. ### 6.2 The delivery layer ```lean structure Delivery (U) where held : Validator → ℕ → Finset BlockId held_spec : ∀ v n, ∀ i ∈ held v n, i ∈ U.ids ∧ (U.block i).round = n accepted : Validator → ℕ → Finset BlockId accepted_sub : ∀ v n, accepted v n ⊆ held v n accepted_inj : ∀ v n, ∀ i ∈ accepted v n, ∀ j ∈ accepted v n, (U.block i).creator = (U.block j).creator → i = j accepts_correct : ∀ v ∈ Correct, ∀ n, ∀ a ∈ held v n, (U.block a).creator ∈ Correct → a ∈ accepted v n includes : ∀ v ∈ Correct, ∀ n, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round = n + 1 → accepted v n ⊆ (U.block b).refs ``` The indexing of `held` is essential: `held v n` denotes what `v` had in hand *at the moment it built its round-`(n+1)` block*, not what `v` eventually receives. This is the build-time index which a view cannot supply (§26.1). Between holding and referencing sits **acceptance** — at most one block per author, correct blocks always taken — which is deliberately where the protocol may refuse: the DoS arc's novelty budget (§8) is a rule about `accepted`, and the liveness development reads only `accepted`. The structure contains no clock. In the absence of a time model, "waited longer" can manifest only as a larger `held`. **This structure carries no liveness.** It is the storage and acceptance model: the DoS arc's budgets (§8) and the garbage-collection windows (§9) are stated over it, `EventuallyDelivers` (§6.4) feeds their post-`R` increments, and P7's untimed incarnation is its `includes` clause. The liveness development never reads it — production and coverage come from the timed route of §6.9, whose `holds` is indexed by *time* rather than by round, which is exactly the index this structure cannot supply (§26.1). ### 6.3 Progress, and the horizon ```lean def PopulatedFrom (blk : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (T : Finset Validator) (r : ℕ) : Prop := ∀ v ∈ T, ∃ b ∈ ids, (blk b).creator = v ∧ (blk b).round = r def PopulatedOn (U) (T : Finset Validator) (r : ℕ) : Prop := PopulatedFrom U.block U.ids T r ``` The predicate is stated over a raw block assignment and id population (`Common/Participation.lean`), neither of which mentions validity, quorums or a fault model, so every universe type instantiates one definition rather than restating it: `PopulatedOn` is the `BlockUniverse` instance, and the crash universe of §15 takes the same predicate. `PopulatedOn` is the production half of the liveness interface (§6.7): every result above this section takes production as a hypothesis — over `T`, from the synchrony round on — and it is discharged by the derivation of §6.9, which runs the pacemaker's progress rule against blocks that convergence places in the builder's hands, from a seed that is genesis alone. The horizon `N` is not a technical convenience. Since `U.ids` is finite, a formulation demanding blocks at every round unconditionally would require infinitely many distinct blocks in a finite set, so that no universe satisfies it and every theorem assuming it is vacuous. Three consequences follow. First, `N` is a demand upon the DAG rather than a bound upon it: production to horizon `N` requires that correct validators actually possess blocks at every round up to `N`, so that a larger `N` is a *stronger* hypothesis satisfied by *fewer* universes. Generality is obtained by universal quantification over `N`, not by choosing it large. Second, `N` and `R` measure independent quantities — extent and quality respectively — and all four combinations occur. All four combinations occur: `N` large with `R` small is a tall, synchronous DAG that commits; `N` large with `R` large (or with coverage never holding) grows for ever and commits nothing; `N` small with `R` small is synchronous but too short to commit; `N` small with `R` large is short and asynchronous. Third, unboundedness becomes a property of a *family* of universes. The assertion that the ledger grows without bound is not that one DAG commits infinitely often — no finite DAG can — but that no slot is the last one which some sufficiently grown DAG commits. The horizon is consumed exactly where production is derived — the induction of §6.9; L4 itself never mentions `N`. ### 6.4 Eventual DAG synchrony ```lean def SynchronisedFrom (blk : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (T : Finset Validator) (R : ℕ) : Prop := ∀ n, R ≤ n → ∀ b ∈ ids, (blk b).round = n + 1 → (blk b).creator ∈ T → ∀ a ∈ ids, (blk a).round = n → (blk a).creator ∈ T → a ∈ (blk b).refs def SynchronisedOn (U) (T : Finset Validator) (R : ℕ) : Prop := SynchronisedFrom U.block U.ids T R ``` Coverage is hoisted for the same reason as production, and instantiated the same way. The condition is restricted to correct authors on both sides, and both restrictions are consequential. Nothing may be assumed about the existence of Byzantine blocks: a Byzantine validator may publish nothing, or publish and reveal selectively, so no argument may depend upon its blocks being available. Nothing need be assumed either, since the commitment argument counts only correct certificates and the correct validators form a quorum. The stronger reading — that every block is referenced — would amount to assuming that Byzantine validators behave. Well-formedness survives the restriction: a correct block referencing every correct block of the round below already names at least `n−f` distinct creators, so P3 is satisfied without any Byzantine reference. The predicate is antitone in `T` (`SynchronisedOn.mono`), which allows results established at `T := Correct` to be supplied to the quorum-relative statements of §6.6. The condition is derived, not assumed (§4.4); §26 discusses its formulation. ### 6.5 Monotonicity and propagation **L2.** ```lean theorem decided_mono (hl : R.Laws I) (hI : I S U) {V V' : U.View} (hsub : V.ids ⊆ V'.ids) {k : ℕ} {v : Option BlockId} (h : R.Decided U V k v) : R.Decided U V' k v ``` A validator never revises a decision as its view grows. This is to be distinguished from the safety results: those establish that decisions do not *conflict*, whereas this establishes that they do not *change*. The proof is the relation's: the direct rules grow with the view by the laws `commit_mono` and `skip_mono`, and the anchored premises are universe-level, the links being read off the anchor's history and not off the view (§3.3). **L3.** ```lean def View.full (U : BlockRecord Validator BlockId Payload P honest) : U.View := ⟨U.ids, Finset.Subset.rfl, U.complete⟩ theorem decided_full (hl : R.Laws I) (hI : I S U) {V : U.View} {k : ℕ} {v : Option BlockId} (h : R.Decided U V k v) : R.Decided U (BlockRecord.View.full U) k v ``` Every verdict reached on any view holds on the full view. Since the full view is every correct validator's eventual view, this is the formal content of the informal claim that all correct validators eventually reach the same decision. It also fixes the interpretation of `U`: not every block anyone ever wrote, but every block some correct validator ever held. A Byzantine block revealed to nobody is simply not in the universe. ### 6.6 Commitment, skipping and recurrence **L4.** ```lean theorem directCommit_of_leader_mem (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound k) (hpop0 : PopulatedOn U T (S.slotRound k)) (hpop1 : PopulatedOn U T (S.slotRound k + 1)) (hpop2 : PopulatedOn U T (S.slotRound k + 2)) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` with `decided_of_leader_mem` giving the corresponding statement about `Decided`. The intermediate step, `certifies_of_synchronisedOn` — a correct round-`(r+2)` block certifies any correct round-`r` block — is the statement appearing as Lemma 10 of Mysticeti, that after GST every honest validator eventually creates a certificate for a leader block created by an honest validator. That lemma is the one Sailfish identified as flawed and [QXS26] refuted outright. It is a theorem here, and its hypotheses show why: `PopulatedOn` at rounds `r+1` and `r+2` requires *every* validator of `T` to have a block at those rounds, and the counterexample of [QXS26] is precisely a countermodel to that, arranging for only `f+2` to `f+3` of the `2f+1` honest validators to build in any given round. The counterexample therefore refutes neither L4 nor anything else below; what it refutes is the availability of L4's hypothesis under Mysticeti as published, and so, in the terms of §4.1, it refutes P8 (§4.4). The argument consists of two applications of coverage and nothing further. Every correct round-`(r+1)` block references `L`, since `L` is correct-authored and coverage applies at round `r`; every correct round-`(r+2)` block references all of those, so its votes for `L` originate with every correct validator and hence with a quorum, and it certifies (`certifies_of_synchronisedOn`); and since the correct validators form a quorum, the certificates themselves do. The round-level form `directCommit_of_synchronisedOn` is stated without reference to `Slots`, since nothing in the argument depends on `L` being a leader block. The hypotheses are three local population facts. Neither the horizon, nor growth, nor any limiting construction appears. **The commit argument is factored through what the rule counts.** Two predicates name the targeted half of coverage — the only part the commit rules read: ```lean def VotesAt (U) (T) (r : ℕ) (L : BlockId) : Prop := ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = r + 1 → L ∈ (U.block c).refs def CertifiesAt (U) (T) (r : ℕ) (L : BlockId) : Prop := ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = r + 2 → Certifies U c L ``` and one counting theorem per rule consumes them — `directCommit_of_certifiesAt` here, `directCommit_of_votesAt` for the two-round rule of §10 — with production supplying the blocks counted. Coverage implies both predicates (`votesAt_of_synchronisedOn`, `certifiesAt_of_synchronisedOn`), which is how L4's proof runs; and the reactive schedule of §11 supplies them *directly*, without coverage, from its wait clauses. The two pacing disciplines therefore meet at this interface: one commit argument, two suppliers. **L5.** ```lean theorem decided_none_of_leader_absent {V : View …} (h : ∀ b ∈ U.ids, (U.block b).round = S.slotRound k → (U.block b).creator ≠ S.leader k) (hq : quorumCard Validator ≤ (creatorsOf U.block (blocksAt U (S.slotRound k + 1) ∩ V.ids)).card) : Decided U V k none ``` If the leader of a slot published nothing, every view that saw a quorum at the round above decides `none`: with no candidate, every voting-round block blocks the slot, so the skip rule's count reduces to a quorum being present. The second hypothesis is what a validator can actually check, the first being a statement about the whole DAG; §3.5 records why a skip may not rest on the first alone. **L6.** ```lean def FairScheduleOn (T : Finset Validator) : Prop := ∀ k, ∃ k', k ≤ k' ∧ S.leader k' ∈ T def CommitsAt (BlockId) (Payload) (T : Finset Validator) (R k : ℕ) : Prop := ∀ U N, (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound k + 2 ≤ N → ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) theorem commits_recur_on (hT : T ⊆ Correct) (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ CommitsAt BlockId Payload T R k' ``` The order of quantification carries the content. The alternative reading — that given a universe populated to `N`, for every `k` there exists a committing `k' ≥ k` with `slotRound k' + 2 ≤ N` — is false: fairness promises a correct leader somewhere beyond `k`, and that slot may lie beyond the horizon, with nothing to license requesting a nearer one. As stated, `k'` depends only upon the schedule, which is a property of the `Slots` instance and not of any DAG; the universe is then required to have grown far enough. This is also the correct reading of the claim that the ledger grows without bound (§6.3). The unboundedness of slot rounds required by the proof is `Slots.unbounded`, with `Slots.mono` carrying it past the fair slot; three-round spacing, which would supply both, is not assumed (§3.4). ### 6.7 The interface: production and coverage Every liveness result above this line consumes exactly two conditions, and names no route to either: ```lean def CommitsAt (BlockId) [DecidableEq BlockId] (Payload) [S : Slots Validator] (T : Finset Validator) (R k : ℕ) : Prop := ∀ U N, (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound k + 2 ≤ N → ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` Production over `T` from the synchrony round on, and coverage over `T` from the same round — both `Prop`s about a block universe, so the interface is a statement rather than a module boundary. §6.9 derives both; everything in §6.6 consumes them; and the two quantified halves are relative to the *same* `T`, which is what lets the whole account run at any quorum-sized reliable set rather than at all of `Correct` (§6.9's closing discussion). Both pacing disciplines supply the interface: the full-timeout discipline of §6.9 derives coverage and production outright, and the reactive discipline of §11 derives production and the targeted predicates of §6.6, which the commit arguments consume in coverage's place. `Delivery` (§6.2) is the storage and acceptance model — not an independent assumption, since a pacing structure induces one (V19, §6.12) — the DoS budgets of §8 and the garbage-collection windows of §9 are stated over it — and `EventuallyDelivers` is the post-`R` delivery premise of their incremental bounds. Neither is consumed by any liveness result. ### 6.8 The layering ![**The core account: what supports what.** Every arrow is extracted from the compiled Lean environment — `A → B` means `A` is used in the proof of `B`, directly or through unlabelled lemmas, with arrows implied by longer paths removed. Assumptions occupy the left column; each further column is one step from them. A box with no incoming arrow depends only on definitions and unlabelled lemmas; L4 is the notable case, taking its quorum as a hypothesis rather than from the fault model. §25 describes the extraction; a version carrying each result's Lean name is in `docs/depgraph/`.](depgraph/support-core-compact.svg) No theorem above `SynchronisedOn` mentions time, and no theorem below it mentions certificates. The diagram also locates the trust boundary: the leftmost column is the whole of what is assumed, and it divides into the one assumption about an adversarial network (N2, as `converges`) and clauses of the algorithm (P1–P11). **Three separations, different in kind.** The development draws three lines; only the first is the one usually meant by "confining the time model". *Time from graph structure* — the interface of §6.7. Everything above it is finite combinatorics over a DAG; everything that mentions an instant lies below. Extraction confirms the division: every labelled result that mentions a clock lies below the interface — the view-pace family (V1–V17), catch-up (CU1–CU4) and the reactive schedule (RS1–RS5) — and none above it does. Every theorem of `Mysticeti/ViewPace.lean` that concludes anything used above the line does so by first establishing `SynchronisedOn` or `PopulatedOn`. This is the separation the report's title claims, and the diagram shows it as a column that every liveness result passes through. *Network from protocol* — the interface is the pair `converges` / `references` of §6.9. `converges` mentions no blocks, rounds or references — it is one sentence about views — and the step from views to references is the protocol's clause P7. That the fused covers-shape is derivable from the pair is V1 (`ViewPace.covers_of_converges`), and it is where §4.3's claim that the network's whole contribution is one sentence about views is discharged. *Assumed from derived* — production. The structure carries no block function: `built_of_le_top` at `n = 0` is genesis, which a validator satisfies alone, and every round above is a consequence of the progress rule and convergence (`ViewPace.reached`, §6.9). Production is assumed at one round, and that round needs no network. ### 6.9 The route: view convergence over a partial schedule *(module `LeanDag/Mysticeti/ViewPace.lean`; witnesses in `LeanDagTest/Mysticeti/ViewPace.lean`)* The network assumption is **view convergence** — after GST, whatever a correct validator holds reaches every correct validator within `delay` — and it is stated over a build schedule that can be *stuck*: ```lean structure PaceCore (U) (T : Finset Validator) (N : ℕ) where top : Validator → ℕ built : Validator → ℕ → ℕ timeout : ℕ → ℕ gst delay : ℕ rounds_le : ∀ b ∈ U.ids, (U.block b).round ≤ N built_of_le_top : ∀ v ∈ T, ∀ n ≤ top v, ∃ b ∈ U.ids, (U.block b).creator = v ∧ (U.block b).round = n le_top_of_built : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round ≤ top v holds : Validator → ℕ → Finset BlockId holds_sub : ∀ v, ∀ t, holds v t ⊆ U.ids holds_closed : ∀ v ∈ T, ∀ t, ∀ b ∈ holds v t, ∀ j ∈ (U.block b).refs, j ∈ holds v t refs_held : ∀ v ∈ T, ∀ n, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round = n + 1 → (U.block b).refs ⊆ holds v (built v (n + 1)) holds_own : … holds_mono : ∀ v, ∀ s t, s ≤ t → holds v s ⊆ holds v t converges : ∀ v ∈ T, ∀ w ∈ T, ∀ t, gst ≤ t → holds w t ⊆ holds v (t + delay) advances : ∀ v ∈ T, ∀ n < N, ∀ t, quorumCard Validator ≤ (authorsIn U (holds v t) n).card → n < top v proc : ℕ catchup : ∀ v ∈ T, ∀ n ≤ N, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = n → ∀ t, gst ≤ t → b ∈ holds v t → n ≤ top v ∧ built v n ≤ t + proc structure ViewPace (U) (T : Finset Validator) (N : ℕ) extends PaceCore U T N where waits : ∀ v ∈ T, ∀ n < top v, built v n + timeout n ≤ built v (n + 1) references : ∀ v ∈ T, ∀ n < N, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 1 → ∀ a ∈ holds v (built v (n + 1)), (U.block a).round = n → a ∈ (U.block c).refs ``` (`timeout_pos`, `latest` and `built_le_latest` are elided; `latest` is an upper bound production reads for a common time, and nothing else consults it.) The split is by role, and it is the trunk both pacing disciplines share. `PaceCore` carries what production and drift consume — the partial schedule, the views, the network's clause, and the pacemaker's two rules: `advances` (P8), forcing a validator forward on a quorum, and `catchup` (P11), forcing it forward on a single sighted block within the processing bound `proc` — and nothing about *when* a validator chooses to build within a round; `reached`, `populatedOn` and the drift collapse are proved on it, once. `ViewPace` is the full-timeout discipline: the waiting floor P9 and global referencing. The reactive schedule (§11) extends the same trunk with a deadline in place of the floor, and inherits production and drift rather than assuming them. `top v` is the highest round `v` reached; `built v n` is read only at `n ≤ top v`, and rounds above were never built. The two `top` clauses say `v`'s blocks are exactly rounds `0` through `top v` — at `n = 0` the first is genesis, which needs no network. The schedule clause `waits` (P9) is guarded by `n < top v`, since a round never reached has no build time worth constraining. **Stuck is expressible, and that is why the structure exists.** A total build schedule assigns a time to every round whether or not the validator could build there, so it cannot distinguish *stuck at round `n`* from *built round `n+1` with no quorum*, and a coverage argument over it requires a side condition on the round straddling GST. The pacemaker's own rule replaces it: `advances` says a validator holding a quorum of distinct round-`n` authors — **at any time whatever** — gets past round `n`. Its trigger is `authorsIn U (holds v t) n`, the distinct authors of the round-`n` blocks among `v`'s holdings — an image, so an equivocator's duplicates collapse and the count is of validators, not blocks. Over the whole universe the same measure is `authorsAt` (`authorsAt_eq_authorsIn`), so L0's density and the progress rule's trigger are one quantity read off two sets. Conditional on the quorum, so it asserts no production; naming no time, so there is no deadline to miss and nothing for a schedule hypothesis to protect. A validator racing ahead of the network before GST is not excluded by assumption; it is not expressible, since one that never held a quorum at round `n` never reached round `n+1`. `ugrowStuckPace_stuck` exhibits the state the total schedule could not: a `ViewPace` satisfied by an execution that has genuinely halted — horizon `5`, `top = 0`, round `1` unpopulated — with the quorum bound the only thing keeping it out of the production theorem, its `T` being a single validator. `advances` and `built_of_le_top` together are P8, and this incarnation answers [QXS26] in their own terms (§4.1): reaching a round *is* having built there, so Starfish's pacemaker rule A2 holds as a structural invariant and the round-jumping counterexample is inexpressible. **The separation, V1.** The fused covers-shape — *a `T`-block built after GST and early enough is referenced*, a network guarantee and a protocol clause in one sentence — is derivable from `converges` and `references` alone: ```lean theorem covers_of_converges {n : ℕ} (hn : n < N) (hc : c ∈ U.ids) (hcT : (U.block c).creator ∈ T) (hcr : (U.block c).round = n + 1) (ha : a ∈ U.ids) (haT : (U.block a).creator ∈ T) (har : (U.block a).round = n) (hgst : vp.gst ≤ vp.built ((U.block a).creator) n) (hearly : vp.built ((U.block a).creator) n + vp.delay ≤ vp.built ((U.block c).creator) (n + 1)) : a ∈ (U.block c).refs ``` The block is in its author's hands when built (`holds_own`), reaches the builder within `delay` (`converges`), is still there when the builder acts (`holds_mono`), and is therefore referenced (`references`). No counting, no drift, no waiting rule — those enter only to discharge the arrival-before-build hypothesis, which is the race the drift argument wins. Because `references` and `holds_own` are stated over any block a validator authored, there is nothing to identify by non-equivocation, and `T ⊆ Correct` is not consumed anywhere in the coverage half. **Coverage — drift-free.** ```lean theorem synchronisedOn_of_converges {R : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) (hbackoff : ∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) : SynchronisedOn U T R ``` read as: *after GST, once the timeout clears `2Δ + proc`*. Only `gst ≤ R` is the network's; no drift bound and no start spread appear, because drift is **derived** from catch-up — ```lean theorem driftOn_of_catchup (vp : ViewPace U T N) {R : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) : DriftOn vp.built T R (vp.delay + vp.proc) N ``` — the collapsed spread `Δ + proc`, from any `R` past GST, with no hypothesis about the start (§6.11 gives the collapse itself, on the trunk). The race is then run against that constant by the coverage engine `synchronisedOn_of_driftOn`, which takes an *arbitrary* drift bound `D` and the backoff `D + delay ≤ timeout`. The engine consumes neither production, nor the quorum bound, nor `T ⊆ Correct`; the headline consumes the quorum, which is what feeds the collapse — and the engine stays public precisely for reliable sets below the quorum, where drift must be supplied from outside (V12 below runs on a two-member `T`). The round guards that the partial schedule imposes are discharged by `reached` (below) rather than assumed. "After GST" alone is **not** the hypothesis, and the factoring makes the reason precise. `converges` is partial synchrony in its usual two-part shape: qualitatively, holdings converge at all (`ConvergesEventually`); quantitatively, from `gst` on the lag is uniformly at most `delay` (`ConvergesWithin`), and `convergesWithin_iff_bounded` (V4) is the factoring. The bound is not decoration — a lag that merely exists cannot be compared with a timeout, and `D + delay ≤ timeout n` is where the comparison happens. **Production — on the trunk, once.** ```lean theorem PaceCore.reached (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, ∀ v ∈ T, n ≤ pc.top v theorem PaceCore.populatedOn (pc : PaceCore U T N) (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, PopulatedOn U T n ``` Stated on `PaceCore`, so every pacing discipline inherits both — the full-timeout one below, the reactive one in §11. From genesis, convergence and the progress rule, and from nothing else: no drift, no backoff, not even `timeout`, since with no deadline there is nothing to beat. The induction: each `w ∈ T` reached round `n`, so holds its own block there; `holds_mono` carries it to a common time past `gst`; `converges` puts a quorum of distinct round-`n` authors in `v`'s hands; `advances` fires. `reached` is also the answer to what a wait bound means on a schedule that can be stuck: with `T` a quorum and the progress rule, no `T`-validator is stuck below the horizon — as a theorem rather than as the shape of a field. **The spine.** ```lean theorem commits_recur_via_pace (hT : T ⊆ Correct) (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ ∀ U N (vp : ViewPace U T N), vp.gst ≤ R → (∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) → S.slotRound k' + 2 ≤ N → ∃ L, IsLeaderBlock U k' L ∧ Decided U (View.full U) k' (some L) ``` The chain view convergence + P7 + P8 + P9 + P11 ⟹ production and coverage ⟹ commits is the proof: production and coverage are derived and handed to L6 (`commits_recur_on`), whose quantifier order is preserved — the slot is fixed by the schedule and the bound alone, before any execution is named. `T` need not be all of `Correct`. What is required of the world is that **some `n − f` correct validators converge in view** — the rest may be starved, partitioned or silent for the whole run. That is weaker than partial synchrony in its usual form, which stabilises every correct-to-correct link, and the gap is real (V12 below). It is a genuine weakening only below full fault load: `reliable_eq_correct` shows that at `|byzantine| = f` the conditions `T ⊆ Correct` and `n − f ≤ T.card` force `T = Correct`, so the `T := Correct` instantiation is not a restriction but the one always available. **The limits are witnessed, not merely stated.** Three models — each a `ViewPace` over the `Uomit` construction, certifying every clause field by field — show that weakening a hypothesis does not merely block the proof but makes the conclusion false: * **V10** (`bound_is_necessary_pace`): holdings converge from time `0` over all of `Correct` (`ugapPace_convergesEventually`), every clause holds, and coverage fails at every round below the horizon. The bound in `converges` is what carries coverage; the qualitative half alone carries none of it. * **V11** (`gst_is_forced_pace`): the same instance satisfies the drift and backoff hypotheses of the coverage engine at `R = 0` outright — spread `3`, `3 + 1 ≤ 4` — and the headline's quorum bound too; coverage at `0` is false, and only `gst ≤ 0` fails, so the GST side condition is the working hypothesis, forced rather than chosen. * **V12** (`reliable_set_is_forced_pace`): with `T = {1, 2}` a proper subset of `Correct` over a DAG withholding validator `3`'s blocks from everyone's references, coverage over `T` is *derived* — through a route that asks neither `T ⊆ Correct` nor a quorum for that half — and coverage over `Correct` is *false*, the second statement being about the DAG alone. The `T`-relativisation is a property of the setting, and the weakening above has content. ### 6.10 Quantitative results The structure-free rated hypotheses live in `Mysticeti/Quantitative.lean`; the results they pin are stated over the structure in `Mysticeti/ViewPace.lean`. Each strengthens a result above under a strengthened clause (§4.5); a reader declining those clauses retains §6.1–§6.9 intact. **The weak hypotheses admit no bound.** The recurrence results conclude with a bare existential, and this is not a deficiency of the proofs: each governing hypothesis has the same form, and under such hypotheses no bound exists. A backoff clearing every threshold *eventually* admits `timeout n = ⌊log₂(n+1)⌋`, which needs `n ≥ 2^(D+delay) − 1` to clear the threshold; `FairScheduleOn T` admits a schedule naming `T`-leaders at slots `0, 10, 1000, …`. A bound therefore requires a *rated* hypothesis rather than a better proof. **The round of coverage.** ```lean def Rated (timeout : ℕ → ℕ) : Prop := ∀ n, n ≤ timeout n theorem synchronisedOn_of_rate (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hrate : Rated vp.timeout) : SynchronisedOn U T (max (2 * vp.delay + vp.proc) vp.gst) ``` Each term of the maximum is interpretable: the threshold the timeout must clear, and GST — no base round and no measured spread, since the rated timeout clears the constant threshold from the round the threshold itself names. A hypothesis is removed as well as added — `backoff_ge_of_rate` requires no monotonicity, the bound at `n` deriving from `n` itself and so being incapable of lapsing — and `unbounded_of_rated` confirms that `Rated` strengthens the hypothesis it replaces. **The committing slot, and its round.** ```lean def FairWithin (T : Finset Validator) (w : ℕ) : Prop := ∀ k, ∃ k', k ≤ k' ∧ k' < k + w ∧ S.leader k' ∈ T theorem commits_recur_within … (fair : FairWithin T w) (R k : ℕ) : ∃ k', max k R ≤ k' ∧ k' < max k R + w ∧ R ≤ S.slotRound k' ∧ … theorem commits_recur_by_round … (hs : BoundedSpacing s) (R k : ℕ) : ∃ k', k ≤ k' ∧ S.slotRound k' ≤ S.slotRound (max k R) + s * w ∧ … S.slotRound (max k R) + s * w + 2 ≤ N → … ``` `FairWithin.fairScheduleOn` records that the rated schedule is a fair one, so that L6 applies unchanged. `BoundedSpacing` has no counterpart among the weak hypotheses. Eligibility bounds an anchor's round from *below*, which is what safety requires, the anchor of M4 being obliged to clear the decision round. A latency claim requires the opposite bound, and the class provides none, no safety result having occasion to ask for one. Supplying the mirror image is what converts a bound on the slot index into a bound on its round. **The wait bound.** ```lean theorem directCommit_of_wait (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) (hwait : ∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` > Beyond GST, if every reliable validator waits at least `2Δ + proc` > before building, every correct leader is committed. Thus `Delay(Δ) = 2Δ + proc`, a constant of the network and the implementation, with nothing anywhere stating how the validators started; `decided_of_wait` gives the corresponding statement about `Decided`. Production is derived, so nothing asserts blocks above round `0`, and `T ⊆ Correct` is not consumed. The timeout is here a *constant*: no backoff, no rate condition, no monotonicity and no existential round appear. This locates the role of the backoff precisely. A backoff is required only because Δ is unknown; a validator in possession of the delivery bound requires no adaptation whatever. The shape of the constant merits comment. `Δ + proc` is the collapsed spread — what catch-up leaves between any two validators' entries into a post-GST round (§6.11) — and the further `Δ` is the delivery of the block to be referenced; the threshold is their sum, and it degrades linearly in the processing bound and in nothing else. At instantaneous entry it admits an external check: `directCommit_of_wait_two_delay` specialises `proc = 0` to a threshold of `2Δ` — and Starfish [PMV25], designing a pacemaker for this family rather than deriving a threshold, fixes its block-creation timeout at `δ_TO = 2Δ`, Mysticeti's own creation rule using a `2Δ` timer likewise. The constant obtained here as a derived requirement is the one independently arrived at as a design choice, met by the deployed timers exactly when processing is negligible against delivery. ### 6.11 Catch-up: the drift collapse *(clause and collapse in `LeanDag/Mysticeti/ViewPace.lean`; witnesses in `LeanDagTest/Reactive/Catchup.lean`, `LeanDagTest/Reactive/Collapse.lean`)* The schedule alone does not contract drift. Under `waits` every clock advances by the same timeout, so the spread between validators is preserved, not compressed — the intuition that it shrinks as the protocol reaches synchrony is false of the timeout discipline by itself, since the network converging moves nobody's clock. What contracts it is the trunk's second pacemaker rule, `catchup` (P11, displayed in §6.9): *seeing evidence of a round is entering it*, stated off any `T`-authored block and saying one thing more than a total schedule could — seeing a round means **reaching** it, which is exactly what a real pacemaker's catch-up does. The collapse is immediate and total, proved on the trunk so both pacing disciplines inherit it: **CU2.** ```lean theorem drift_collapse {n : ℕ} (hn : n ≤ N) (htop : ∀ u ∈ T, n ≤ pc.top u) (hg : ∀ u ∈ T, pc.gst ≤ pc.built u n) : ∀ v ∈ T, ∀ w ∈ T, pc.built v n ≤ pc.built w n + (pc.delay + pc.proc) ``` No hypothesis mentions the previous spread. The laggard cannot stay behind: the earliest builder's block reaches it within `Δ`, and catch-up converts the sighting into entry within `proc`. The contraction happens in one round, not gradually, and `driftOn_of_catchup` (L11) puts it in the form the coverage engine consumes — `DriftOn` at the constant `Δ + proc`, from any `R` past GST, the quorum bound discharging the round guards through `reached`. This is what makes every headline of §6.9–§6.10 drift-free: the spread the race is run against is a derived constant, not an assumed quantity (CU3, the deployment-free threshold `2Δ + proc`, is thereby the main line's own wait bound, L9). **The bound is exact from both sides.** `ugrowSkew_spread_constant` (CU1) shows the running witness carrying a spread of exactly `Δ + proc = 2` at every round, for ever: the collapse contracts any larger spread down to the bound, and nothing contracts past it. And `ugrowLag` (CU4) shows the contraction itself: a round-`0` spread of `10` — admissible because GST has not yet arrived and no evidence has crossed — collapsing to exactly `Δ + proc = 3` at round `1`, where the laggard's `waits` floor and its catch-up deadline meet with no slack (`ugrowLag_collapse`). The same run commits a slot at timeout `5 = 2Δ + proc` (`ugrowLag_decided`), the spread of `10` appearing in no hypothesis. This also settles that `catchup` and `waits` are jointly satisfiable *from* a large spread, not only near synchrony: the floor and the deadline are in genuine tension — one holds a validator back, the other pulls it forward — and the witness threads both exactly. **The rush bound: the clause cannot be exploited (CU5).** The pacemaker rules are stated over `T`, but `T` is an analysis-side object no validator can test membership of, so a deployment runs the author-blind strengthening: catch up on *any* valid block sighted. The worry that raises is being rushed — a Byzantine validator, by not waiting, dragging correct validators past their own timeouts with manufactured evidence of a far-future round. It cannot: a block of round `n + 1` references a quorum of distinct round-`n` authors (P3), a quorum meets any quorum-sized `T` (`exists_reliable_parent`, with `n − 2f ≥ f + 1` members to spare), and by `waits` that parent's author has paid the full timeout bill for every round below: ```lean theorem exists_honest_floor (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) {b : BlockId} (hb : b ∈ U.ids) {n : ℕ} (hbr : (U.block b).round = n + 1) : ∃ u ∈ T, n ≤ vp.top u ∧ vp.built u 0 + (∑ i ∈ Finset.range n, vp.timeout i) ≤ vp.built u n ``` Evidence of a round cannot exist before the honest schedule permits the round: catch-up only ever pulls a validator to where a reliable peer already is, and the adversary's whole freedom is the single layer it may build the instant a quorum forms beneath it — `PaceCore.round_le_top_succ`: no valid block's round exceeds some reliable `top` by more than one. On the running witness the floor is met with equality (§24). The clause itself is asserted only from `gst` (§4.1), so what it demands coincides with what the clamped author-blind rule delivers: pre-GST it demands nothing — matching a network that may deliver nothing and a floor that may bind — and post-GST the collapse arithmetic puts every catch-up deadline at or past the holder's own floor, `ugrowLag` exhibiting the two meeting with no slack. One caveat is recorded honestly: the model does not axiomatise that a block cannot be *held* before it exists — `holds` is constrained only by `holds_own`, monotonicity and `converges` — so the statement mechanised is about the block's existence certifying the floor, with arrival-time realism carried by the satisfiability witnesses. **What catch-up does not supply is coverage.** A validator entering a round on evidence has not waited for the round below to assemble, so its own block may reference little; the coverage argument still runs through `waits`, from the collapsed spread onward. Catch-up supplies the *base* of the drift argument, not the argument — which is also why it composes with the reactive schedule of §11 rather than replacing it: the two clauses cut different waits, entry into a round and exit from it. ### 6.12 The view a validator holds *(module `LeanDag/Mysticeti/PaceDelivery.lean`; `viewAt` and the local commit in `Mysticeti/ViewPace.lean`)* Two objects in this development mean *what a validator has*, and this section connects them. The commit rules are view-relative — `Decided U V k v` over a refs-closed `View` — while the pacing line reasons about `holds : Validator → ℕ → Finset BlockId`, a time-indexed set tied to the universe by nothing at all. The storage arcs add a third, `Delivery.held`, indexed by round. This section closes both gaps, and each needs one clause. **The store clauses.** Two clauses tie `holds` to the universe, and they are what the section rests on. `holds_sub` says a validator holds only blocks that exist; `holds_closed` says holdings are causally closed — P4 as a *store* property. The second is not bookkeeping. A block whose history is missing cannot be validated, since P3 and P3′ read the referenced blocks, and cannot be built upon; without the clause the model would oblige a validator to advance on evidence no implementation could act on, and `viewAt` below would be the closure of a validator's fragments rather than its view. Adding it *weakens* what is assumed of an implementation. Both witnesses satisfy it on data. `ugapPace` (V10, V11) delivers every non-starved block one tick after its build, which leaves validator `2` starved and `converges` binding only from its late GST. `ugrowLag` (CU4) has its leaders build round `1` at time `14`, when the round-`0` blocks they reference have arrived, and the collapse is met with equality — spread `10` at round `0`, exactly `Δ + proc = 3` above. **Liveness is local (V18).** With the store clauses a validator's holdings generate a view: `viewAt v t` is the causal closure of `holds v t`, free by the `View.ofAccepted` argument, and under `holds_closed` that closure is a no-op — `viewAt_ids` proves `(viewAt v t).ids = holds v t` for reliable `v`, so the view *is* the holdings and the statement below is about the blocks the validator actually has. With `holds_roundBlocks` (past GST every reliable validator holds every `T`-authored round-`n` block by `latest n + delay`), the counting of L4 runs *inside* that view: ```lean theorem decided_local (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) (hbackoff : ∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ ∀ v ∈ T, Decided U (vp.viewAt v (vp.latest (S.slotRound k + 2) + vp.delay)) k (some L) ``` The hypotheses are the main line's exactly — GST and the constant backoff, and nothing further. This strengthens every liveness statement above: those conclude `Decided U (View.full U) k (some L)`, the verdict of a view no deployed validator ever holds, and `decided_of_local` recovers that form through L3. The argument is proved on the trunk (`PaceCore.decided_local_of_certifiesAt`), so the reactive discipline inherits it with its own certificate supplier and no coverage anywhere (`ReactiveM.decided_local`). The spine follows in the same form. `commits_recur_local` keeps the quantifier order of §6.9's `commits_recur_via_pace` — the slot is fixed by the schedule and the round bound before any execution is named — and concludes locally at that slot, the slot-selection skeleton being pure schedule reasoning independent of what is proved there. One hypothesis drops: `T ⊆ Correct` is not needed. The global spine threads it through `commits_recur_on`, whose production comes from L1 over `Correct`; here production is the pacing structure's own, over `T` directly, so the hypothesis has nothing left to do. The local spine is weaker in hypotheses and stronger in conclusion. **The delivery layer is induced (V19).** `Delivery.held` is the same notion read at the build instant, and defining it so discharges every field of the structure: ```lean def heldOf (v : Validator) (n : ℕ) : Finset BlockId := if n < N then (vp.holds v (vp.built v (n + 1))).filter (fun b => (U.block b).round = n) else ∅ def acceptedOf (v : Validator) (n : ℕ) : Finset BlockId := if v ∈ (Correct : Finset Validator) then vp.heldOf v n else ∅ def toDelivery : Delivery U ``` — for `vp : ViewPace U (Correct : Finset Validator) N`, which `reliable_eq_correct` shows is the instantiation always available at full fault load. One field is worth naming. `accepted_inj` — the acceptance rule, at most one block per author — is a field of `Delivery` (§6.2), and here it is a consequence: P7 obliges a builder to reference every round-`n` block it holds, and P2 forbids citing one author twice, so a correct validator cannot be holding two blocks by one author when it builds (`heldOf_inj`). What the DoS arc takes as a field is here a consequence of the reference discipline. **The reference discipline, both ways (V20).** The storage arcs take one hypothesis beyond `Delivery`: `RefsAccepted`, that a correct validator references *only* what it accepted. P7 does not give it — `references` obliges a builder to include what it holds and says nothing about what else its block may cite — so the trunk carries the converse directly, as `refs_held` (S3). Every witness in the development satisfies `refs_held`, and the collapse witness `ugrowLag` (CU4) meets it with the collapse still exact. A clause every model satisfies, and whose absence would let models be physically impossible, belongs on the trunk. The composition capstone is then unconditional: ```lean theorem dos_resistance_of_pace {κ : ℕ} (hu : UniformBudget vp.toDelivery κ) : (∀ r ≤ N, Populated U r) ∧ ∀ v ∈ (Correct : Finset Validator), ∀ n, (viewUpto vp.toDelivery v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * κ))) ``` One structure yields both halves of §8's capstone, and the **only** thing assumed beyond the pacing structure is the acceptance budget — the mechanism a validator runs. Production comes from genesis and the pacemaker's rules; the reference discipline, in both directions, from P7 and S3. Nor is the correspondence an equivalence: a `Delivery` has no instants, so it cannot determine a schedule, and it remains the right object for arcs that never mention time. --- ## 7. Chain quality: coverage and inclusion *(companion document: `chain-quality.md`; modules `LeanDag/Properties/Arcs/Quality.lean` and `LeanDag/Quality/`)* Every protocol of this family claims that leader rotation prevents censorship; this section proves what the ledger actually contains. A commit does not append one block — it flushes the entire causal cone of the committed leader (§3.6) — and two families of theorems, split exactly along the trust boundary, say whose blocks the flush carries: **coverage**, an aggregate guarantee that holds with no synchrony assumption anywhere; and **inclusion**, an individual guarantee that costs certification at a slot the author leads, with a witness model showing that aggregate coverage alone does not supply it. The metric is **distinct correct authors per round**, not a block-count fraction: an equivocator can inflate a cone with any number of blocks per round, so the conventional fraction is adversary-deflatable, while the author count is what the quorum structure bounds. ```lean def coveredAt (R : DagRule Validator BlockId Payload) (rel : Reliability Validator) (U : R.Universe) (b : BlockId) (δ : ℕ) : Finset Validator := rel.correct.filter fun v => ∃ i ∈ historyFrom (R.block U) b, (R.block U i).creator = v ∧ (R.block U i).round = δ ``` **The arc is stated for any rule, and the core's is an instance.** Its one rule-dependent step is `Properties.CommitsCandidate`, and density's dependence on block validity is `Properties.Quorate`, the quorum clause at the carrier. `rel` is the fault model as a counting parameter — a reliable set, a slack bounding everything outside it, and that slack being a minority — because six fault classes are in play across the development and density counts against whichever one a rule carries. **Coverage (CQ1–CQ3), asynchronous.** Density (D25, §8) forces every layer of every valid cone to carry all but at most `f` correct authors, and a committed leader's block is in particular valid — any commit route, any view: ```lean theorem card_coveredAt_ge_of_decided (hq : Quorate R rel) (hcc : CommitsCandidate R) (h : R.Decided S V k (some L)) (hδ : δ < (R.block U L).round) : rel.correct.card - rel.slack ≤ (coveredAt R rel U L δ).card theorem card_correct_le_two_mul_coveredAt_of_decided (hq : Quorate R rel) (hcc : CommitsCandidate R) (hhalf : 2 * rel.slack ≤ rel.correct.card) (h : R.Decided S V k (some L)) (hδ : δ < (R.block U L).round) : rel.correct.card ≤ 2 * (coveredAt R rel U L δ).card ``` — **every commit carries, at every round below it, blocks from at least half of the correct validators** (`|Correct| ≥ 2f+1` makes `|Correct| − f` at least half), and the cumulative ledger form (`ledger_coverage`) exhibits, for any verdict assignment covering a committed slot, a set of at least `|Correct| − f` correct validators each with a round-`δ` block in `ledgerSet`. No synchrony, no delivery model, no populated rounds appear in any hypothesis. **The boundary, witnessed.** Aggregate coverage is *not* individual inclusion. The witness model `Ucens` (CQ8) (§24) runs six rounds in which three validators reference only each other and commit with the full certificate pattern, while a fourth — correct, building validly, never referenced — is the missing author of **every** layer of **every** flush: `missingAt = {3}` throughout, so CQ1's `≤ f` is exactly tight, and `Synchronised` fails at every round while the commit stands. The same correct validator can be censored for ever under asynchrony; no validity, delivery or liveness clause objects. **Inclusion (CQ5–CQ7), from self-reference.** A correct validator's blocks form a chain — each references the one before (P3′), one per round (non-equivocation) — so any later block of the author reaches every earlier one, and the author's next committed leader block is such a block (`mem_history_of_decided_commit`). Fairness to each validator supplies that commit: ```lean def IncludesAt (BlockId : Type) [DecidableEq BlockId] (Payload : Type) (T : Finset Validator) (m k : ℕ) : Prop := ∀ (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U), MysticetiProperties.certLive S V T k (k + 1) → ∃ L, Decided U V k (some L) ∧ ∀ b ∈ U.ids, (U.block b).creator = S.leader k → (U.block b).round = m → b ∈ history U L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k = some L → k < n → b ∈ ledgerSet U g n theorem committed_of_correct_block (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hlc : LeaderCommits R Live) (S : Slots Validator) (hT : T ⊆ rel.correct) (fair : ∀ v ∈ T, ∀ n, ∃ k, n ≤ k ∧ S.leader k = v) (m : ℕ) (hv : v ∈ T) : ∃ k', m ≤ S.slotRound k' ∧ S.leader k' = v ∧ … ``` — for every round `m` and every `v ∈ T` the schedule fixes, *before the universe is quantified*, a slot `v` leads whose commit carries every round-`m` block by `v`. No synchrony appears: the precondition is the certification one (`certLive`), which a timed execution reaches through `certLive_of_coreLive` and a reactive one from its wait clauses. The quantitative forms pin the slot to a window: under `FairToEachWithin T w` the committing slot lies within `w` slots of the first slot at or above round `m` (`committed_of_correct_block_within`), and under `BoundedSpacing s` its round within `s·w` rounds (`committed_of_correct_block_by_round`) — *a correct block is committed within a schedule-window of its creation*. The capstone `chain_quality` packages both halves under enforceable or standard conditions only. The backbone consumes no coverage, so both pacing disciplines reach it: the full-timeout one through `certLive_of_coreLive`, the reactive one from its wait clauses (`ReactiveM.committed_of_correct_block`, §11.5, RS5). A block-count purity variant was considered and rejected: under `DoSValid` alone the per-author block count carries the exponential constant of §8.3, and under the budget the cone-level Byzantine count is a whole-store bound. Neither yields an informative ratio, and the author-coverage metric is the appropriate one. --- ## 8. Denial of service: equivocation, growth, and the novelty budget *(companion document: `dos-equivocation-and-growth.md`; modules `LeanDag/DoS/`)* Safety needs no protection from equivocation: every result of §5 holds with no anti-equivocation condition anywhere in its hypotheses, and the independence is itself recorded — a witness model satisfies safety while violating every storage condition of this section (`LeanDagTest/DoS/SafetyUnderDoS.lean`). *Storage* is another matter. An uncertified DAG admits Byzantine blocks into correct views by design, an equivocator may produce arbitrarily many blocks per round, and a correct validator that retains the cones of what it accepts can be made to retain material injected by an adversary. This section bounds that growth in two ways: first under a *reference-validity* condition (exposure), whose bound is shown essentially optimal yet exponential in `f`; then under a *rate-limiting* condition (the novelty budget), which is enforceable, author-blind, and yields the linear result `dos_resistance`. The two compose: the budget limits the rate at which an equivocator can inject material, and exposure terminates it. ### 8.1 The store, and what growth means A validator's store is the accumulation of the cones of everything it accepted (§6.2 introduced acceptance): ```lean def viewUpto (D : Delivery U) (v : Validator) : ℕ → Finset BlockId | 0 => (D.accepted v 0).biUnion (history U) | n + 1 => viewUpto D v n ∪ (D.accepted v (n + 1)).biUnion (history U) ``` `viewUpto D v n` is everything `v` has retained by round `n`: accepting a block means holding its entire causal history — that is what downward closure of views (§2.3) demands. Growth questions are questions about `(viewUpto D v n).card`, and since correct production alone contributes `|Correct|` blocks per round, *linear in `n`* is the best attainable; the question is the constant, and whether the Byzantine share can exceed it. The *novelty* of an arriving block is what its cone adds over the store: ```lean def novelty (U) (V : Finset BlockId) (b : BlockId) : Finset BlockId := history U b \ V ``` Novelty is antitone in the store — the more a validator already holds, the smaller the novelty of any arriving block — the monotonicity on which every argument below depends. ### 8.2 Exposure, and the DoS-validity condition An author is *exposed* in a cone that holds two of its blocks from one round; the DoS condition forbids building on the exposed: ```lean def EquivPair (U) (X : Validator) (i j : BlockId) : Prop := i ≠ j ∧ (U.block i).creator = X ∧ (U.block j).creator = X ∧ (U.block i).round = (U.block j).round def ExposedIn (U) (b : BlockId) (X : Validator) : Prop := ∃ i ∈ history U b, ∃ j ∈ history U b, EquivPair U X i j def DoSValid (U) : Prop := ∀ b ∈ U.ids, ∀ i ∈ (U.block b).refs, ¬ ExposedIn U b (U.block i).creator ``` Exposure is objective — a property of the cone, checkable by any holder — and *monotone up the DAG*: a cone containing an exposing cone is exposing. Only the guilty are ever exposed (a correct author has one block per round, by non-equivocation), and at most `f` authors are exposed in any one cone: ```lean theorem card_exposedTo_le (hb : b ∈ U.ids) : (exposedTo U b).card ≤ F.f ``` Exclusion costs quorum *margin*, not liveness. At the extreme — every Byzantine validator caught — the condition pins references exactly: ```lean theorem creators_refs_eq_correct (hdos : DoSValid U) (hb : b ∈ U.ids) (hround : 0 < (U.block b).round) (hk : F.f ≤ (exposedTo U b).card) : creatorsOf U.block (U.block b).refs = (Correct : Finset Validator) ``` — the references of every later block are precisely the correct validators, and the commit chain still operates over them: the witness model `Uexcl` carries a direct commit whose three rounds all lie after the exclusion of its equivocator (§24). Nor does exclusion depend on favourable circumstances: *density* establishes that a cone can be selectively blind to at most `f` correct authors per round, even below Byzantine blocks, because the quorum clause forces every layer of every valid cone to carry `n − f` distinct authors: ```lean theorem card_missingAt_le (hb : b ∈ U.ids) (hδ : δ < (U.block b).round) : (missingAt U b δ).card ≤ F.f ``` where `missingAt U b δ` is the set of correct authors with no round-`δ` block in `H(b)`. ### 8.3 Growth under the condition alone: the exponential wall How large can one cone be under `DoSValid` alone? Byzantine authors are excluded only *after* both halves of an equivocation meet in one cone; until then, distinct branches may carry distinct halves, and `e` cooperating equivocators can chain reveals so that each unexposed author doubles the mass a branch may adopt. The general upper bound is proved through *pedigrees* — for each exposed author, the chain of adoption events by which its blocks entered the cone — and is linear in the round with a constant exponential in `f`: ```lean theorem card_history_le' (hdos : DoSValid U) (hb : b ∈ U.ids) : (history U b).card ≤ (Fintype.card Validator + (Fintype.card Validator - 1) * F.f ^ F.f) * ((U.block b).round + 1) ``` The exponential constant is not an artefact of the proof: a matching family of witnesses (`Udouble` (C5), §24) realises `2^(e−2)` growth from `e` equivocators, so any bound obtainable from reference-validity conditions alone carries a constant exponential in `f`. This is the assessment of the exposure mechanism as a *storage* defence: it is the right accountability layer — it identifies and permanently retires equivocators at the cost of quorum margin — but no practical storage bound can rest upon it. Rate limiting is required, and is orthogonal to it. ### 8.4 The novelty budget The budget is a rule about acceptance, and deliberately about nothing else. Two formulations are related. The analysis-side form guards on the author being Byzantine; the mechanism-side form is the rule a validator can actually run — **author-blind**, since correct validators cannot in general tell who is Byzantine: ```lean def ByzBudget (D : Delivery U) (κ : ℕ) : Prop := ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ D.accepted v (n + 1), (U.block b).creator ∉ (Correct : Finset Validator) → (novelty U (viewUpto D v n) b).card ≤ κ def UniformBudget (D : Delivery U) (T : ℕ) : Prop := ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ D.accepted v (n + 1), (novelty U (viewUpto D v n) b).card ≤ T def RefsAccepted (D : Delivery U) : Prop := ∀ w ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ U.ids, (U.block b).creator = w → (U.block b).round = n + 1 → (U.block b).refs ⊆ D.accepted w n ``` `RefsAccepted` is the converse of `includes` (§6.2): together they say a correct block's references are *exactly* its author's acceptances — local, observable conduct. The blind and guarded formulations bound each other to within a factor of `f`: * `UniformBudget.byzBudget : UniformBudget D T → ByzBudget D T` — dropping a guard weakens nothing; and conversely * `uniform_of_byzBudget` (B6) — post-`R`, under `ByzBudget κ`, *every* acceptance (correct authors included) adds at most `f·κ + 1`. The converse direction is the substantive one, and the mechanism behind it should be stated. Why would a *correct* author's block have small novelty? Because a correct block's cone is a complete record of everything its author ever accepted — `includes` puts each round's acceptances among the next block's references, and the self-parent chain (P3′) carries every earlier round forward: **B7.** ```lean theorem viewUpto_subset_history (hw : w ∈ Correct) (hb : b ∈ U.ids) (hbc : (U.block b).creator = w) (hbr : (U.block b).round = n + 1) : viewUpto D w n ⊆ history U b ``` One delivered block therefore erases the entire standing gap between two correct stores — the DAG is its own repair channel, and no cone-exchange protocol needs modelling. Quantitatively, the gap between correct stores is a *constant*, not a drift (`card_viewGap_succ_le`): post-`R` it is at most `f·κ`, one round of Byzantine budget, however long the system has run. The same self-parent mechanism yields a purely structural form: if every correct block adds at most `κ'` over its self-parent (`StepNovelty`), then correct cones are linear outright, `|H(b)| ≤ κ'·round(b) + 1` (`card_history_le_of_stepNovelty` (C4)) — a telescope along the self-parent chain, with no delivery model at all. ### 8.5 The principal result, and the composition Under the guarded budget the Byzantine share of a correct store is priced through a global object, the *pool* — the Byzantine-authored blocks any correct validator retains: ```lean def byzPool (D : Delivery U) (n : ℕ) : Finset BlockId := (Correct : Finset Validator).biUnion fun w => (viewUpto D w n).filter fun i => (U.block i).creator ∉ Correct ``` A Byzantine block enters the pool only as a direct budgeted acceptance — if it arrived inside a correct block's cone, `RefsAccepted` places it in that author's *earlier* store — so the pool grows by at most `|Correct|·f·κ` per round (`card_byzPool_le`), and the store bound follows: ```lean theorem card_viewUpto_le (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hv : v ∈ Correct) (n : ℕ) : (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * κ))) ``` — correct production, a Byzantine genesis allowance, and a Byzantine rate. The capstone quotes **enforceable conduct only** — the author-blind budget and the reference rule — and carries production through alongside the storage bound, so both hold of the same execution. Production itself is a hypothesis rather than a premise of the DoS argument: any of the routes of §§6.7–6.9 discharges it (§4.3): ```lean theorem dos_resistance {τ N : ℕ} {P : Finset Validator} (hpop : ∀ r ≤ N, PopulatedOn U P r) (hu : UniformBudget D τ) (hra : RefsAccepted D) : (∀ r ≤ N, PopulatedOn U P r) ∧ ∀ v ∈ (Correct : Finset Validator), ∀ n, (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * τ))) ``` with a post-`R` incremental form (`dos_resistance'`) in which the slope is per-round and the pre-`R` prefix is a single opaque constant. Note what the hypotheses do *not* contain: no `DoSValid`, no exposure, no appeal to identifying the Byzantine — the budget alone suffices for the linear bound. The two conditions then compose. Once every equivocator stands exposed to every correct validator (`AllExposed U m`), `DoSValid` blocks all further Byzantine acceptances, the pool freezes at its round-`m` value, and the store's slope decays to the correct-production rate: ```lean theorem card_viewUpto_le_of_allExposed' (hdos : DoSValid U) (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hexp : AllExposed U m) … (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + (m + 1) * ((Correct : Finset Validator).card * (F.f * κ))) ``` — the budget limits the rate at which an author can inject material; exclusion terminates it. On data, the budget is satisfiable at its exact constant: the witness schedule `Dtwin` satisfies `UniformBudget 3` with its costliest acceptance costing exactly `3`, and `ByzBudget 0` — nothing Byzantine accepted after the genesis round (§24). How should the parameter `T` be set? Any `T ≥ 1` admits every correct block post-`R` (the sandwich's `f·κ + 1` with `κ = 0` would be the correct-only floor); smaller `T` tightens the Byzantine rate and defers — never refuses — correct blocks of high novelty, deferral being a rate limit rather than a refusal, since novelty is antitone in the growing store. --- ## 9. Garbage collection: the horizon *(companion document: `garbage.md`; modules `LeanDag/GC/`)* The DoS results end at linear-forever storage, and linear still diverges: a validator that runs for years retains years of history, and a joining validator must fetch it. The remedy is a **horizon** — a round `G`, chosen per validator, below which nothing is retained, requested, or served — with theorems that commit safety, commit liveness, bounded storage, and bootstrap all survive above it, and that **no consensus on the horizon is ever needed**. Throughout, `G` denotes a horizon round and `Λ` (the *lag*) how far a horizon trails the current round; both are per-validator quantities. One scoping fact first: garbage collection bounds *stores*, not the universe. `U` — every block any correct validator ever held — keeps growing as an analysis object; the theorems live at the level of what a validator retains (`viewUpto`, §8.1) and what a joiner must fetch. ### 9.1 Truncation as rebasing The model-side operator keeps the blocks at rounds `≥ G`, rebases rounds by `−G`, and empties the reference sets of the new base layer — the round-`G` layer becomes the new genesis layer: ```lean def chopBlk (blk : BlockId → Block Validator BlockId Payload) (G : ℕ) (i : BlockId) : Block Validator BlockId Payload := if (blk i).round ≤ G then { blk i with round := (blk i).round - G, refs := ∅ } else { blk i with round := (blk i).round - G } ``` The operator is stated over a bare block assignment: the *data* of a cut is the same for every rule in this report. The cut of a universe, `BlockRecord.chop`, keeps the identifiers at rounds `≥ G` and applies the operator to the block map, and it is built once at the block record (§2.3): closure needs references to sit one round below, the base layer is valid because a reference-free round-zero block is, and the rest is the predicate's `chops`. The core's `chop` is that construction at the core's record, and Nemo (§15), FinWhale (§20) and Hydrozoan (§22) take it at theirs. Every validity clause of §2.2 constrains only rounds `> 0`, and the old genesis special case applies verbatim to the new base — so `chop U G` is a bona-fide `BlockUniverse`, and **every theorem of this report applies to it unchanged**. This is the design in its entirety: the task is never to re-prove the theory above the cut, only relating verdicts *across* the cut and choosing the cut. (In an implementation, block identity is a hash over references, so emptying the base layer's references is not a re-hash of history: it is the checkpoint reinterpreting those identifiers as opaque geneses — a rule about what the sync layer serves.) The DoS condition crosses the cut one way (`dosValid_chop`): cones shrink under truncation, so exposure shrinks, so the per-block condition weakens. The converse fails *by design*, and the failure is the **statute of limitations**: an equivocation whose witnessing pair falls strictly below the cut is forgiven — in `chop U G` its author is no longer exposed — while a pair *at* the cut survives into the base layer. §9.5 prices the forgiveness; the witness file exhibits it on data, an exposure present in the full universe and absent from its truncation (§24). ### 9.2 Verdicts survive the cut Every commit-rule notion of §3 for a slot above the cut is invariant — the rules read a window of rounds that truncation does not touch. The invariance is `LocalTruncate.of_banded` at the core's band, which carries the whole decision relation at once. The full decision relation holds under an *induced schedule*: slots re-indexed from a base slot `d` whose round clears the horizon, with `Slots.chop S G d hd` given by `slotRound' k = slotRound (d + k) − G` (the condition `hd : G ≤ S.slotRound d` is what keeps the re-based rounds above zero, where subtraction is faithful and the schedule's keying survives). Views truncate by the same filter (`View.chop`), and: ```lean theorem decided_chop_iff (hd : G ≤ S.slotRound d) {V : View Validator BlockId Payload U} {k : ℕ} {v : Option BlockId} : Decided U V (d + k) v ↔ Decided (S := S.chop G d hd) (chop U G) (V.chop G) k v ``` — a validator re-running the protocol on the truncation decides slot `k` exactly as it decided slot `d + k` on the full universe, by structural induction through anchors and intermediate skips, in both directions. The **only hypothesis is the base-slot condition**: no synchrony, no liveness, no lag bound, no DoS condition. Cross-cut agreement is deliberately asymmetric: ```lean theorem decided_agree_chop (hd : G ≤ S.slotRound d) (hW : Decided (S := S.chop G d hd) (chop U G) W k w) (hV : Decided U V (d + k) v) : w = v ``` Here `W` is an **arbitrary** view of the truncation — not a truncated full-history view. The asymmetry matters because a joiner's view is never of the form `V.chop`: lifted to `U` it would not be downward closed, its base layer having lost its references. The proof plays the agreement theorem of §5.5 *inside* the truncation against a truncated view, and carries the verdict across the cut through `decided_chop_iff`. So a validator that joined from the truncation and never saw the pruned prefix agrees with every full-history validator, slot for slot. ### 9.3 Windowed storage: constant at a lag Liveness transfers with the offset. Production on the truncation is production upstream with the round index shifted — a round-`r` block of `chop U G` is a round-`(G+r)` block of `U` — so `populated_chop` (G5) takes the `Populated` hypothesis every liveness result consumes and re-establishes it above the cut, consuming no network assumption of its own. Stores correspond exactly — ```lean theorem viewUpto_chopD (m : ℕ) : viewUpto (chopD D G) v m = (viewUpto D v (G + m)).filter fun i => G ≤ (U.block i).round ``` — pruning a store below `G` yields precisely the store of the induced delivery, which is what lets §8's bound `card_viewUpto_le` be read on the truncated universe. Two prerequisites make this legitimate over a *sequence* of cuts. The budget must be measured on the truncated universe — otherwise pruning would make every arriving block's novelty explode with the discarded prefix — and windowed novelty is *antitone under cut-advance* (`novelty_chop_anti`): as the window slides, pruning only decreases novelty, so a block within the budget never falls outside it. The budget conditions themselves descend to the window (`byzBudget_chopD`, `refsAccepted_chopD`). The principal storage result is stated per time, because a validator's life is a sequence of cuts: ```lean theorem card_retained_le {κ Λ t : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hv : v ∈ Correct) (hG : G ≤ t) (hΛ : t ≤ G + Λ) : ((viewUpto D v t).filter fun i => G ≤ (U.block i).round).card ≤ (Correct : Finset Validator).card * (Λ + 1) + ((Correct : Finset Validator).card * F.f + Λ * ((Correct : Finset Validator).card * (F.f * κ))) ``` — **constant in `t`**: at lag `Λ`, the retained store is bounded independently of how long the system has run. The same constant bounds a joiner's entire fetch — the attested base plus the window is a *subset of one correct peer's retained store* (`card_joinIds_le`) — and, plus one, a correct author's serving obligation: everything it can be asked to serve for its block is its own retained store above its own horizon, plus the block itself (`card_serve_le`, via `RefsAccepted` one step down and the self-parent chain the rest of the way). Garbage collection bounds sync cost as well as storage, and the obligation on correct validators is bounded likewise. ### 9.4 Bootstrap: the attested base A validator so far behind that its needs predate every peer's horizon cannot fetch the prefix; it must adopt a genesis layer from others. Requiring `f+1` *identical* checkpoints would be wrong — correct validators' layers share the correct core exactly but differ in Byzantine fringe — so the primitive is an **inexact certificate**, filtered per block: attest the layer, keep what `f+1` distinct authors attest. In this model an attestation *is* a block — its cone is its objective, unforgeable statement of what the layer contains — so the certificate is DAG-internal, decidable, and needs no signatures: ```lean def attesters (U) (t : ℕ) (y : BlockId) : Finset Validator := creatorsOf U.block ((blocksAt U t).filter fun a => y ∈ history U a) def Base (U) (t G : ℕ) : Finset BlockId := (blocksAt U G).filter fun y => F.f + 1 ≤ (attesters U t y).card ``` The base is **sandwiched** between the shared correct layer and the union of correct cones: everything in it has a correct attester, so the adversary cannot smuggle fabrications in (`exists_correct_attester_of_mem_base`); post-`R`, every correct round-`G` block is in every correct attestation, so nothing of the correct layer can be filtered out, by anyone (`correct_mem_base`). Completeness in fact extends to everything *obtainable* — Byzantine-authored included: ```lean theorem accepted_mem_base (hs : Synchronised U R) (hv : v ∈ Correct) (hy : y ∈ viewUpto D v m) (hyr : (U.block y).round = G) (hcar : Populated U (m + 1)) (hpop : Populated U t) (hR : R ≤ m + 1) (hmt : m + 2 ≤ t) : y ∈ Base U t G ``` — every round-`G` block a correct validator accepted into its window by `m` is in the base attested at any `t ≥ m + 2`: acceptance puts the block in a correct store, the store rides into its keeper's next block (`viewUpto_subset_history` (B7), §8.4), and the backbone carries that block into every correct round-`t` cone — a cone *is* an attestation. The lag is tight on data: at `t = m + 1` the witness exhibits an accepted equivocation half missing from the base (§24). Consequently the joiner's assembly — base as genesis layer plus a correct peer's window strictly above the cut — is a bona-fide view of the truncation (`joinView`; downward closure is the content: window references above the cut stay in the window, references *at* the cut are exactly the blocks `accepted_mem_base` puts in the base), and: ```lean theorem bootstrap_agree {R : Properties.DagRule Validator BlockId Payload} (c : R.OnRecord ValidWrt (Correct : Finset Validator) …) (ha : Properties.Agree R) (hb : Properties.Banded R) … (hJ : R.Decided (S.chop G d hd) (c.chop_ofRec U True.intro ▸ c.ofView (joinView …)) k jv) (hV : R.Decided S V (d + k) fv) : jv = fv ``` — any decision reached from base-plus-window equals any full-history validator's. Inexact certificates, exact decisions; bases sampled from different peers need never agree, exactly as horizons need not. ### 9.5 Horizons without consensus, and the lag envelope Each validator sets its own horizon by a local rule — trail the decided frontier by `Λ`, or trail the current round by `Λ` — and three theorems make the heterogeneity safe. Verdicts at different horizons are equal outright (`decided_agree_horizons`, matching slots through their absolute index). A a deeper cut is again a cut: ```lean theorem chop_chop (hG : G₁ ≤ G₂) : chop (chop U G₁) (G₂ - G₁) = chop U G₂ ``` so validators at different horizons sit on one tower of truncations, never in incomparable worlds. And post-`R` possession universalises in **one** round (`viewUpto_subset_viewUpto_succ`): everything any correct validator retains by round `m` is in every correct store by `m + 1` — the keeper's next block carries its whole store, and that block is delivered and accepted — so pruning at depth `≥ 1` below a correct frontier discards nothing any correct peer still lacks (`pruned_subset_peer_store`). The constraints on the lag are best set out theorem by theorem, since *safety constrains it not at all*: | bound | source | what breaks below it | |:---|:---|:---| | — (any `G` is safe) | `decided_chop_iff`, `decided_agree_chop`, `bootstrap_agree` | nothing — commit safety carries no lag hypothesis; its only premise is `G ≤ slotRound d` | | `Λ ≥ 0` vs the *decided* frontier | ledger totality | a slot reads rounds `slotRound k … +2`; cutting above an undecided slot discards its certificates and the slot is undecidable forever — output stalls, safety unharmed | | `Λ ≥ 1` | `viewUpto_subset_viewUpto_succ` | peer no-desync: possession universalises in exactly one round | | `Λ ≥ 2` | `accepted_mem_base` (tight) | base completeness for joiners | | upper bound: none | the §9.3 constants | nothing breaks; storage, join and relay grow linearly in `Λ` | Finally, the statute of limitations is a bounded-rate, priced phenomenon rather than a cliff. Within an epoch the entire exposure economy of §8.2 applies to the truncation verbatim, the truncation being simply another universe. Across a cut, a forgiven author must equivocate *again, inside the new window*, to be debarred again — one reveal per author per epoch — and the re-entry runs under the windowed budget: `Λ·f·κ` of injected material per correct store per epoch, a term the `card_retained_le` constant already carries. Commit safety never depended on any of it: the cross-cut results above carry no exclusion, budget, or exposure hypothesis. A world that forgives every equivocation still commits the same blocks; it merely retains more. --- ## 10. Odontoceti: two-round commitment *(companion document: `odontoceti.md`; modules `LeanDag/Odontoceti/`)* Odontoceti [Van25] commits in **two** communication rounds: a leader block at round `r` is decided by the supports and blames of round `r+1` alone, with no certificate round anywhere. The price is a larger committee, `n = 5f+1`. This section proves safety and liveness of the two-round rule at the *generalization* `n ≥ 5f+1` — direct thresholds `n − f`, indirect threshold `n − 3f`, specializing to the published `4f+1` and `2f+1` at the boundary — and reports four findings about the published safety argument, one of which is a genuine gap that the formalized rule must repair (§10.4). ### 10.1 The reuse boundary At `n = 5f+1` Odontoceti's quorums *are* `n − f`: the DAG quorum `4f+1`, and both direct thresholds. Its validity rules coincide with `ValidWrt` clause for clause — including the mandatory self-parent (P3′) — and its support/blame primitives are the `supporters`/`blames` of §3. The entire DAG layer of this report therefore applies verbatim, and only the rule layer is new; the fault bound is an *extension*, ```lean class Faults5 (Validator) extends Faults Validator where card_validators5 : 5 * f + 1 ≤ Fintype.card Validator ``` so a `Faults5` instance is a `Faults` instance and every existing theorem continues to apply to the same types. The stronger bound is consumed in exactly two proofs (O2 and O4′ below) — the two-round rule's *direct* safety already holds at `3f+1`. The witness file proves the reuse claim as a computation: a quorum-5 universe over six validators satisfies the untouched `BlockUniverse` by `decide` (§24). ### 10.2 The rule layer, and the arithmetic core ```lean def DirectCommit (U) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (supporters U L (r + 1)).card def DirectSkip (U) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (blames U L (r + 1)).card abbrev coneSupports (U) (A L : BlockId) (r : ℕ) : Finset Validator := coneSupporters U A L (r + 1) def ThickLink (U) (A L : BlockId) (r : ℕ) : Prop := coneLink (Fintype.card Validator - 3 * F.f) U A L r ``` `ThickLink` is the indirect test: enough supports for `L` visible in an anchor's cone. It counts **distinct authors** of in-cone support blocks, not raw blocks — the published rule says "`2f+1` supports in the history of the anchor" without disambiguating, and the block count is adversary-inflatable (an equivocating supporter can plant any number of support-twins in one cone), while the author count is the one the arithmetic on both sides actually bounds. Four counting theorems carry safety, and they identify precisely where the committee of size `5f+1` is required: **O1 (commit versus skip; needs only `n ≥ 3f+1`).** `not_directSkip_of_directCommit`: no block is both directly committed and directly skipped. Two `(n−f)`-quorums over `n` authors share `n − 2f ≥ f+1`; an author both supporting and blaming has two distinct decision-round blocks, so every member of the intersection is an equivocator — one more than exist. **O1′ (twin uniqueness; `n ≥ 3f+1`).** `eq_of_directCommit`: two directly committed same-author blocks are equal. Same intersection; an author supporting two distinct twins is an equivocator, because one block cannot cite an author twice (`distinct_creators`) and two supporting blocks are an equivocation. **O2 (a skipped leader fails the test everywhere; the first use of the fifth `f`).** `card_supporters_le_of_directSkip`: a directly skipped leader's supporters — anywhere in the universe, hence in any cone — number at most `2f`; and `2f < n − 3f` exactly when `n ≥ 5f+1` (`not_thickLink_of_directSkip`). The proof requires the **exact complement identity** `|Correct| = n − |byzantine|`: correct supporters and correct blamers are disjoint, correct blamers number at least `(n−f) − |byzantine|`, and the `|byzantine|` cancels, leaving correct supporters at most `f`. Bounding `|Correct| ≤ n` instead degrades the estimate to `3f`, which does *not* clear the threshold at the boundary — the natural loose count fails, and only the exact one proves the published lemma. **O3 (propagation — every anchor's cone is the certificate).** `thickLink_of_directCommit`: if `L` is directly committed, then *every* block from two rounds above it onward — Byzantine-authored included, validity being structural — carries at least `n − 3f` distinct authors of support blocks in its cone. One hop: a round-`(r+2)` block's `n − f` distinct-author parents meet the `n − f` supporters in `n − 2f` authors, of whom up to `f` are Byzantine equivocators whose *referenced* parent may be a non-supporting twin; the remaining `≥ n − 3f` are correct, and a correct author's unique decision-round block is both supporting and in the cone. Depth: cones are monotone through any single parent, so the bound never decays. This is the two-round replacement for M2/M4: there is no certificate object, so its rôle is played by the support pattern that every later cone is forced to contain. **O4′ (a direct commit excludes every rival; the second use of the fifth `f`).** `eq_of_directCommit_of_thickLink`: a directly committed block is the *only* same-author block that can pass the indirect test, at any anchor — `n − f` supporters of `L₁` and `n − 3f` in-cone supporters of a twin `L₂` would overlap in `n − 5f ≥ 1` correct authors, each supporting two twins. This lemma has no counterpart in the published argument, and §10.4 explains why it had to exist. ### 10.3 The decision relation, and agreement Eligibility contracts by one round — `decisionRound k = slotRound k + 1` and `Eligible k j ↔ slotRound k + 2 ≤ slotRound j` — and the view-relative direct rules lift to universe level exactly as in §3.2. The decision relation is the shared one (§3.5) at Odontoceti's data — wave one, the thick link as the rung, and the identifier order as its tie: ```lean def odontocetiAnchored : AnchoredRule Validator BlockId Payload ValidWrt Correct where wave := 1 Commit := fun U V L r => Odontoceti.DirectCommitIn U V L r Skip := fun U V S k => DirectSkipSlotIn (S := S) U V k rungs := 1 Link := fun _ U A L S k => ThickLink U A L (S.slotRound k) tie := fun _ L L' => L < L' ``` The tie — the committed candidate is the `≤`-least one passing the test at the anchor, under `[LinearOrder BlockId]` — is the *canonicity* of §10.4. With it, agreement is the relation's at Odontoceti's laws (`odontocetiLaws`), and safety follows the pattern of §5.5. Safety proper is the headline (`Properties/Arcs/Headline.lean`) at Odontoceti's rule, `OdontocetiProperties.safety : Properties.Safe odontocetiRule`: across any stack of mechanisms, verdicts transport, any two views agree, a commit is the slot's candidate, no block is committed twice, and across an extension every slot agrees. The laws are the arithmetic core: `commit_unique` is O1 and O1′; the crossings `commit_link`, `commit_link_unique` and `skip_link` are O2, O3 and O4′; and `link_unique`, two choices at the rung being one block, is canonicity, `le_antisymm` on the two tie premises. ### 10.4 The finding: agreement needs a canonical candidate The published agreement proof (its Lemma 5) handles the indirect/indirect case by arguing that both validators use the same anchor and that "the indirect decision rule solely depends on the causal history of the anchor". That is true of the *test* — but the rule must also **choose a candidate**, and nothing in the quorum arithmetic prevents two equivocating candidates from both passing the test at one anchor. The counting that would be needed — two `(n−3f)`-sized in-cone supporter sets overlapping in more than the `f` equivocators — requires `2(n−3f) − f > n`, that is `n > 7f`, which **fails** at `n = 5f+1`. The configuration is moreover realisable: on a valid six-validator universe, a Byzantine leader's two round-0 twins each gather exactly three supporters (disjoint correct pairs plus the equivocator's own split), and a round-3 block sees all of round 1 — **both twins pass `ThickLink` against it**, by `decide` (`utwin6_both_pass` (O11), §24). An indirect rule that commits "some passing candidate" therefore admits derivations committing either twin: agreement is *refutable*. What arbitrates in practice is the iteration order of the implementation's candidate loop — every honest node examines candidates in the same, unspecified but deterministic, order. The formalized rule states that determinism as mathematics: commit the least passing candidate in a fixed linear order on block identifiers (block hash, in an implementation). Under that premise agreement is a theorem; without it, false. The premise is consumed *exactly* where the published argument is silent — O4′ shows a directly committed block is the unique candidate passing the test anywhere, so every other pairing closes by counting, and canonicity arbitrates only the indirect-versus-indirect case with a shared anchor and an equivocating leader. For implementers: **the candidate-iteration order of the indirect rule is consensus-critical**. Two honest nodes iterating in different orders (for instance, arrival order) can commit different blocks for one slot at `n = 5f+1`; any fixed shared order restores agreement, and "first seen" does not. The remaining findings are recorded in the design document: a missing lemma (O4′, assumed tacitly by the published case analysis), the blocks-versus-authors ambiguity in the indirect test (only the author count is provable), and the exact-complement subtlety in the published Lemma 2 (§10.2, O2). ### 10.5 Liveness, one round shorter Liveness follows the §6 development with every hypothesis one round shorter — the protocol's latency advantage made visible as proof structure: * **O7.** Post-`R`, a correct-led slot commits *directly* from **two** populated rounds and one synchronised step (`Odontoceti.decided_of_leader_mem`): coverage makes every correct decision-round block reference the leader's block, and `Correct` carries a quorum. The §6.6 analogue needed three populated rounds. * **O8.** Under a pipelined identity-round schedule, a run of **two** consecutive committed slots spans eligibility for everything below (the relation's `spansEligible_of_identity`, at wave one): a slot cannot anchor on the round immediately above it, but the second slot of the run clears `slotRound + 2`. Two consecutive correct leaders is the published Lemma 10, now visible as arithmetic. * **O9.** A committed run of eligible span clears every slot below it (`Odontoceti.decided_below_of_committed_run`), by the nearest-eligible-committed-anchor induction of §6.6, with the indirect commit taking the minimum of the passing candidates — the constructive face of the canonicity premise. * **O10.** The composition, under enforceable hypotheses only: **L10.** ```lean theorem all_decided_below_of_fairRun {c : ℕ} (hc : 0 < c) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hspan : (coreAnchored Validator BlockId Payload).SpansEligible c) (fair : FairRunOn T c) (R : ℕ) (k : ℕ) : ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound (b + c - 1) + 2 ≤ N → ∀ i, i < b → ∃ v, Decided U (View.full U) i v ``` Note the horizon: the run's last slot needs rounds up to `slotRound + 1` — one round of certificates fewer than the §6 analogue, again the two-round structure showing through. The conclusion is on any view caught up to the horizon (`View.CoversUpto`, §13's idiom): the supporters sit one round above each leader, so a caught-up view holds them (`Odontoceti.directCommitIn_of_coversUpto`), and the full view is the special case (`View.coversUpto_full`). --- ## 11. The reactive schedule *(modules `LeanDag/Reactive/`)* The full-timeout discipline of §6.9 directs a validator to wait a full timeout in every round, so latency is a multiple of the timeout however fast the network happens to be. A **reactive** validator waits only as long as it must: at the round above a leader it builds as soon as it holds the leader's block and enough references, falling back to the timeout only if the leader does not arrive; under Mysticeti a validator that voted likewise waits at the next round only until it can certify. When leaders propagate faster than the timeout, consensus proceeds at network speed — and if every reliable validator is fast, no timeout ever fires (§11.3). The two disciplines share everything but the pacing. Both extend `PaceCore` (§6.9), so the assumptions of the trunk — the partial schedule, the views, `converges`, the progress rule — and the derived production are common property; they differ in the clauses that say *when* a validator builds within a round, and in what each supplies to the commit arguments: the full-timeout discipline derives coverage, the reactive one the targeted predicates of §6.6 and not coverage itself — what that forgoes, and what survives it, is §11.5. The commit rules, the decision relations and the whole safety development are consumed as found; only the schedule changes. ### 11.1 The reactive dichotomy `ReactivePace` is the schedule and network layer both protocols share, and it **extends the same trunk as the full-timeout discipline**: `PaceCore` (§6.9) supplies the partial schedule, the views, `converges` and the progress rule, so production is inherited (`PaceCore.populatedOn`) rather than assumed — the reactive arc carries no block function, and every block its statements name is produced by the derivation of §6.9. Relative to `ViewPace`, the full-timeout floor `waits` is gone; in its place: ```lean structure ReactivePace (U) (T : Finset Validator) (N : ℕ) extends PaceCore U T N where built_lt : ∀ v ∈ T, ∀ n < top v, built v n < built v (n + 1) deadline : ∀ v ∈ T, ∀ n < top v, built v (n + 1) ≤ built v n + timeout n vote_or_wait : ∀ v ∈ T, ∀ k : ℕ, S.slotRound k + 1 ≤ N → S.leader k ∈ T → ∀ L, IsLeaderBlock U k L → ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = S.slotRound k + 1 → L ∈ (U.block c).refs ∨ (built v (S.slotRound k) + timeout (S.slotRound k) ≤ built v (S.slotRound k + 1) ∧ (L ∈ holds v (built v (S.slotRound k + 1)) → L ∈ (U.block c).refs)) prompt_vote : … ``` `deadline` is the ceiling — a validator never waits *past* the timeout — and `built_lt` the only remaining floor, both over the rounds the validator reached. `vote_or_wait` is the reactive dichotomy: at the round above a reliable leader, any `T`-authored block either references the leader (the reactive exit), or its builder waited the full timeout and references any leader block it holds (the fallback). Building early *without* the leader is thereby excluded, which is the entire discipline: the schedule accelerates only where acceleration cannot cost the vote. The clauses are stated over any authored block, as everywhere on the pace line, so non-equivocation is never consulted. The clause is stated for slots whose leader lies in `T`. For a Byzantine leader nothing useful can be said — it may equivocate, and P2 forbids referencing two of its blocks — and no liveness statement concerns such slots. The reference coverage of the main line is deliberately unavailable here: a reactive builder omits whatever had not arrived when its exit condition was met, so `SynchronisedOn` fails in general. It is also unneeded — the exit conditions are chosen so that exactly the references the commit rule counts are present, early exit and fallback alike — and the extraction confirms that neither liveness result below reaches `SynchronisedOn`. ### 11.2 Liveness, both protocols The vote is the shared step: **RS1.** ```lean theorem votes (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hgst : rc.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rc.delay + rc.proc ≤ rc.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) : VotesAt U T (S.slotRound k) L ``` The fallback case is the whole argument, and it is the chain of `covers_of_converges` (V1) aimed at a single block: the leader holds its own block when it builds, convergence carries it across within `delay`, and the collapsed drift plus the full timeout place the arrival before the waiter's build, where the fallback clause obliges the vote. The reactive exit needs nothing — it *is* the vote. No drift hypothesis is taken: the reactive discipline inherits the trunk's collapse like the timed one, with `le_built` supplied by `built_lt` rather than by the floor, so the backoff is the same constant `2Δ + proc` — which is why the quorum bound now appears, feeding the collapse. The conclusion is `VotesAt` — the targeted interface of §6.6 — and this is where the two pacing disciplines meet. The full-timeout discipline derives the same predicate from coverage (`votesAt_of_synchronisedOn`); the commit is then one counting theorem either way (`directCommit_of_votesAt`, `directCommit_of_certifiesAt`), proved once and fed by whichever supplier the schedule provides. Under Mysticeti, `ReactiveM` adds the certificate stage as the analogous dichotomy `cert_or_wait` — a round-`(r+2)` block either already certifies, or its builder waited the full timeout and references every reliable vote it holds. `ReactiveM.certifies` concludes `CertifiesAt`, the second targeted predicate, and the liveness statement mirrors L4's conclusion with the coverage hypothesis replaced by the two wait clauses: **RS2.** ```lean theorem decided (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hgst : rm.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` The leader block's existence is a conclusion rather than a hypothesis — supplied by the trunk's derived production, as are the vote and certificate blocks the proof counts. Odontoceti requires **no new structure at all**. With no certificates, the vote stage is the whole protocol: a vote is a support, `T` is a quorum of supporters, and `Odontoceti.reactive_decided` (RS3) concludes from `ReactivePace` alone. The two-round rule is the natural home of the reactive discipline — one delivery separates a fast leader from its commit. ### 11.3 The fast path, quantified The claim that reactive execution tracks the network is a theorem, not a design intention. `prompt_vote` bounds the reactive exit by the trunk's processing constant `proc` — the same bound catch-up entry carries: once a validator past its round entry holds the leader and every reliable round-`r` block, it builds within `proc` — and the latency of a round is then bounded with the timeout appearing nowhere: **RS4.** ```lean theorem no_timeout_of_fast {δ : ℕ} (hδ : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ rc.holds v (rc.built ((U.block b).creator) (S.slotRound k) + δ)) (hD : ∀ u ∈ T, ∀ v ∈ T, rc.built u (S.slotRound k) ≤ rc.built v (S.slotRound k) + D) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) (hT : T ⊆ (Correct : Finset Validator)) (hfast : D + δ + rc.proc < rc.timeout (S.slotRound k)) : ∀ v ∈ T, rc.built v (S.slotRound k + 1) < rc.built v (S.slotRound k) + rc.timeout (S.slotRound k) ``` `δ` is the *actual* per-block propagation bound of the run — a premise about this execution, not an assumption about the network — and the conclusion degrades continuously as it approaches the timeout. Below it, every reliable validator builds strictly before its deadline: the fallback branch of the dichotomy is never taken, and consensus proceeds at the pace of `built_succ_le_of_fast` (RS4)'s bound, drift plus delivery plus processing per round. ### 11.4 The witness `ugrowReactive` (§24) runs the Mysticeti structure on the round-robin schedule at build spacing `6` inside a timeout of `9 = 2Δ + proc` — the drift-free backoff met with equality: every fallback branch untaken, the commit, the latency bound and the strictly-inside-deadline conclusion all exhibited on data. Its processing constant is honest rather than generous: `proc = 5` is the least value `prompt_vote` admits on this model, because a validator's shortcut to its *own* round-`r` block lets the trigger fire one tick before the slowest peer's block would force it. ### 11.5 Inclusion without coverage: the rotation backbone Chain quality's inclusion results (§7) run on full reference coverage, and the reactive discipline deliberately does without it: an early exit omits whatever had not arrived, so a straggler's block may be referenced by nobody at the round above — `SynchronisedOn` is false, and CQ5's per-round backbone with it. Inclusion survives anyway, by a different backbone. A correct author's blocks form a single descending chain under the self-parent clause P3′ (`SelfParent.reaches_of_creator`): each block references its author's previous one, existence at every step coming from completeness rather than from any production hypothesis, and uniqueness from T1. A straggler's block therefore lies below every later block of its *own author* — and when that author leads a slot, the reactive vote discipline commits the leader block, pulling the whole chain into the common cone at once. The schedule must return to the author in particular, which is a strictly stronger fairness than `FairScheduleOn` promises: ```lean def FairToEach (T : Finset Validator) : Prop := ∀ v ∈ T, ∀ k, ∃ k', k ≤ k' ∧ S.leader k' = v ``` Round-robin supplies it (`rrSlots_fairToEach`). The result then has CQ6's quantifier order — the slot is fixed by the schedule, before any execution is named: **RS5.** ```lean theorem committed_of_correct_block (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairToEach (S := S) T) {u : Validator} (hu : u ∈ T) (R m : ℕ) (hRm : R ≤ m) : ∃ k', m < S.slotRound k' ∧ R ≤ S.slotRound k' ∧ S.leader k' = u ∧ ∀ U N (rm : ReactiveM U T N), rm.gst ≤ R → (∀ n, R ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) → S.slotRound k' + 2 ≤ N → ∀ b ∈ U.ids, (U.block b).creator = u → (U.block b).round = m → ∃ L, IsLeaderBlock U k' L ∧ Decided U (View.full U) k' (some L) ∧ Reaches U L b ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSet U g n ``` No coverage appears among the hypotheses — they are `ReactiveM.decided`'s, plus the per-validator fairness. So the reactive system trades the inclusion *latency*, not the guarantee: where full coverage puts a correct round-`m` block in every correct cone one round later (CQ5), the reactive discipline puts it in the agreed ledger one leadership rotation later. Commit latency at network speed, inclusion latency at rotation speed — and both halves of that sentence are theorems. This is also one of the two liveness consumers of the self-parent clause P3′, the other being chain quality's inclusion half (§7); everywhere else it serves storage and recovery (§§8, 12), as the tables of §§2.2 and 4.8 show. `ugrowReactive` exhibits the result on data: validator `2`'s round-`1` block enters the ledger through a slot led by `2` itself, the reach running down `2`'s own self-parent chain. --- ## 12. Safe Skip: crash recovery in one message *(modules `LeanDag/SafeSkip/`)* A validator that crashes and recovers faces a re-entry problem. Every liveness result rests on production — P8, correct validators building in every round — so a validator that simply resumes at the current round leaves a gap that the liveness account does not tolerate, while rebuilding the missed blocks one at a time costs a round trip per round of downtime. **Safe Skip** closes the gap with a single message. The recovering validator `v1` names its own last block `B1` and a block at round `r` on a donor validator `v2`'s history line; the message *denotes* one block per missed round, deterministically given the DAG: at each gap round the filled block carries the references of `v2`'s block at that round, plus one added self reference to `v1`'s block of the round below — `B1` at the boundary, the previous filled block above it. The message is constant-size; the fill is as large as the outage. The added self reference is a validity requirement rather than a design choice: P3′ demands that every non-genesis block reference a block by its own creator, and the donor's references cannot supply one, `v1` having authored nothing in the gap. The clause that §2.2 records as absent from safety and liveness is here consumed for the first time outside the storage arcs — the fill exists in the shape it does because P3′ forces it. What is proved: the denotation is a block universe extending `U` with every old block untouched (SS1), production is restored at every gap round (SS2), the fill cannot conjure a commit for a slot the network already passed (SS3) — and every verdict reached before the fill re-derives after it, so recovery cannot disturb a decision (SS5, SS6). ### 12.1 The message and its denotation `SkipMsg` packages the message's content with the hypotheses its receiver verifies against its own DAG: ```lean structure SkipData (ids : Finset BlockId) (blk : BlockId → Block Validator BlockId Payload) where v1 : Validator B1 : BlockId v2 : Validator r : ℕ line : ℕ → BlockId fresh : ℕ → BlockId idx : BlockId → ℕ … hgap : ∀ b ∈ ids, (blk b).creator = v1 → (blk B1).round < (blk b).round → (blk b).round ≤ r → False ``` `SkipMsg U` abbreviates `SkipData U.ids U.block`. The message is stated over a bare block assignment for the reason `chopBlk` is (§9.1): its content and the receiver's checks are the same whatever validity rule the DAG satisfies. The fill of a universe is built once at the block record (§2.3), and what a rule supplies is the validity of the filled blocks, which the copy reading discharges for any predicate that does not read the author; the core's self-referencing reading proves it below. `line` is the donor's history line — one block per round from `r0 := round B1` up to `r`, each referencing the one below, which is the chain the message's pinned block determines by following self-parents — and `fresh` supplies unused identifiers for the filled blocks, with `idx` their decoder. The elided clauses record what the receiver checks: `v1` is distinct from `v2`, `B1` is `v1`'s block *and its only one at that round* (`hB1uniq`), the line has the stated creators, rounds and chaining, and the fresh identifiers are new. `hgap` is the crash itself: `v1` authored nothing strictly between `B1` and `r`. `hB1uniq` is stated as the uniqueness fact rather than as `v1 ∈ Correct`, from which it would follow by non-equivocation (`hB1uniq_of_correct`). The two are not interchangeable in every fault model: the hybrid model of §14 splits `Correct` into honest and available, and a *crash-prone* validator — the one Safe Skip exists to serve — is honest but outside `Correct`. Stating the fact the boundary argument actually uses is what lets the same structure describe both (§14's crash-prone case). The filled block at gap round `k` is the donor's references plus the forced self reference: ```lean def prev (k : ℕ) : BlockId := if k = sk.r0 + 1 then sk.B1 else sk.fresh (k - 1) ``` ```lean def fillBlock (k : ℕ) : Block Validator BlockId Payload where round := k creator := sk.v1 refs := insert (sk.prev k) (blk (sk.line k)).refs payload := (blk (sk.line k)).payload ``` and the denotation is the extension, with every old identifier looked up unchanged: **SS1.** ```lean def skipFill : BlockUniverse Validator BlockId Payload := BlockRecord.fill U sk (sk.selfBlocks U.complete) (fun _ hk1 hk2 => sk.fillBlock_valid hk1 hk2) ``` The fill is built once at the block record (§2.3): `BlockRecord.fill` adds one block per gap round under a *reading* of the filled blocks, looks every old identifier up unchanged, and proves closure, non-equivocation and the old blocks' validity for any predicate that is `Mechanised`. What a reading owes is the one clause the record cannot supply, that each filled block is valid under the extended map. The core's reading is the self-referencing one, and `fillBlock_valid` is that obligation: every clause of §2.2 must survive the fill. The predecessor clause P1 holds because the copied references sit one round below by the line's own P1 and the self reference is placed there by construction. P2, distinct creators, is the delicate clause: the copied references must not already contain a `v1`-authored block. Inside the gap the crash (`hgap`) forbids one; at the boundary round the only candidate is `B1` itself, pinned by non-equivocation (T1) — so the self reference collides with nothing. The reference quorum P3 only grows, P3′ holds by the inserted reference, and non-equivocation P5 survives because a filled block equal in author and round to an old one would contradict the crash. The companion fact is conservativity — ```lean [simp] theorem skipFill_block_old {b : BlockId} (hb : b ∈ U.ids) : sk.skipFill.block b = U.block b ``` — so every store, view and certificate built on `U` reads the same in the extension. ### 12.2 Production restored, consensus untouched Production is what the mechanism exists to restore, and it is restored exactly: **SS2.** ```lean theorem skipFill_populatedOn {T : Finset Validator} {k : ℕ} (hpop : PopulatedOn U T k) (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) : PopulatedOn sk.skipFill (insert sk.v1 T) k ``` With `v1` restored to the reliable set, every gap round carries a `v1` block — and `PopulatedOn` is the production hypothesis the liveness results of §6 consume, so the recovering validator re-enters the liveness account at the round after its anchor, from one message. The converse concern is that a filled block might *create* consensus where there was none: a gap round may be a leader round for `v1`, and the fill then supplies a leader block for a slot the network already passed. It cannot be committed: **SS3.** ```lean theorem decided_none_fresh [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {T : Finset Validator} {k : ℕ} (hcard : quorumCard Validator ≤ T.card) (hlead : S.leader k = sk.v1) (hk1 : sk.r0 < S.slotRound k) (hk2 : S.slotRound k ≤ sk.r) (hpres : PresentAt MysticetiProperties.mysticetiRule V T (S.slotRound k + 1)) : Decided sk.skipFill (sk.liftView V) k none ``` stated as a verdict on the lifted view: an instance of `decided_none_of_novel` at `SkipsUnsupported` and the fill's `Extends` witness (`Properties/Arcs/SafeSkip.lean`). No old block references a fresh identifier — the filled blocks did not exist when the old blocks were built, and completeness (P4) keeps old reference sets inside the old identifiers — so every `T`-authored block of the pre-crash view one round above the slot supports no candidate (`Unsupported`), and the core's `SkipsUnsupported` decides the slot `none`. The hypothesis `hpres` asks only that `T`, a quorum, be present in `V` at that round; `T` is drawn from the pre-crash view, so the recovering validator is not asked to blame its own fill. The clause the crash violates is exactly P8 (§4.4): a correct validator holding a quorum builds the following round unconditionally, and [QXS26] show that dropping it lets round-skipping starve certificate formation altogether — an infinite execution in which at most `2f` certificates are ever formed for any round, because production never recovers. Safe Skip does not weaken P8 to accommodate the crash; it keeps the base development's unconditional form and repairs the hypothesis after the fact, once, over the closed gap `hgap` bounds. That boundedness is what distinguishes the two situations: [QXS26]'s counterexample needs skipping to be permitted indefinitely, with nothing pulling validators back into lockstep, where the fill's gap has a fixed right endpoint and SS3 forbids exploiting it — a filled candidate is always decided `none`, never committed. ### 12.3 Verdict invariance The two theorems above concern rounds inside the gap. What remains is every slot the network decided *before* the fill: a verdict is a statement about a view of `U`, and after recovery the network's views extend `skipFill` instead. The garbage-collection arc met the same question for truncation and answered it with `decided_chop_iff` (G3); Safe Skip is the extension-side analogue. The route is `Persist`. The fill is an extension of `U` (`extends_of_skipFill`), and `Persist`, which the core derives from `Banded`, carries every verdict across an extension; no case analysis on the rule is needed. Reachability from an old block never leaves the old identifiers (`reaches_fill_old`), which the exposure and coverage arcs of §16 consume. **SS5.** ```lean theorem decided_fill_of_persist [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {k : ℕ} {v : Option BlockId} (h : Decided U V k v) : Decided sk.skipFill (sk.liftView V) k v ``` It is `Persist` at `mysticetiRule`, applied to the lifted view. Composing with agreement in the extension (M6) yields the statement deployment relies on — no verdict moves across a recovery, whatever view either side held: **SS6.** ```lean theorem decided_fill_agree_of_properties [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {W : View Validator BlockId Payload sk.skipFill} {k : ℕ} {v w : Option BlockId} (hv : Decided U V k v) (hw : Decided sk.skipFill W k w) : v = w ``` ### 12.4 The witness `Ucrash N` (SS7, §24) is the round-robin family with validator `3` crashed after its genesis block: three validators run full lines whose references omit the absent author, and `3` owns exactly one block. The message `ucrashMsg` targets validator `1`'s line, and the development's house rule is exercised end to end: the fill's reference sets and cardinality are computed by `decide`, the gap is populated (`ucrash_populated`), and `decided_fill_of_persist` is applied to the full view with no side condition. ### 12.5 How the mechanism should be used Three constraints on Safe Skip are not visible from this section, since each arises only against another arc. §16 proves them; they are collected here because they are what an implementer of §12 needs. **Recover within the garbage-collection lag.** The fill needs its anchor, and a horizon that has passed the crash round has pruned it. So a fill is available exactly for outages no longer than the lag (§16.4); beyond it the validator's chain is severed, and it recovers by the longer route of §12.6. **Draw the donor line from the common core.** §5.2's T3c supplies, at every round and under no assumption, a correct-authored block that every block two rounds later reaches. A donor line chosen from such blocks cites only material every recipient already holds (§16.7), so the message needs to carry nothing but the target's name — which is the property the mechanism was designed for, and which §12.7 proves: the line, and the whole fill, are derived from that name alone (SS8, SS9) — and the recovering validator holds what it cites, rather than pointing at history it cannot serve. **Check the fill before accepting it.** The self reference P3′ obliges enlarges a filled block's cone past the donor's, so the exposure condition of §8.2 must be re-established rather than inherited. The check is local to the fill and needs no identity oracle (§16.6), and against a covered donor line it reduces to reachability. ### 12.6 When the fill is not available A validator down longer than the lag is in a worse position than unable to fill: P3′ requires every non-genesis block to cite a block by its own creator, so a validator with nothing in the retained layer can produce nothing at all (§16.5). Bootstrapping by §9.5's attested base makes it a correct *reader* — its verdicts agree with everyone's — and not a producer. Recovery is then three steps rather than one: bootstrap to read, **re-genesis** to write, and a fill to catch up, the last anchored on the re-genesis block and spanning the retained window (§16.5). The middle step needs no exemption from P3′, the retained layer being genesis after truncation. Until it completes, the validator counts against the fault budget however well caught up it is, which prices the recovery window (§16.5). ### 12.7 Round jumping: the fill is derived, not transmitted *(results in `LeanDag/SafeSkip/Jump.lean`; witness in `LeanDagTest/SafeSkip/Model.lean`)* A slow validator at round `r` that sights a quorum at round `R ≫ r` wants its next block at `R + 1`, not `R − r` blocks of catch-up. The pacemaker already permits the jump — P11 turns evidence of a round into entry into it (§6.11) — and what pins the DAG is P3′: a block at `R + 1` must reference a block by its own creator at round `R`, and the laggard has none above `r`. The fill is exactly what closes that gap, so the question is what the jump *costs*, and §12.5 already claimed the answer in prose: the message "needs to carry nothing but the target's name". This section makes the claim a theorem — the fill is not data but a *denotation*, derived by every receiver from a message of constant size. The first step is that the donor line was never free data. P2 admits at most one reference per author and P3′ guarantees one by the block's own, so "the self-parent" is a well-defined function (`selfParent`, its uniqueness `eq_selfParent_of_mem` being P2 verbatim), and the chain descending from the message's pinned target is the only line there is: **SS8.** ```lean theorem SkipMsg.line_eq_lineOf (sk : SkipMsg U) : ∀ k, sk.r0 ≤ k → k ≤ sk.r → sk.line k = lineOf U (sk.line sk.r) k ``` Any `line` satisfying a `SkipMsg`'s four clauses coincides, on the whole interval the clauses govern, with the derived chain `lineOf` — the self-parent iterated down from the top block. The message's largest field is thereby redundant, and with it the two validator names: the anchor determines `v1` (`v1_eq_of_B1`) and the target determines `v2`. What remains is the compact core, and the denotation is a function of it: **SS9.** ```lean theorem SkipMsg.skipFill_eq_of_core (sk₁ sk₂ : SkipMsg U) (hB1 : sk₁.B1 = sk₂.B1) (hr : sk₁.r = sk₂.r) (htop : sk₁.line sk₁.r = sk₂.line sk₂.r) (hfresh : sk₁.fresh = sk₂.fresh) : sk₁.skipFill.ids = sk₂.skipFill.ids ∧ ∀ b ∈ sk₁.skipFill.ids, sk₁.skipFill.block b = sk₂.skipFill.block b ``` Two messages naming the same anchor and the same target denote observationally equal universes — equal identifiers, equal blocks at every one of them, the statement shaped like `regenesis_converges` (I11) because the two objects differ only on junk nothing reads. The decoder `idx` needs no hypothesis: `block` consults it only at fresh identifiers, where `hidx` pins both decoders. The `hfresh` hypothesis is the model's rendering of content addressing — deployments name blocks by hash, so two parties deriving the same blocks assign the same identifiers. `JumpMsg` packages the core as the message a recovering validator actually sends — four names and the fresh-identifier supply, no line — and its elaboration `JumpMsg.toSkipMsg` rebuilds the `SkipMsg` with `lineOf` as the line, every clause discharged by the derivation lemmas. `JumpMsg.denote`, the elaboration's fill, is the round jump: the universe in which the sender produces at `R + 1`. Being a `skipFill`, everything §12.1–§12.3 proved — SS1 through SS6 — applies to it verbatim. The last step is that receivers can run the elaboration at all, and here the view structure of §2.3 pays off twice. Views are closed downward, so a view holding the target holds the entire derived line — the message points at nothing a receiver lacks; and views share `U.block`, so all parties derive the *same* fill: **SS10.** ```lean theorem lineOf_mem_view (V : View Validator BlockId Payload U) {B2 : BlockId} (hB2 : B2 ∈ U.ids) (hB2V : B2 ∈ V.ids) : ∀ k, k ≤ (U.block B2).round → lineOf U B2 k ∈ V.ids theorem JumpMsg.denote_eq_of_core (j₁ j₂ : JumpMsg U) (hB1 : j₁.B1 = j₂.B1) (hB2 : j₁.B2 = j₂.B2) (hfresh : j₁.fresh = j₂.fresh) : j₁.denote.ids = j₂.denote.ids ∧ ∀ b ∈ j₁.denote.ids, j₁.denote.block b = j₂.denote.block b ``` This is I11's move — *derive rather than transmit, so nothing is sent and nothing can be rejected* — applied to the whole fill rather than to one genesis block, and it is what licenses reading Safe Skip as round jumping: the wire carries a constant-size message, the gap blocks exist only as the denotation every party reconstructs identically, and the laggard produces at `R + 1` after one message rather than `R − r` round trips. **The witness (SS11).** `ucrashJump` is `ucrashMsg`'s compact core — the same four names, no line — and the two theorems are exercised against the hand-built original: the elaboration reproduces the line `ucrashMsg` wrote by hand (`ucrashJump_line_eq`, SS8 on data), and the two denotations agree on identifiers and blocks (`ucrashJump_denote_eq`, SS9 on data). The jump and the hand-built fill are the same universe. What is deliberately not claimed: the *logical* universe still grows by one block per gap round, so the counting results of §8 and §9 read unchanged. The theorems price the wire and the derivation, not the denotation — and the gap itself stays bounded by the garbage-collection lag, since an outage longer than the lag recovers by §12.6's longer route, whose fill spans at most the retained window. --- ## 13. Adaptive leaders: the schedule as a fixpoint *(modules `LeanDag/Adaptive/`; the design record is `adaptive-leaders.md`)* Deployed systems do not run the blind rotation the `Slots` instance models: Hammerhead-style implementations [Tsi+23] consult the agreed prefix after a commit and reassign the leaders ahead, demoting validators whose slots were skipped. The intuition for safety is this development's own agreement theorem — the verdict sequence is agreed (M6, M7), so any function of it is agreed, and every correct validator derives the same revised schedule. Turning the intuition into a proof meets a circularity the base development does not have. In the decision relation verdicts flow *downward*: a slot is decided indirectly by anchoring on a committed slot above it, arbitrarily far up. An adaptive schedule makes leader identity flow *upward*: the leaders of a high slot depend on verdicts below. Composed without restriction, the verdict of a slot may depend on the leader of its own anchor, whose identity depends on that verdict — and nothing rules out two *self-justifying* schedules, an agreement failure manufactured by the mechanism itself. The arc stratifies the dependency and proves the fixpoint forced: **safety is unconditional** — any two adaptive fixpoints agree, under no synchrony or fairness hypothesis, for arbitrary, even adversarial, adapted policies (AL3) — while **liveness prices the policy's choices** through one clause, the adaptive counterpart of the run fairness the fixed schedule assumes (AL5). Both hold for both commit rules: the adaptive layer is rule-agnostic, and its two-round mirror consumes the policy objects unchanged (AL7). ### 13.1 Epochs, the lag, and the bounded relation Slots are grouped into epochs of `W` consecutive slots (`epochOf W k := k / W`); the schedule of an epoch is a function of the verdicts of epochs at least two below it, and an epoch's verdicts must be derivable with anchors strictly below the start of the epoch two above. The dependency is then well-founded — each stage consults strictly earlier data than the stage above it produces — and the lag of two is the least that works: with lag one, the slots at the top of an epoch would have no eligible anchors inside their window. This mirrors what deployments do, applying reputation to leader selection after a pipeline delay. The bound is carried by `Properties.DecidedBelow` (`Properties/Derived/Bounded.lean`), a definition on the rule's `Decided` that records every anchor below `B`, whose congruence `DecidedBelow.reschedule` is a theorem. The relation also states the bounded form explicitly (AL2, `LeanDag/Common/Anchored/Bounded.lean`), once for every rule, since a `Decided` derivation is a `Prop` and its anchors cannot be recovered from it: ```lean inductive DecidedWithin (U : BlockRecord …) (V : U.View) (B : ℕ) : ℕ → Option BlockId → Prop … | indirectCommit {k j : ℕ} {A L : BlockId} {i : ℕ} : k < j → j < B → R.Eligible k j → DecidedWithin U V B j (some A) → (∀ m, k < m → m < j → R.Eligible k m → DecidedWithin U V B m none) → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → IsLeaderBlock U k L → R.Link i U A L S k → R.Least U A i k L → DecidedWithin U V B k (some L) … ``` Two structural facts carry the arc. The bound forgets to `Decided` (`DecidedWithin.toDecided`), so every safety theorem of the base development applies to bounded verdicts without restatement — agreement for the bounded relation *is* M6. And congruence (`DecidedBelow.reschedule` generically; `decidedWithin_congr_of_slotRound` for the explicit form, at the rule's laws `skip_congr` and `link_congr`): the relation reads the schedule's `leader` only at slots below `B` (only `IsLeaderBlock`, the skip and the links consult it, each at its own slot; the round structure is fixed), so two assignments agreeing below the bound derive exactly the same verdicts — ```lean theorem decidedWithin_congr {hinj : Function.Injective S.slotRound} {a₁ a₂ : ℕ → Validator} {V : View Validator BlockId Payload U} {B k : ℕ} {v : Option BlockId} (ha : ∀ m, m < B → a₁ m = a₂ m) (h : DecidedWithin (S := slotsOf hinj a₁) U V B k v) : DecidedWithin (S := slotsOf hinj a₂) U V B k v ``` — which is what permits judging an epoch against a schedule only partially determined. `slotsOfKeyed` (AL1) is the `Slots` instance a leader assignment induces over the base round structure, and its `keyed` clause is the policy's own: `Policy.keyed` asks that two slots of one round are led by different validators. One leader per round makes it a lemma, since the rounds separate the slots whatever the assignment does (`slotsOf`); under multi-leader rounds a reassignment could collide two slots of one round onto one validator, and the clause is genuinely owed. `Adaptive.PickKeyed` is that obligation stated at *every* count a count-varying mechanism may reach, and `pickKeyed_one` is the one-leader case, where it holds of any reassignment at all. **AL10 — a verdict is local in the rounds, not only in the leaders.** `DecidedBelow` holds the round structure fixed and lets the leaders vary below a bound, which is what reassignment means. `Banded` gives the other half once its round clause is read below the band rather than everywhere, and `Properties.exists_roundLocal` is that half: every decided slot has a round below which *any* schedule agreeing on the rounds and the leaders decides it the same way. `AnchoredRule.banded_direct` pins the bound for a directly decided slot at the slot's own round plus a wave, the derivation reading nothing above its own wave. `Barnacle.cfg_local` is what that says of §21's mechanism, and it is an assumption that arc makes without stating: `PartialRun` records a configuration's verdicts as decided against that configuration's schedule extended to every round, which is not the schedule that runs once the configuration changes. A configuration's verdicts being settled within its own rounds is what makes it sound, and this is that statement. ### 13.2 The policy and the run `Adaptive.Policy` packages the reassignment rule with the clauses it owes. It reads a protocol's carrier only — universes and views — so it is stated over any `Properties.DagRule`, and the core's `AdaptivePolicy` is this structure at the core's carrier: ```lean structure Policy (R : DagRule Validator BlockId Payload) [S : Slots Validator] where W : ℕ … pick : (U : R.Universe) → R.View U → (ℕ → Option BlockId) → ℕ → Validator adapted : ∀ (U : R.Universe) (V₁ V₂ : R.View U) v w k, (∀ j, epochOf W j + 2 ≤ epochOf W k → v j = w j) → pick U V₁ v k = pick U V₂ w k base_prefix : ∀ (U : R.Universe) (V : R.View U) v k, epochOf W k < 2 → pick U V v k = S.leader k ``` `adapted` is the measurability clause and the heart of the safety argument: the leader of slot `k` is a function of the verdicts of epochs `≤ epochOf k − 2` and of nothing else — the view included. `pick` receives the universe and the validator's own view of it, so that a reputation rule may consult the committed blocks themselves — certification patterns, payload contents — and not merely the verdict vector. The view is what a validator actually has, and a rule reading it freely could hand two correct validators different leaders; `adapted` rules that out, and is no restriction in practice, since the committed prefix is what every view holding those verdicts holds whole. What a *deployed* validator may consult is its committed prefix only: as with the enforceability discussion of §4.7, the model states the mathematical condition and the implementation owes the discipline. Fairness is deliberately absent from the structure — safety must hold for policies that violate it. The central object is a schedule-and-verdict pair coherent with the policy, every slot decided inside its epoch window against the schedule the policy computes from the verdicts themselves: ```lean structure Run (P : Policy R) (U : R.Universe) (V : R.View U) where /-- The leader assignment. -/ assign : ℕ → Validator /-- The verdicts. -/ vdct : ℕ → Option BlockId /-- Every slot is decided inside its epoch window. -/ closed : ∀ k, DecidedBelow R (slotsOfKeyed assign keyed) (P.W * (epochOf P.W k + 2)) V k (vdct k) /-- The assignment is the policy's, computed on this view, everywhere. -/ coherent : ∀ m, assign m = P.pick U V vdct m ``` Existence and uniqueness are deliberately separated, mirroring the base development's split between the `Decided` relation and `decided_unique`: **uniqueness is the safety theorem, existence is the liveness theorem.** A partial variant (`PartialRun`, closed up to an epoch height) states what a validator holds mid-execution. ### 13.3 Safety: the fixpoint is unique **AL3.** ```lean theorem run_agree {V₁ V₂ : R.View U} (A₁ : Run P U V₁) (A₂ : Run P U V₂) : (∀ k, A₁.vdct k = A₂.vdct k) ∧ (∀ m, A₁.assign m = A₂.assign m) ``` Two adaptive fixpoints over one universe — derived from *any* two views, under *no* synchrony or fairness hypothesis — hold the same verdicts and run the same schedule. The proof is a strong induction on epochs in which nothing about counting is ever re-proved: verdict agreement below an epoch forces the two assignments to agree through the epoch above it (`adapted`), which places both runs' derivations in the *same* `Slots` instance (`DecidedBelow.reschedule`), where agreement is the property `Agree`, which Mysticeti, Odontoceti, Hydrozoan and Nemo-Nemo each prove of their own rule. The induction is carried by the partial-run form (`partialRun_agree`), so validators that have not decided equally far agree on their common prefix. Two corollaries: the commit sequence read from any two runs is the same list (`run_commitSeq_agree`, AL6, the shape of M7), and under the constant policy a run's verdicts are ordinary `Decided` verdicts of the base schedule (`Policy.const_run_decided`, AL4) — the anchor demanded by the house rule that a new relation must instantiate to the old one. ### 13.4 Liveness: the fixpoint exists Existence consumes the standard interface — `SynchronisedOn` and `Populated`, both untouched by reassignment, since neither mentions a leader — plus the one clause that prices the policy: ```lean def PlacesRuns {R : DagRule Validator BlockId Payload} (P : Policy R) (T : Finset Validator) (c : ℕ) : Prop := ∀ (U : R.Universe) (V : R.View U) (v : ℕ → Option BlockId) (e : ℕ), ∃ b, P.W * (e + 1) ≤ b ∧ b + c ≤ P.W * (e + 2) ∧ ∀ i, i < c → P.pick U V v (b + i) ∈ T ``` Every assignment the policy can emit places, in each epoch past the base prefix, a run of `c` consecutive `T`-led slots — `FairRunOn`, relativised to the policy's outputs. Hammerhead's purpose lands on this clause: a policy that reacts to observed skips satisfies it by construction where a blind rotation satisfies it by assumption — but which validators are reliable is not the designer's to know, so it remains a joint condition exactly as P10 is. **AL5.** ```lean theorem run_exists_of_support (ha : Agree R) (hcom : sp.Commits rel) (hind : Indirect R (fun sr i j => sr i + sp.wave + 1 ≤ sr j)) (hc : 0 < c) (hspans : SpansEligibleAt (S := S) sp.wave c) (hruns : PlacesRuns P T c) (V : R.View U) (hlive : ∀ (E : ℕ) (A : PartialRun P U V E), sp.live rel (slotsOfKeyed (fun m => P.pick U V A.vdct m) (P.keyed U V A.vdct)) V T P.W (P.W * (E + 2))) : Nonempty (Run P U V) ``` The statement is on a validator's own view, caught up to the horizon — `View.CoversUpto U V N`: `V` holds every block of `U` at a round up to `N` — which the full view is at every `N` (`View.coversUpto_full`), so the whole-universe reading is the special case; under eventual DAG synchrony (§2) every correct validator's view is caught up once delivery has reached the horizon. The construction consumes the derived `LeaderCommits` and `Descends` of the rule, and derives both from a support: Law 2 gives the first at `Support.live`, the indirect rule with `SpansEligible` the second (`descends_slotsOf`). Mysticeti, Odontoceti, Hydrozoan and Nemo-Nemo each supply a support with Law 2, so each instantiates the mechanism without a theorem of its own. It counts nothing anew: the run `PlacesRuns` puts in epoch `e + 1` commits directly (L4 at the induced instance, its certificates under the horizon and so in the view), the committed-run descent — restated with the anchors' bound carried through, the base proof already anchoring at or below the run's top — clears the epoch below it, and `exists_partialRun` stacks epochs under a finite horizon, re-reading the schedule off the verdicts so far at each stage. Partial runs at every height then glue into a total run along the diagonal, with `partialRun_agree` supplying the coherence that the stage-by-stage choices need not. A total run decides every slot there is, which is why the precondition is asked for at every height; the finite-horizon statement is `exists_partialRun`, which asks for it only below `E`, and it is the witnessable form. The precondition itself is `coreSupport.live`, the support's own, and the theorem names no execution model. A synchronous caller supplies it from coverage through `Timed.live_of_coverage` — production at every round and a view caught up to every horizon — and a reactive one through `coreSupport_live_of_reactiveLive`, whose `SynchronisedOn` is false by design. The two were separate instantiations of the same construction until they were read at the socket both already reach. Hammerhead [Tsi+23] — the reputation-based schedule deployed in Sui mainnet since v1.9.1 — proves an analogous result by a different route. Validators may run different schedules concurrently there, and safety is recovered by proving the schedules necessarily *reconverge*, via quorum intersection between anchors committed under different schedules, interleaved with the base protocol's own liveness — its safety corollary is stated to follow from its liveness lemma directly. The account here separates the two questions instead: `DecidedWithin`'s bound makes divergence unstatable rather than something to reconverge from, so `run_agree` needs no synchrony assumption at all, and liveness is the independent question of whether the fixpoint the bound describes exists. ### 13.5 Schedules and pruning A policy reading the committed prefix meets garbage collection, which prunes it. §16.4 states the two conditions that keep the two compatible: the policy must be **horizon-stable**, computing on a truncated prefix what it would compute on the full one, and a garbage-collection base slot must be a whole number of epochs. Otherwise two validators can agree on who leads every slot and still disagree about which verdicts the policy was entitled to read. ### 13.6 The two-round mirror The Odontoceti development (AL7) exhibits that the layer is rule-agnostic. `AdaptivePolicy` and `PlacesRuns` are consumed as found — they are protocol-free — and only the decision relation is mirrored: `Odontoceti.DecidedWithin` carries the canonicity clause of the two-round indirect commit through the bound, and its congruence transports the clause in both directions, the candidate set reading the schedule only through `IsLeaderBlock`, which the protocols share. Per epoch, agreement is O5 through the embedding exactly as the three-round side used M6, and existence consumes O7 and the bounded two-round descent with its least-candidate selection — two populated rounds where Mysticeti needs three: ```lean theorem run_agree {V₁ V₂ : R.View U} {V₁ V₂ : View Validator BlockId Payload U} (R₁ : AdaptiveRun P U V₁) (R₂ : AdaptiveRun P U V₂) : ∀ k, R₁.vdct k = R₂.vdct k ``` `Faults5` supplies the `Faults` instance the shared policy layer expects, so nothing is restated on the way. ### 13.7 The witness, and what remains `demotePolicy` (AL8, §24) is genuinely adaptive at epoch length one — a slot whose verdict two below was a skip is handed to a fixed replacement — and the witness exhibits the phenomena the theorems govern: the same DAG under a reassigned leader commits a *different block* for a slot, on both rules; a skip two slots below moves a later slot's leader off the base rotation; two runs over distinct views are constructed and shown verdict- and schedule-identical by AL3; and the two-round indirect commit carries its canonicity clause through the bound on data. The runs are **partial**, closed to the height the witness universe supports: `U7` is finite, a skip asks for a quorum of blockers at the voting round, and no slot past the frontier has one — so a total run needs the populated-everywhere hypothesis AL5 carries, and the reassignment is read off `pick` rather than off a run. One question from the design record remains open (AL9): whether the anchor bound is *necessary* — a model with two self-justifying runs under unbounded anchors would justify the stratification the way the `bound_is_necessary_pace` witness justified the convergence bound. The interaction between an anchor's leader and the verdict it anchors is delicate, and the answer may need more than four validators. --- ## 14. Hybrid fault tolerance: Byzantine and crash faults apart *(modules `LeanDag/Hybrid/`; the design record is `hybrid-plan.md`; the protocol is Orcaella's DAG instantiation [KS26])* Treating every fault as Byzantine is pessimistic: crashes are common, equivocation is expensive. The hybrid fault model separates the two — `fb` Byzantine validators, who may equivocate, and `fc` crash-prone validators, who are honest but may halt — and [KS26] derives the tight committee for two-round commitment under it: n ≥ 5·fb + 3·fc + 1, q = n − fb − fc, k = 2·fb + fc + 1 for the committee, the direct threshold and the indirect threshold. At `fc = 0` this is Odontoceti's `5f + 1`; the point of the model is what it yields at the other end: at `fb = 0, fc = 1` the committee is **four validators with two-round finality**, where tolerating the same single fault as Byzantine costs six. This section machine-checks both directions of that result for the DAG rules: safety and liveness at the generalized bound (H1–H8), and a data refutation one validator short (H10). ### 14.1 The model: one honest class, one correct class In the base development `Correct` does two jobs at once — it is the population that does not equivocate (P5 binds its creators) and the population liveness may rely on. The hybrid model splits them. **Honest** (`≥ n − fb`) is the complement of the Byzantine set alone: a crash-prone validator's block is one block, identical to all recipients, so it counts for safety. **Correct** (`≥ n − fb − fc`) is honest *and* available: only it counts for liveness. ```lean class HybridFaults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where fb : ℕ fc : ℕ byzantine : Finset Validator crash : Finset Validator disjoint : Disjoint byzantine crash card_byzantine : byzantine.card ≤ fb card_crash : crash.card ≤ fc … ``` Two devices keep the arc small. First, crashing is *invisible to a structural model*: the object of study is a DAG with invariants, not a transition system, so "halts at time `t`" cannot even be expressed. A crash is absence — the validator's blocks stop — and the crash class therefore needs no behavioural clause at all: it enters only through the cardinality arithmetic and its exclusion from liveness's reliable set, which the `T`-relativised interface of §6 was already built to express. Second, the **derived instance**: `HybridFaults.toFaults` places the union class `byzantine ∪ crash` in the base `Faults` structure, so the base quorum `n − F.f` *is* the hybrid quorum `q` and every quorum-shaped clause of the DAG layer — validity P3, views, the counting vocabulary — instantiates verbatim. What the derived instance gets wrong is exactly one clause: its P5 binds only the fully-correct class. The strengthening is the arc's one genuinely new assumption, threaded through the safety theorems the way `DoSValid` is: ```lean def HonestNoEquiv (U : BlockUniverse Validator BlockId Payload) : Prop := U.NoEquivOn (Honest Validator) ``` the record's non-equivocation on a set (`BlockRecord.NoEquivOn`) at the honest class: one block per round for every author outside the Byzantine set. That crash-prone validators do not equivocate is a clause of the *fault model* — the honesty of a class, like the Byzantine bound itself — not conduct the protocol enforces. The counting core the conflict arguments route through is H1, the shared intersection lemma (`exists_mem_inter_notMem`) at the Byzantine bound `fb`: two author sets whose sizes sum past `n + fb` share an honest member. The extraction records the division of labour: the H-family consumes P2 and P4 exactly where the Odontoceti family does, with `HonestNoEquiv` standing in the place P5 occupies there. ### 14.2 The rules, and the admissible interval The rules are Odontoceti's shape at the hybrid constants: direct commit and skip at `q` distinct authors, the indirect test `ThickLink` at `k` distinct in-cone authors — with `k` carried as a *parameter*. The development does not fix [KS26]'s constant; it proves the theorems for every threshold in an interval: ```lean def Admissible (k : ℕ) : Prop := 2 * H.fb + H.fc + 1 ≤ k ∧ k + 3 * H.fb + 2 * H.fc ≤ Fintype.card Validator ``` The lower end is what the skip-side conflicts consume — a directly skipped leader's supporters number at most `2·fb + fc` anywhere in the universe (H3), one honest validator below it — and the upper end is what link integrity supplies: every valid block from two rounds above a directly committed leader carries at least `2q − n − fb = n − 3·fb − 2·fc` support authors in its cone (H4), the interval's upper end with equality. Twin uniqueness and the exclusion of rival candidates (H2, H5) complete the Odontoceti mirror, each discounting against `Honest` where the pure-Byzantine proofs discount against `Correct`. The interval is nonempty **exactly when** `n ≥ 5·fb + 3·fc + 1` — the committee bound *is* the existence of a working threshold (`committee_bound_of_admissible` states the converse), and this is the form in which the bound enters every theorem: the `HybridFaults` class itself carries only the base `3·(fb + fc) + 1` clause the derived instance needs, which is what makes the one-short committee expressible for §14.6. [KS26]'s `k = 2·fb + fc + 1` and the house-style `n − 3·fb − 2·fc` are the two named instantiations (`kTight`, `kRel`), coinciding at the minimal committee. ### 14.3 Agreement The decision relation is the shared one at Odontoceti's data with the thick link at threshold `k` (`hybridAnchored … k`), tie included — a *Byzantine* leader can still plant two passing candidates in one anchor's cone, and nothing about the crash class closes that gap — and agreement is the relation's, the laws holding under `HonestNoEquiv` at every admissible threshold: **H6.** ```lean theorem hybridLaws {k : ℕ} (hk : Admissible Validator k) : (hybridAnchored Validator BlockId Payload k).Laws (fun _ => HonestNoEquiv) ``` The carrier is `toDagRuleOn HonestNoEquiv`, the records satisfying the invariant, and `HybridProperties.agree` is the relation's agreement under it. A remark on the source protocol is owed here. [KS26] proves safety for its core vote-counting protocol, whose view change selects among tied digests deterministically (`min`); the DAG instantiation's indirect rule, as published, does not carry that tie-break, and its correctness is argued by correspondence with the core conditions. The correspondence is not complete on this point: in the core protocol the tied selection feeds a re-proposal that must re-earn `q` votes, while the DAG rule commits outright — and without a canonical selection, agreement between indirect commits is refutable on data in the pure-Byzantine case (`utwin6_both_pass`, §10.3), an arithmetic the hybrid parameters do not close. The relation verified here therefore retains the canonical-candidate premise, the same repair §10 supplies for Odontoceti. ### 14.4 Liveness Liveness consumes the `T`-relativised interface exactly as the base development states it — `T ⊆ Correct` now excludes the crash-prone through the derived instance, and coverage and production never mention a leader or a fault class: **H7.** ```lean theorem decided_of_leader_mem (hcard : q Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ Decided k U V s (some L) ``` with the run machinery (`decided_below_of_committed_run`, `all_decided_below_of_fairRun`) composing as in §10. Two facts are worth drawing out. `HonestNoEquiv` appears in **no** liveness theorem — no liveness argument counts an equivocator — and every statement holds at *every* threshold `k`: only agreement prices the interval. And the tight committee has no slack: at `n = 5·fb + 3·fc + 1` the correct class numbers exactly `q`, so the reliable set must be all of it — the hybrid analogue of §24's remark that at `f = 1` every correct validator is needed for a quorum. ### 14.5 Conservativity At `fc = 0` the thresholds are Odontoceti's (`q = n − f`, `kRel = n − 3f`, the interval anchored at `2f + 1`), and the fault models identify: every `Faults5` committee is a crash-free hybrid one (`Faults5.toHybrid`), and the two derived `Faults` instances are **equal** (`toHybrid_toFaults`), so a block universe over one is a block universe over the other with no transport (H8). ### 14.6 The bound is necessary **H10.** ```lean theorem hybrid_bound_necessary (k : ℕ) : ∃ (U : BlockUniverse (Fin 8) (Fin 29) Unit) (L : Fin 29), HonestNoEquiv U ∧ Hybrid.Decided k U (View.full U) 0 (some L) ∧ Hybrid.Decided k U (View.full U) 0 none ``` One validator short — `fb = 1, fc = 1, n = 8`, where the would-be interval `[4, 3]` is empty — agreement fails **at every threshold**, and the two ends of the interval name the two attacks. For `k ≤ 3`, indirect safety fails: six blame authors directly skip the candidate while an anchor's cone carries three support authors (two honest, one Byzantine twin), and the indirect rule commits what the direct rule skipped. For `k ≥ 4`, link integrity fails: six support authors directly commit the candidate while a perfectly *valid* anchor packs its six references with all three available blame authors, leaving three supports in its cone. In both universes the conflicting derivations come from the **same full view** — one validator short, the rule set itself is inconsistent — and both universes are lawful, `HonestNoEquiv` included: the only twins are the Byzantine validator's. Each attack fails by exactly one honest validator, which is the bound being tight rather than convenient. With H1–H8 this machine-checks both directions of [KS26]'s Theorem 1 for the DAG rules: the committee bound is sufficient, and it is the least sufficient committee. ### 14.7 The witnesses `Uhyb4` (H9, §24) is the arc's principal witness: `fb = 0, fc = 1, n = 4` — the classical `3f + 1` committee with two-round finality when the single tolerated fault is a crash. Validator `3` halts after its genesis block; the survivors run three rounds at quorum `3`, slots commit directly in one delivery, and the crashed validator's slot is skipped vacuously. `Uhyb9` is the tight genuinely hybrid committee (`fb = 1, fc = 1, n = 9`): one equivocator, one halted line, `HonestNoEquiv` holding on data with the twins Byzantine-authored, and a slot committed on eight supporting authors. --- ## 15. Nemo-Nemo: crash-fault consensus in two rounds *(modules `LeanDag/Nemo/`; the protocol is Nemo-Nemo [Ker+26], the Mysticeti rule under crash faults)* Nemo-Nemo transplants the uncertified-DAG commit rule to the crash-fault setting: `n ≥ 2f + 1` validators of which at most `f` may halt and none equivocates, and every quorum a bare majority. The arc is self-contained: its universe is the block record (§2.3) at a majority validity with every validator honest, and it consumes only the fault-agnostic core — `Block` and the creator sets, the schedule class `Slots`, the anchor comparison (`eq_of_indirect`), the ledger reader `commitSeq`, and the participation predicates of `Common/Participation.lean` (`PopulatedFrom`, `SynchronisedFrom`), stated over a raw block assignment so that the Byzantine and the crash universes instantiate one definition. The extraction records that the arc consumes no clause of §4's trust boundary: its universe carries its own. What it proves: agreement with no fault bound and no side conditions (NN5), a ledger that is agreed and never retracted (NN6), and liveness at the classical crash bound (NN8), under a fairness clause the mechanisation sharpens to _committed leaders at adjacent rounds_ (§15.4). ### 15.1 The model: safety with no fault class The quorum is the strict majority, ```lean def majority (Validator : Type*) [Fintype Validator] : ℕ := Fintype.card Validator / 2 + 1 ``` and the arc's whole counting core is **NN1**: two majorities intersect, the shared intersection lemma (`exists_mem_inter_notMem`) with nothing excluded. Where the Byzantine core must find a _correct_ member in the overlap, and the hybrid arc an _honest_ one (H1), here any member serves — which is the crash model itself, stated as the universe's non-equivocation clause with no correctness guard: one block per author per round, for every author. Crashing needs no behavioural clause for the reason §14.1 gives — a crash is absence — and under crash the simplification reaches further than it does there: no fault set exists on the safety side at all. Validity is two clauses, ```lean structure ValidWrt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round quorum : 0 < b.round → majority Validator ≤ (creators blk b).card ``` exactly the pair the protocol's block verifier enforces. The base model's remaining clauses are not weakened but _derived_: distinct creators among a block's references follow from non-equivocation one round below (`eq_of_mem_refs_of_creator_eq`), and no self-parent clause is imposed — the verifier imposes none, and no crash proof consumes one. ### 15.2 The rules at wave length two At wave length two the voting round and the decision round coincide, and the certificate collapses onto the vote: a certificate for a leader block `L` at round `r` is a round-`(r + 1)` block that references it. The rules are two definitions: ```lean def DirectCommit (U : Universe Validator BlockId Payload) (L : BlockId) (r : ℕ) : Prop := majority Validator ≤ (supporters U L (r + 1)).card ``` ```lean def CertifiedIn (U : Universe Validator BlockId Payload) (A L : BlockId) (r : ℕ) : Prop := ∃ p ∈ history U A, (U.block p).round = r + 1 ∧ L ∈ (U.block p).refs ``` The indirect test is the two-round arcs' in-cone support (O2's and H4's object) at link size **one**: a single anchor-visible vote suffices, because a vote's author is honest and twins do not exist, so the counted `Finset` degenerates to an existential — stated over the finite cone `history`, which makes it decidable. The hitting lemma **NN2** (the record's `exists_mem_refs_of_honest_support_of_card`) meets a majority of backers against a valid block's majority of parents, and link integrity **NN3** (`certifiedIn_of_directCommit`) carries a direct commit into the cone of every block two or more rounds above, by the generic propagation lemma rather than a bespoke descent. ### 15.3 Agreement, without hypotheses The decision relation is the shared one at Nemo's data: wave one, the certificate as the rung, no tie, and no direct skip at all (`Skip` is `False`) — a crash-only committee has no leader to blame, and the indirect skip does the work. Two clauses present elsewhere are absent here. There is no tie and no order on block ids: a slot has at most one candidate before commitment is even asked (**NN4**, `isLeaderBlock_unique_of_honest`), non-equivocation applied at the slot's own round, so the tie-break O5 and H6 require has nothing to select among. And the laws carry no side condition at all: **NN5.** ```lean theorem nemoLaws : (nemoAnchored Validator BlockId Payload).Laws ``` Every commit-against-commit law closes by NN4 with no counting; the crossing `commit_link` is the visibility lemma — the one place in the agreement argument that consumes the quorum — and the skip laws are vacuous. Agreement is the relation's (`NemoProperties.agree`). The ledger layer restates M7–M9 over the crash relation (**NN6**: `Nemo.commitSeq_agree`, `Nemo.ledgerSet_agree`, `Nemo.outputAt_agree`): two views output identical ledgers, and a block, once written, stays written, in the same place. ### 15.4 Liveness: the bound, and the two adjacent commits The fault bound enters the arc here, and nowhere else: ```lean class CrashFaults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where f : ℕ crashed : Finset Validator card_crashed : crashed.card ≤ f card_validators : 2 * f + 1 ≤ Fintype.card Validator ``` `Live` is the complement of the crashed set, and one inequality — `majority_le_card_live`, from `n − f ≥ n/2 + 1` — is the whole of what the bound contributes. The chain is §14.4's at the majority: a reliable-led slot commits from two populated rounds and one coverage step, **NN7.** ```lean theorem decided_of_leader_mem (hcard : majority Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ Decided U V s (some L) ``` and the composed statement **NN8** (`all_decided_below_of_fairRun`, with `all_decided_below_of_fairRun_live` at `T := Live`) settles every slot below a recurring run of **two consecutive** reliable-led slots. The run length is forced, not convenient. Because a crashed validator cannot be distinguished from a slow one, the indirect rule may not settle a slot past an undecided one — the nearest-anchor premise that agreement itself stands on — and an anchor must sit two rounds above the slot it settles. A committed leader at round `r` therefore settles nothing at round `r − 1`: that slot's anchor must lie at `r + 1` or above. For a commit set with no two members at adjacent rounds, the decided slots are exactly the commits and their round-minus-two neighbours — commits at every even round, both pipeline offsets committing for ever, settle no odd slot — while two commits at adjacent rounds settle everything below them: the upper one anchors the round below the lower with no eligible slot between, and the round below the upper _is_ the lower. Round-robin at the tight committee satisfies the clause by counting: `f + 1` live validators cannot be pairwise non-adjacent on a cycle of `2f + 1`. ### 15.5 The witness `Unemo` (NN9, §24) is the arc on data: three validators at the tight committee, fourteen blocks, validator `2` authoring rounds 0–1 and then halting, the live pair carrying the DAG to round 5 with the parent quorum at exactly `majority` from round 3 on. Slots 0, 1, 3 and 4 commit directly by `decide`; slot 2, whose leader left no candidate, is settled by `indirectSkip` anchored on slot 4 — slot 3 is not eligible for it at wave length two, so the intermediate premise is vacuous — and slot 5, with no anchor above it, is claimed by nothing. `Populated` holds to the horizon and fails past it; `FairRunOn` at run length two is proved for the round-robin schedule — the counting argument of §15.4, on data — and the composed theorem is instantiated at the concrete schedule. The DAG is the protocol's good-case transcript with one crash, and the decision procedure of [Ker+26]'s implementation computes the same five verdicts on it. --- ## 16. Integration: composing the arcs through the properties *(modules `LeanDag/Properties/`, `LeanDag/Timed/`, `LeanDag/Integration/`; the design record is `target-properties.md`, whose opening part is the current statement)* Each arc of §§7–15 is built additively, consuming the core read-only and modifying no other. That discipline secures independence and leaves a question: **do the arcs compose with each other?** A validator that garbage-collects below a horizon, recovers from a crash by Safe Skip, runs an adaptive leader schedule and tolerates hybrid faults is running four mechanisms at once, and nothing above says the four are jointly consistent. The answer is one interface. §16.1 states a small set of **properties of a commit rule** — a band the verdicts read, agreement, candidacy, the indirect rule, and a *support* saying what a commit counts — against which every mechanism is proved once, so that a rule showing them inherits every mechanism and the mechanisms compose through the same properties; two headline theorems say what a rule gets, safety across any stack of mechanisms and liveness with certification as the only antecedent. §16.2 is the matrix: nine rules, every mechanism. The remaining sections are facts about the mechanisms themselves that no property states, because they are not about verdicts. Coverage is **refuted** under the Safe Skip fill, with an exact boundary and for the same reason the fill is safe (I4, §16.3). *Placement conditions* say where a garbage-collection horizon may be put (I5, I6, §16.4). A validator pruned past its own history can be restored by a provision costing no exemption from P3′ and no agreement on the cut (I10–I12, §16.5). And §8's storage account is sharpened twice over: its reference discipline is stated more tightly than its own bound needs (I17), and a fill drawn against a common-core target carries no material its recipients lack (I19, §16.6–§16.7). ### 16.1 The properties: one interface for every rule and every mechanism **The carrier.** A mechanism reads a protocol through `Properties.DagRule`: a universe type, a view type over it, projections `block` and `ids` into the shared `Block` vocabulary, the ids a view holds, and the decision relation. Three laws come with the record — views hold universe blocks, views are closed under references, and a universe is a block DAG (`causal`) — because every universe type in the development carries them already. Barnacle's `BaseRule` extends it and each instantiation names the protocol's carrier, so there is one carrier per rule. **Four properties.** `Banded`: every verdict reads a finite band of rounds, so any universe carrying the band up to a shift, any view holding it, and any schedule matching on it reaches the same verdict. ```lean def Banded (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (k : ℕ) (v : Option BlockId), R.Decided S V k v → ∃ top : ℕ, ∀ (g g' d d' : ℕ) (S' : Slots Validator) (U' : R.Universe) (V' : R.View U') (k' : ℕ), k + d' = k' + d → (∀ m m', m + d' = m' + d → S.slotRound m + g = S'.slotRound m' + g') → (∀ m m', m + d' = m' + d → S.slotRound m ≤ top → S.leader m = S'.leader m') → AgreeBand R U U' (S.slotRound k + g) (top + g) g g' → (∀ b, b ∈ R.viewIds V → S.slotRound k ≤ (R.block U b).round → (R.block U b).round ≤ top → b ∈ R.viewIds V') → R.Decided S' V' k' v ``` `Agree`: two views of one universe under one schedule do not disagree, skips included. `CommitsCandidate`: a commit names the slot's candidate. `Indirect`: an eligible committed anchor with every eligible slot between skipped decides the slot, and the verdict survives reassignment of the other leaders. ```lean def Agree (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V₁ V₂ : R.View U) (k : ℕ) (v₁ v₂ : Option BlockId), R.Decided S V₁ k v₁ → R.Decided S V₂ k v₂ → v₁ = v₂ ``` ```lean def CommitsCandidate (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (k : ℕ) (L : BlockId), R.Decided S V k (some L) → R.IsCandidate S U k L ``` ```lean def Indirect (R : DagRule Validator BlockId Payload) (Elig : (ℕ → ℕ) → ℕ → ℕ → Prop) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (i j : ℕ) (A : BlockId), Elig S.slotRound i j → R.Decided S V j (some A) → (∀ i', i < i' → i' < j → Elig S.slotRound i i' → R.Decided S V i' none) → ∃ v, ∀ S' : Slots Validator, S'.slotRound = S.slotRound → S'.leader i = S.leader i → R.Decided S' V j (some A) → (∀ i', i < i' → i' < j → Elig S.slotRound i i' → R.Decided S' V i' none) → R.Decided S' V i v ``` **The support.** A rule's liveness interface is a `Support` — the wavelength and what certifying is — with two laws: `Local`, certification is unchanged across any rebase above its window, and `Commits`, a reliably-led slot whose every candidate the reliable set certifies commits on a covered view. ```lean structure Support (R : DagRule Validator BlockId Payload) where /-- The wavelength: certifiers sit `wave` rounds above the candidate. -/ wave : ℕ /-- `Certifies U c L`: block `c` certifies candidate `L`. -/ Certifies : R.Universe → BlockId → BlockId → Prop ``` ```lean def Commits (rel : Reliability Validator) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (T : Finset Validator) (k : ℕ), rel.IsQuorum T → (∀ n, S.slotRound k ≤ n → n ≤ S.slotRound k + sp.wave → PopulatedOn R U T n) → (∀ L, R.IsCandidate S U k L → sp.certifiesAt U T (S.slotRound k) L) → CoversUpto R V (S.slotRound k + sp.wave) → S.leader k ∈ T → ∃ L, DecidedBelow R S (k + 1) V k (some L) ``` The precondition every liveness theorem reads is `Support.live`: a quorum, a view caught up to a horizon, production across each wave and **certification of every candidate** of every reliably-led slot in the window. Synchrony is not in it and not among the properties: `LeanDag/Timed/Coverage.lean` holds `SynchronisedOn`, `CoversToward` and the coverage law `OfCoverage`, and one bridge turns a covered, populated window into `live` for a rule that has a synchronous story. ```lean theorem live_of_coverage (sp : Support R) {rel : Reliability Validator} (hcov : OfCoverage sp rel) {U : R.Universe} {T : Finset Validator} (hq : rel.IsQuorum T) {Rnd N : ℕ} (hs : SynchronisedOn R U T Rnd) (hpop : ∀ r, Rnd ≤ r → r ≤ N → Properties.PopulatedOn R U T r) (S : Slots Validator) (V : R.View U) {lo K : ℕ} (hV : CoversUpto R V N) (hRnd : Rnd ≤ S.slotRound lo) (hN : ∀ k, k < K → S.slotRound k + sp.wave ≤ N) : sp.live rel S V T lo K ``` Reactive Mysticeti (§11) reaches `live` from its wait clauses and never touches the bridge; every theorem after `live` is the same for both executions, and `scripts/check-arc-holes.py` fails the build if synchrony is named under `Properties/` again. **Optional and derived.** Five properties are owed only when a mechanism reads them: `CommitsDirect` (Barnacle's health count), `SkipsUnsupported` (the prompt skip), `Quorate` (chain quality's coverage half), `SelfParent` and `NoEquiv` (its inclusion half). Four are derived and no protocol proves them: `Persist` and `LocalTruncate` from `Banded`, `LeaderCommits` from `Commits`, `Descends` from `Indirect`. **The mechanisms.** Each DAG-transforming mechanism delivers one relation between the universe it reads and the one it writes — the same blocks at and above a settling round, at rounds `G` apart, with the same authors and, strictly above, the same references. A cut is it at `R₀ = G` with a rebase of the schedule (`Truncates`); a fill or a re-genesis is it at `G = 0` settling at the top of the gap (`Sustains`); an extension proper is the stronger `Extends`. A `Stack` is a finite sequence, and the composition theorem reads it as one mechanism: ```lean theorem Stack.safe_and_live (hb : Banded R) (ha : Agree R) (sp : Support R) (hloc : sp.Local) (st : Stack R U S U' S' G R₀ d) {V : R.View U} {V' : R.View U'} (hv : ViewAgreeAbove R V V' R₀) : (∀ (k : ℕ) (v : Option BlockId), R₀ ≤ S.slotRound (d + k) → (R.Decided S V (d + k) v ↔ R.Decided S' V' k v)) ∧ (∀ (W : R.View U') (k : ℕ) (w v : Option BlockId), R₀ ≤ S.slotRound (d + k) → R.Decided S' W k w → R.Decided S V (d + k) v → w = v) ∧ (∀ {rel : Reliability Validator} {T : Finset Validator} {lo K : ℕ}, sp.live rel S V T lo K → R₀ ≤ S.slotRound lo → d ≤ lo → lo < K → (∀ N, G ≤ N → CoversUpto R V N → CoversUpto R V' (N - G)) → sp.live rel S' V' T (lo - d) (K - d)) ``` Verdict transport across a cut is `LocalTruncate.of_banded` at the `Truncates` witness; across a fill, `Persist.of_banded` at the `Extends` witness; liveness across either, `Support.live_of_truncates` and `Support.live_of_sustains` from Law 1. The prompt skip is SS3 for every rule that skips — a slot whose candidates are all novel is decided `none` at once, and no view of the fill or of a later extension decides it otherwise (`decided_none_of_novel`, `decided_none_of_novel_agree`). Chain quality's coverage half is `Quorate` with the carrier's causal law; its inclusion half is the self-parent chain: a reliable block reaches its author's next committed leader block, so a schedule fair to each validator commits every reliable block, with no synchrony (`committed_of_correct_block`). **The headlines.** `Properties/Arcs/Headline.lean` states what a rule gets. Safety, from `Banded`, `Agree` and `CommitsCandidate`, across any stack: verdicts above the settling round transport, any view of the composite agrees with any view of the source, a commit is the slot's candidate, no block is committed at two slots; and across an extension read on its own, verdicts agree at every slot. ```lean def Safe (R : DagRule Validator BlockId Payload) : Prop := (∀ {U U' : R.Universe} {S S' : Slots Validator} {G R₀ d : ℕ}, Stack R U S U' S' G R₀ d → ∀ {V : R.View U} {V' : R.View U'}, ViewAgreeAbove R V V' R₀ → (∀ (k : ℕ) (v : Option BlockId), R₀ ≤ S.slotRound (d + k) → (R.Decided S V (d + k) v ↔ R.Decided S' V' k v)) ∧ (∀ (W : R.View U') (k : ℕ) (w v : Option BlockId), R₀ ≤ S.slotRound (d + k) → R.Decided S' W k w → R.Decided S V (d + k) v → w = v) ∧ (∀ (W : R.View U') (k : ℕ) (L : BlockId), R.Decided S' W k (some L) → R.IsCandidate S' U' k L) ∧ (∀ (W : R.View U') (k k' : ℕ) (L : BlockId), R.Decided S' W k (some L) → R.Decided S' W k' (some L) → k = k')) ∧ (∀ {U U' : R.Universe}, Extends R U U' → ∀ (S : Slots Validator) {V : R.View U} {V' W : R.View U'}, R.viewIds V ⊆ R.viewIds V' → ∀ (k : ℕ) (v w : Option BlockId), R.Decided S V k v → R.Decided S W k w → v = w) ``` Liveness, from Law 2, `CommitsCandidate`, `SelfParent` and `NoEquiv`, is `Lives := Progresses ∧ Includes`, one antecedent and the schedule quantified before the execution: every slot below a fair run is decided, a reliably-led slot the execution commits lies past every point, and every block by a reliable author enters the ledger through a slot its author leads. ```lean def Progresses : Prop := ∀ (S : Slots Validator) (c : ℕ), 0 < c → Descends R S c → ∀ (T : Finset Validator), (∀ k, ∃ k', k ≤ k' ∧ ∀ i, i < c → S.leader (k' + i) ∈ T) → (∀ k, ∃ b, k ≤ b ∧ ∀ (U : R.Universe) (V : R.View U), sp.live rel S V T b (b + c) → ∀ i, i < b → ∃ v, DecidedBelow R S (b + c) V i v) ∧ (∀ k, ∃ k', k ≤ k' ∧ S.leader k' ∈ T ∧ ∀ (U : R.Universe) (V : R.View U), sp.live rel S V T k' (k' + 1) → ∃ L, DecidedBelow R S (k' + 1) V k' (some L)) ``` ```lean def Includes : Prop := ∀ (S : Slots Validator) (T : Finset Validator), T ⊆ rel.correct → (∀ v ∈ T, ∀ n, ∃ k, n ≤ k ∧ S.leader k = v) → ∀ (m : ℕ), ∀ v ∈ T, ∃ k', m ≤ S.slotRound k' ∧ S.leader k' = v ∧ ∀ (U : R.Universe) (V : R.View U), sp.live rel S V T k' (k' + 1) → ∃ L, R.Decided S V k' (some L) ∧ ∀ b ∈ R.ids U, (R.block U b).creator = v → (R.block U b).round = m → b ∈ historyFrom (R.block U) L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ Arcs.ledgerSetOf R U g n ``` Every rule instantiates both in one line — `MysticetiProperties.safety` and `MysticetiProperties.liveness` for the core and its reactive execution, and likewise for Odontoceti, Hybrid and Mahi-Mahi; Nemo, FinWhale, Hydrozoan and Optimal-Hydrozoan, whose models carry no self-parent clause at the carrier, show `safety` and `progress`. ### 16.2 Every rule, every mechanism `scripts/audit-conformance.py` and `scripts/audit-mechanisms.py` read the dependency graph and print what each rule shows and which mechanism cells exist. As of this writing: nine carriers over nine rules show the four properties and a support; every cell of cut, fill, re-genesis, adaptive leaders, prompt skip (where the rule skips) and chain quality is an instance, and liveness across each mechanism and across any stack is derived from the rule's support and its witnesses. `audit-bespoke.py` checks the other direction — no mechanism reaches a protocol's verdicts except through the properties — and reports no bespoke links. Black Marlin has no carrier, commits by round with no slot-indexed relation, and is out of scope by decision. **Every cut, fill and re-genesis is one construction.** A rule's universe is the block record (§2.3) at its own validity, and `BlockRecord.chop`, `BlockRecord.fill` and `BlockRecord.addGenesis` are built once against the four facts the predicate owes. The witnesses the properties read are proved once as well: `DagRule.OnRecord` (`Properties/Record.lean`) is a carrier read as records, two maps with the ids and block map agreeing, and `truncates_chop`, `sustains_chop`, `extends_fill`, `sustains_fill`, `extends_addGenesis` and `sustains_addGenesis` hold at any carrier that has one. The core, Nemo, FinWhale and Hydrozoan have the identity maps; Orcaella and Optimal-Hydrozoan read as records under their one further invariant, `HonestNoEquiv` and leader exclusion, each shown to survive the cut, the copy fill and re-genesis once (`Invariant.Mechanised`). The verdict cells are proved once too: `Properties/Arcs/Record.lean` gives transport and agreement across the cut, the fill and re-genesis at any carrier on the record with `Banded` and `Agree`. A rule's mechanism cell is therefore its `OnRecord` instance and nothing else, which `audit-mechanisms.py` reads as the cell; what a rule proves itself is the four facts of its validity and, where its fill adds a self reference, that block's validity. Two facts about the mechanisms sit beside the properties rather than inside them. Orcaella's carrier is a universe with `HonestNoEquiv`, and that invariant survives the cut and the fill (I1, `honestNoEquiv_chop` and `honestNoEquiv_skipFill`, `Integration/Preservation.lean`), which is what lets Orcaella's carrier be transformed at all; the fill's half consumes `hgap`, the crash itself. And the crash-prone validator §14 serves is honest but not correct, so `SkipMsg` pins its anchor by the uniqueness fact `hB1uniq` rather than by `v1 ∈ Correct`, the base model recovering the latter route by `hB1uniq_of_correct` (I9). A halted validator's slot is skipped by L5, whose hypothesis does not say why the leader is absent, and after recovery SS2 restores production with the validator back in the reliable set; no lemma relates the adaptive policy to the hybrid fault model, because the crash class is invisible in verdicts, which is all a policy reads. ### 16.3 Coverage under the fill Coverage behaves in three ways under a fill, and all three follow from one fact about extensions: an old block references only old identifiers (`Extends.old_refs_old`). `Timed/Extension.lean` states the refutation and the preservation once, for every rule; the core reads them at the fill's `Extends` witness, and Hydrozoan's copy fill reads the refutation at its own (`not_synchronisedOn_copyFill_hz`). **It fails for a reliable set containing the recovering validator, at the rounds that validator slept through.** **I4.** ```lean theorem not_synchronisedOn_skipFill (sk : SkipMsg U) {T : Finset Validator} {R k : ℕ} (hv1 : sk.v1 ∈ T) (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) (hk : R ≤ k) {b : BlockId} (hb : b ∈ U.ids) (hbround : (U.block b).round = k + 1) (hbc : (U.block b).creator ∈ T) : ¬ SynchronisedOn sk.skipFill T R ``` The failure needs no hypotheses beyond the ones SS2 itself creates, and its reason is the fact that makes Safe Skip **safe**. SS3 concludes that a filled candidate is always directly skipped *because no old block references a fresh identifier*; coverage asks the opposite, that every reliable block at round `n+1` reference every reliable block at round `n`. One fact, two consequences: the fill can manufacture neither a commit nor coverage. The hypotheses are exhibited satisfiable on `Ucrash` (§24), so the refutation is not vacuous. **It is preserved for any reliable set that excludes the recovering validator** (`synchronisedOn_skipFill_of_notMem`, from `synchronisedOn_of_extends`). The filled blocks are that validator's alone, so a clause quantified over the others never encounters them. **It returns strictly above the fill** (`synchronisedOn_skipFill_above`), for any set, and it survives the cut at a horizon offset (`synchronisedOn_chop`, I2), both from the fill's and the cut's `Sustains` witnesses. The strictness is not slack: at the target round the lower block may still be the last filled one, and the refutation reaches there too. So the fill composes with the liveness account of §6, and what it cannot support is the claim that a validator was covered at rounds during which it was absent — which §12 does not make. §12's claim is that the fill restores *production*, which is the hypothesis liveness consumes and which SS2 supplies. The reading for a deployment is that a recovering validator is outside the covered set for the duration of its gap, consistent with §16.5's account of what it costs to be absent, and an ordinary participant from the round above the fill onward. ### 16.4 Where a horizon may be put Three conditions constrain the placement of a garbage-collection horizon relative to the mechanisms running above it. None is visible from a single arc. **I5 — the joiner's two obligations.** §13's adaptive schedule is a function of the committed verdicts, and §9 prunes verdicts below a horizon; a validator joining from the truncation may not hold what the policy reads. Both halves of the answer are generic (`Adaptive/Joiner.lean`). The schedule half is arithmetic on the relation a cut delivers: rebasing a schedule commutes with installing an assignment shifted past the base slot (`Rebases.slotsOf`), so a cut at the base schedule is a cut at the adaptive one (`Truncates.slotsOf`); at the core the two constructions are definitionally equal and `slotsChop_slotsOf_eq` closes by `rfl`. All the content lies in whether a joiner can *produce* that shifted assignment, which is ```lean def HorizonStable (P : Policy R) (G d : ℕ) (pick' : (U' : R.Universe) → R.View U' → (ℕ → Option BlockId) → ℕ → Validator) : Prop := ∀ (U U' : R.Universe), RebasedAbove R U U' G G → ∀ (V : R.View U) (V' : R.View U') (v : ℕ → Option BlockId) (k : ℕ), pick' U' V' (fun m => v (d + m)) k = P.pick U V v (d + k) ``` Under it a joiner computes exactly the leaders the network is using (`Adaptive.joiner_assign_agree`), so the two run one schedule seen from two origins, and cross-cut agreement at that schedule, `Agree` and `Banded` through `decided_agree_rebased`, gives `joiner_run_decided_agree`: pruning does not split the ledger, even when the schedule is derived from it. The obligation is stated on the policy's *rule* rather than on a `Policy`, because a policy is indexed by its `Slots` instance and a joiner's inhabits a different type; the rule is the part that survives re-indexing. The cut enters only as `RebasedAbove R U U' G G`, the universe half of `Truncates`, since horizon-stability is about what the two validators hold and not about how their slots are numbered. A second obligation is independent of the policy. Horizon-stability aligns leaders, not *epochs*: a joiner's slot `k` is the network's `d + k`, so the numberings correspond only when the base slot is a whole number of epochs (`epochOf_add_of_dvd`), and the example beneath it shows the correspondence failing otherwise. **A garbage-collection base slot must be a multiple of the adaptive epoch width** — without it two validators can agree on who leads every slot and still disagree about which verdicts the policy was entitled to read. **I6 — the lag bounds the recoverable outage.** A `SkipMsg` requires its anchor in the universe, and `chop` retains the anchor exactly when the horizon has not passed the round at which the validator crashed. `anchor_pruned` states the constraint and `chopMsg` shows it is the only one: with the anchor retained the whole message rebases, every field shifted by `−G`. Composed with §9's lag envelope: ```lean theorem outage_bounded_by_lag (sk : SkipMsg U) {Λ : ℕ} (hlag : G + Λ = sk.r) (hr : sk.r0 ≤ sk.r) : G ≤ sk.r0 ↔ sk.r - sk.r0 ≤ Λ ``` > Garbage collection at lag `Λ` supports Safe Skip recovery from > outages of up to `Λ` rounds, and no more. Beyond it the validator's last block is gone, and §16.5 says what happens then. ### 16.5 Re-genesis, and the long outage A validator whose whole history falls below a horizon is worse off than unable to fill. P3′ requires every non-genesis block to reference a block by its own creator, so `no_blocks_of_no_genesis` shows that a validator with no block in a universe's genesis layer can produce nothing in it at all — the self-parent chain walks every block down to genesis, and a severed chain cannot restart. `severed_of_pruned_anchor` applies this to the truncation: **any** attempt to resume is blocked, not merely Safe Skip. The repair is to start a fresh chain *at the cut*, and it needs no exemption from P3′. Truncation rebases the retained layer to round `0`, where P1, P3 and P3′ are guarded by `0 < round` and P2 is vacuous for an empty reference set — which is how `chop`'s own validity proof discharges that layer. A re-genesis block is therefore indistinguishable, to the validity rules, from a block the cut flattened. **I10.** ```lean def addGenesis (U : BlockRecord Validator BlockId Payload P honest) (v : Validator) (g : BlockId) (p : Payload) (hg : g ∉ U.ids) (hsev : ∀ b ∈ U.ids, (U.block b).creator ≠ v) : BlockRecord Validator BlockId Payload P honest where ``` The record's re-genesis inserts `g` with the block `⟨0, v, ∅, p⟩` and looks every old identifier up unchanged; the new block is valid by the predicate's `base`, and the old ones by its `reads`. The non-equivocation obligation needs no further hypothesis: adding a genesis block would normally risk a twin at round `0`, and the absence that stranded the validator is what makes the new block unambiguous. `populatedOn_addGenesis` puts it back in the genesis layer, which is P8's hypothesis at round `0`. **I11 — heterogeneous horizons need no agreement.** A re-genesis block is valid in the truncation and not in the universe it came from: at a positive round of the original, a reference-free block violates P3. It would therefore be acceptable only to validators that have pruned at least as far — which §9 cannot promise, since its horizons are per-validator by design. The resolution is to **derive** the block rather than transmit it: each validator synthesises a genesis for any validator absent from its own retained layer, so nothing is sent and nothing can be rejected. What that needs is that the derivations converge, and they do. A validator's own derived genesis sits at round `0` and is pruned by any further cut, leaving exactly the base a more-truncated validator holds: ```lean theorem regenesis_converges {U : BlockUniverse Validator BlockId Payload} {G₁ G₂ : ℕ} (hG : G₁ < G₂) … : (chop (addGenesis (chop U G₁) v g p hg hsev) (G₂ - G₁)).ids = (chop U G₂).ids ∧ ∀ b ∈ (chop U G₂).ids, (chop (addGenesis (chop U G₁) v g p hg hsev) (G₂ - G₁)).block b = (chop U G₂).block b ``` Both then derive the same genesis from the same base, so §9's central claim — no agreement on the cut anywhere — survives the provision intact. The statements are observational: identifier sets equal, and blocks equal at those identifiers, the two universes differing only on material outside their identifier sets that nothing reads. **I12 — the three mechanisms are complementary.** A long outage uses all of them, in order: **bootstrap** to read, since §9.5's attested base yields a view whose verdicts agree with everyone's (`bootstrap_agree`) but no ability to produce; **re-genesis** to write, restoring the validator to the genesis layer, which is what P3′ was blocking; and **Safe Skip** to catch up, one message denoting every block from the cut to the current round — §12's mechanism doing the job it was built for, at a gap that now begins at the horizon rather than at the crash. The third step needs an anchor and the re-genesis block is one (`recoveryMsg`). The closure is exact: `hsev`, the total absence that licensed re-genesis, is what discharges the anchor's uniqueness clause — a validator with no other block anywhere cannot have a second at that round — and the same absence discharges `hgap`. Nothing extra is assumed. What every party needs for the fill to denote anything is the retained history including the donor's line up to the target, which is `SkipMsg`'s standing requirement and, after garbage collection, exactly the window everyone keeps. Safe Skip's fast path stands alone only while the anchor survives (§16.4's lag bound). Past that the three compose, and Safe Skip's contribution is undiminished: it remains the succinct encoding of the many blocks missing inside the retained window. **I18 — and the interval between the phases is priced.** A severed validator can read once bootstrapped but cannot produce, and the reliable sets liveness quantifies over are defined by production, so it belongs to none of them (`notMem_of_no_blocks`). Since liveness needs a reliable set of quorum size, at most `f` validators may be severed at once (`card_severed_le`). **The horizon lag is therefore a liveness-margin parameter and not only a storage one**: a shorter lag saves storage and lengthens the window in which a returning validator, however honest and however well caught up on the ledger, counts against the fault budget. ### 16.6 The exposure condition under both mechanisms The two recovery mechanisms part company at the exposure condition, and the reason is structural. **I13.** ```lean theorem dosValid_addGenesis (hdos : DoSValid V) : DoSValid (addGenesis V v g p hg hsev) ``` Re-genesis adds a block with **no references**. It cannot cite an exposed author — the clause is vacuous for it — and it enters no other block's cone, since nothing reaches what nothing references. §8's per-cone bound therefore applies to a re-genesised universe unchanged, and the concern that re-genesis severs the chain §8 relies on does not reach the condition: what §8 forbids is *citing* an exposed author. The fill does the reverse. P3′ obliges `fillBlock` to insert a self reference, so the first filled block reaches the anchor and with it the whole of `v1`'s pre-crash history (`history_B1_subset_fill`). Its citations are inherited unchanged from the donor while its cone is strictly larger, and `DoSValid` forbids citing an author exposed *in one's own cone* — so a citation innocuous in the donor's smaller cone can be a violation in the filled block's larger one. So the clause §2.2 records as consumed by neither safety nor liveness is doubly implicated: the self-parent chain is what makes a cone a complete record of its author's acceptances in §8, and the self reference §12 must add is what pushes a cone past what the donor vouched for. The disturbance is nonetheless **local**, which is what makes it addressable. A fill copies a donor block's references, and `DoSValid U` already vouches for those citations in the donor's cone; what the fill adds affects no other block, because an old block's cone contains no filled block — SS3's observation once more. Exposure at an old block is therefore unchanged in both directions (`exposedIn_skipFill_old`), and the condition decomposes: **I14.** ```lean theorem dosValid_skipFill (hdos : DoSValid U) (hnew : ∀ k, sk.r0 < k → k ≤ sk.r → ∀ i ∈ (sk.skipFill.block (sk.fresh k)).refs, ¬ ExposedIn sk.skipFill (sk.fresh k) (sk.skipFill.block i).creator) : DoSValid sk.skipFill ``` The extension satisfies the exposure condition as soon as its own blocks do. That second half is a property of the fill alone, so a recipient establishes it by computing the fill and inspecting it, consulting no identity oracle and nothing beyond the message and its own DAG — enforceable in the sense §4.7 requires, and admissible as a clause of the mechanism rather than an assumption about the network. A fill whose enlarged cone exposes one of the donor's citations fails the check and is refused, rather than accepted and unsound. The check itself reduces to **reachability** in the ordinary case. **I15.** ```lean theorem dosValid_skipFill_of_covered (hdos : DoSValid U) (hcov : ∀ k, sk.r0 < k → k ≤ sk.r → sk.B1 ∈ history U (sk.line k)) (hv1ne : ∀ p ∈ U.ids, ∀ q ∈ U.ids, (U.block p).creator = sk.v1 → (U.block q).creator = sk.v1 → (U.block p).round = (U.block q).round → p = q) : DoSValid sk.skipFill ``` If each donor block already reaches the anchor, the fill's cone adds nothing but `v1`'s own new blocks (`fill_cone_subset`) — and those cannot form an equivocating pair, since they sit at distinct rounds, `hgap` excludes an old `v1` block at any of them, and `hB1uniq` pins the anchor's round. What is left is the donor's own cone, for which `DoSValid U` already vouches. So a recipient verifies one reachability query per gap round against its own DAG, and needs no exposure computation over the extension at all. The covering hypothesis is what a donor line satisfies whenever it referenced `v1`'s last block, which is the ordinary case: `v1` was producing at `r0`. The second hypothesis is forced rather than chosen — `SkipMsg` records only that the anchor is `v1`'s unique block *at its own round*, leaving open that `v1` equivocated before crashing, and the fill's self reference would then cite an exposed author. It is what the base model's correctness and §14's honesty each supply. ### 16.7 Storage: the delivery layer, and what settles it §8.4's budgets range over a `Delivery U` rather than over `U`, so they cannot be *stated* for the fill until it has a delivery structure of its own: a delivery structure is indexed by its universe, and a universe transformer needs a delivery transformer beside it, which garbage collection has in `chopD`. The transformer is smaller than it appears. A `Delivery` records what validators held and accepted **when they built their blocks**, and nobody received the fill at the time: the filled blocks reconstruct what the recovering validator would have produced. `skipFillD` therefore changes nothing, and the one obligation with content is `includes`, which now quantifies over filled blocks and asks that they reference what `v1` accepted below. The hypothesis that discharges it is that `v1` accepted nothing while down — the acceptance-side counterpart of `hgap`, which says as much of production. **I16.** The author-blind budget then transfers at the same constant (`uniformBudget_skipFillD`), with no arithmetic: every accepted block is old, so views and novelty are literally the same finite sets (`viewUpto_skipFillD`). The reference discipline does **not** transfer, and the failure describes the mechanism rather than the transformer (`not_refsAccepted_skipFillD`). `RefsAccepted` is `includes`' converse — a correct validator cites *only* what it accepted — and a filled block cites the donor's blocks, which the recovering validator did not accept, having been down. A retroactive reconstruction cannot satisfy both under a delivery structure that records what actually arrived. The alternative is to model recovery as acceptance *at recovery time*: `v1` obtains the donor's blocks when it rejoins and accepts exactly what its filled blocks cite, whereupon both clauses hold by construction, `accepted_inj` following from the P2 clause `skipFill` already establishes for `fillBlock`. What that model does not concede is the budget — the novelty of the newly accepted blocks becomes a property of the fill, to be checked as in §16.6 rather than inherited. **I17 — and the choice does not affect the budget.** §8.4's `RefsAccepted` attributes a block's cone to *its own author's* view, and the pool argument does not need that. Its component lemmas are already stated at the right generality: novelty is bounded by the gap toward whichever validator's acceptances contain the references (`card_novelty_le_viewGap_add_one`), and that gap is bounded as soon as the validator **has a block at the round** (`card_viewGap_succ_le`) — which a donor line does at every gap round. Composing them at a `w` other than the author gives ```lean theorem card_novelty_le_of_donor {κ R : ℕ} (hbyz : ByzBudget D κ) (hED : EventuallyDelivers D R) (hn : R ≤ n + 1) (hv : v ∈ (Correct : Finset Validator)) (hw : w ∈ (Correct : Finset Validator)) (hb : b ∈ U.ids) (hrefs : (U.block b).refs ⊆ D.accepted w (n + 1)) {c : BlockId} (hc : c ∈ U.ids) (hcc : (U.block c).creator = w) (hcr : (U.block c).round = n + 1) : (novelty U (viewUpto D v (n + 1)) b).card ≤ F.f * κ + 1 ``` So a filled block respects the budget with the **donor** in the role the author would ordinarily play, whether or not the recovering validator ever accepted the material. The modelling question is therefore about which clause of §8.4 one wishes to state, not about whether the storage bound holds: it holds either way. The discipline is stated more tightly than the bound requires, and the fill is the case that shows the difference. **I19 — and choosing the target from the common core settles the rest.** What the storage argument does not address is *availability*: a validator citing blocks it does not hold cannot serve them. Selecting the fill's donor line from the **common core** removes that at its source. §5.2's T3c produces, at every round and under no assumption whatever, a correct-authored block that every block two rounds later reaches (`exists_commonAt`) — so cones nest and its references lie in the causal past of every validator holding a block two rounds up: ```lean theorem fill_refs_available (sk : SkipMsg U) (hcom : ∀ k, sk.r0 < k → k ≤ sk.r → CommonAt U (sk.line k) k) {k : ℕ} (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) {c : BlockId} (hc : c ∈ U.ids) (hcr : (U.block c).round = k + 2) {i : BlockId} (hi : i ∈ (U.block (sk.line k)).refs) : i ∈ history U c ``` Three things follow. **Nothing needs transmitting**: the message names the target and every recipient reconstructs the filled blocks from its own DAG. **The recovering validator holds what it cites**, having the common core like everyone else once bootstrapped, so the tight author-attributed discipline is satisfiable rather than something to weaken. And the choice above stops mattering in practice, both clauses being met. This is a restriction on how a message picks its target, not on the executions the protocol admits: T3c is a counting theorem with no synchrony and no progress hypothesis, so a common target exists at every round of every universe. --- ## 17. Mahi-Mahi: the asynchronous rule, and the clause *(modules `LeanDag/MahiMahi/`; the protocol is Mahi-Mahi [Jov+24], the Mysticeti rule stretched to a wave of four or five rounds with the leader revealed only after the wave)* Mahi-Mahi is the asynchronous member of the family. Its rule is the core's with three changes: the wave has `w` rounds, a candidate proposed at `r` being voted on at `r + w − 2` and decided at `r + w − 1`; a vote is counted through the voting block's causal cone rather than among its direct references; and the leader of a round is named by a common coin reconstructed from the decision round's blocks, so that an adversary scheduling deliveries during the wave does not know whose block to starve. The thresholds, the anchor rule and the indirect decision are unchanged, and the reference implementation runs the rule at `w ∈ {4, 5}` under round-robin, leaving the coin to the paper. The arc asks two questions. Is the rule at wave `w` safe on the unmodified DAG layer — and is `w = 3` the core? And under what hypothesis is it live with **no synchrony assumption**, given that the model has neither an adversary nor a clock in which "revealed late" could be said? The answer to the second is a single clause on the pair (schedule, DAG), stated in §17.3, which the counting of §17.2 makes non-vacuous and which synchrony derives from the core's fairness (§17.4). The arc is laid out under a statement/proof partition (§17.5): definitions and statements are the audited surface, proofs are generated, and a checker enforces the split. ### 17.1 The rule at wave `w`, and safety At `w = 3` a vote is a direct reference and validity's distinct-creator clause makes "a block votes for at most one candidate of a slot" a consequence of validity. At `w ≥ 4` a voting block's cone may hold two twins of a Byzantine leader, reached by different paths, so the vote must be a choice. The implementation chooses the first block of the slot met in a depth-first walk over the block's stored references; this development's references carry no order, so the choice is the least block of the author and round in the cone under a `LinearOrder` on identifiers — hash order, in a deployment: ```lean def Votes (U : BlockUniverse Validator BlockId Payload) (q L : BlockId) : Prop := L ∈ candidatesAt U q (U.block L).creator (U.block L).round ∧ ∀ L' ∈ candidatesAt U q (U.block L).creator (U.block L).round, ¬ L' < L ``` Safety consumes only that the choice is unique per block; which block a shared rule picks is immaterial, and the support order of the implementation is consensus-critical exactly through that uniqueness. A blame is on the slot, as the implementation's `enough_leader_blame` has it: a voting block that supports no twin of the slot's author. The decision relation `MahiMahi.Decided` is the shared one at wave `w − 1` with the wave's certificate as the rung (`mahiMahiAnchored … w`), and agreement is the relation's at its laws (`mahiMahiLaws`, for `2 ≤ w`). **MM1** (`MahiMahi.Safety.holds`) states, for every `w ≥ 3`: a skipped slot has no certificate for any of its candidates (`MahiMahi.certificates_eq_empty_of_directSkip`); two certified candidates of one author and round coincide (`MahiMahi.eq_of_certificates_nonempty`); two views deciding one slot agree (the relation's agreement at `MahiMahi.mahiMahiLaws`); and conservativity at `w = 3` — a derivation of the arc's relation is a derivation of the core's (`MahiMahi.core_decided_of_decided`), and the direct commit predicates coincide on every candidate at its own round. Conservativity holds in one direction, and the reason is a finding about the core: the core's `directSkip` quantifies over the candidates, a quorum of blames *per twin*, whereas the implementation and this arc blame the *slot*. The arc's premise is the stronger, so its derivations are the core's; the converse fails when a leader equivocates and its twins' blaming quorums differ. The core's rule is safe either way — its proofs go through per twin — but it skips in a corner where the implementation leaves the slot undecided. On a slot with at most one candidate the two coincide. ### 17.2 What a wave commits, with no network hypothesis `goodAt U w r` is the set of validators whose round-`r` block is directly committed at wave `w`; `good U w k` is the same at a slot's round. A property of the DAG alone. The counting results bound it from below under the fault model, validity, and population by a reliable quorum `T` at **two** rounds — the one that supplies a common core and the decision round — and nothing else. The common core is the core's own T3c: if any round-`(r+2)` block exists, a correct round-`r` block lies in the cone of every block at every round `≥ r + 2` (`MahiMahi.exists_commonCore`, which is `exists_common_correct_ancestor` carried upward). The paper's Lemma C.12 is the core's T3c. From it, **MM2** (`MahiMahi.Counting.holds`): at every `w ≥ 4` some correct validator's round-`r` block is directly committed — every voting-round block reaches the common core, so every decision-round block certifies it (`MahiMahi.goodNonempty`); at every `w ≥ 5`, n − f ≤ |goodAt U w r ∩ Correct| + |byzantine| (`MahiMahi.goodCard`) — the round-`(r+1)` common core is reached by every voting-round block, and through it each of the `n − f` distinct-creator round-`r` blocks it references; those by correct authors are voted for by everyone. And **MM2b** (`MahiMahi.multiLeader`): with `2f + 1` distinct validators leading slots at a round and `w ≥ 5`, one of those slots is good, for every schedule and with no randomness clause. The `w ≥ 5` count differs from the paper's. Lemma C.13 there counts all `n − f` references of the common core as committable; a reference by an equivocating author is not, since a voting block whose cone also holds a second twin votes for whichever its support rule orders first, and the adversary can expose the other twin to part of the voters so that no twin of that author gathers a quorum. What the argument proves is the bound on the correct references: at `n = 3f + 1` with `f` equivocators, `f + 1` good correct validators, a commit probability of at least `1/3` per wave under a uniform draw rather than `2/3`, and `2f + 1` leader slots for a deterministic commit where Lemma C.15 has `f + 1`. The paper's count holds in any wave whose round-`r` authors do not equivocate, which is the hypothesis under which Cordial Miners states the same `2/3`; the protocol's liveness needs only a constant fraction and is unaffected. The `aim4` witness (§17.5) shows the four-round count tight: an adversary aiming at slot `1` leaves `goodAt = {0, 2, 3}` on four validators, and round-robin names exactly the validator that is not good; at `w = 5` the same DAG has every validator good. ### 17.3 The clause, and liveness under it The model has no adversary and no time, so "the leader is revealed after the wave" cannot be said directly. What can be said is its observable consequence on the pair (schedule, DAG): the leader keeps landing among the committed candidates. ```lean def UnpredictableWithin (U : BlockUniverse Validator BlockId Payload) (w c N : ℕ) : Prop := ∀ k, (mahiMahiAnchored Validator BlockId Payload w).decisionRound (k + c) ≤ N → ∃ k', k ≤ k' ∧ k' < k + c ∧ S.leader k' ∈ good U w k' ``` In words: in every window of `c` consecutive slots below the horizon `N`, the schedule names a validator whose block the DAG actually committed. Compare the core's `FairScheduleOn` and its rated form `FairWithin`: the shape is the same, and the fixed target set `T` has become `good U w k'`, a property of the DAG. Under partial synchrony the schedule need only hit a *correct* validator, because synchrony then guarantees that validator's block commits (L4); under asynchrony nothing about a validator guarantees that, so the hypothesis must relate the schedule to the DAG. No clause on the schedule alone can serve: for every deterministic schedule the adversary starves the named leader's block, which is the aiming pattern of `aim4`. The horizon `N` is in the definition because identifiers form a `Finset`, so `good` is empty past some round and no finite DAG satisfies an unbounded `∀ k`. The clause says nothing about how the leader is chosen, and carries no probability: a uniform draw after the wave lands in `good` with probability at least the bound of MM2, so `c` consecutive misses have probability at most `(1 − p)^c`, and the clause holds with probability one; any mechanism with the same effect qualifies, and the measure stays in prose exactly as GST does for the core. A commit does not decide every slot below it: a slot is decided through its lowest eligible slot, and an undecided slot there blocks it, so commits at rounds `10, 20, 30, …` leave slot `17` undecided forever at `w = 4`. Deciding every slot needs a *run* of consecutive committed slots spanning eligibility — the core's `FairRunOn` and `SpansEligible` under synchrony — and the clause has a run form, `UnpredictableRunWithin`, accordingly. **MM3** (`MahiMahi.Liveness.holds`): a good leader's slot is committed in the full view (`MahiMahi.decided_of_mem_good`); under the single-hit clause every window below the horizon commits a slot; under the run form with a spanning run length every slot below the run is decided (`MahiMahi.allDecidedBelow`, the core's descent transcribed at wave `w`); and every reliable validator commits a candidate certified by every reliable decision-round block on its own view, at the explicit time `max (latest d) gst + delay` (`MahiMahi.localCommit`), the pacing structure's convergence read as eventual delivery — no `gst ≤ R`, no backoff. **MM2′** (`MahiMahi.AgreeUpto.goodAt_eq`) is the measurability fact the informal "revealed in the last round of the wave" rests on: two universes agreeing on the blocks at rounds up to the decision round have the same `good`. On data (§17.5): round-robin satisfies both forms of the clause on the fully connected universe; on the aiming pattern it satisfies the core's `FairScheduleOn Correct` and violates the clause — so the clause is not a consequence of fairness, and Mahi-Mahi under a predictable schedule is not asynchronously live, which is the reason the coin exists; and the single-hit clause holds where the run form fails, so the two are distinct hypotheses. ### 17.4 Partial synchrony, recovered The arc remains usable by the partially synchronous development. **MM5** (`MahiMahi.Synchrony.holds`): under the core's coverage hypothesis `SynchronisedOn` at a slot's round, a reliable leader's block is a committed candidate at `w ≥ 4` (`MahiMahi.good_of_synchronisedOn`) — coverage at **one** round suffices, where L4 needs it at two, since once every reliable round-`(r+1)` block references the candidate every block two rounds up reaches it through its reference quorum; and hence under coverage from the start and population through the horizon the clause is *derived* from `FairWithin` (`MahiMahi.unpredictableWithin_of_synchronisedOn`). The two liveness accounts are one: under synchrony the clause is discharged by fairness, under asynchrony by the coin. ### 17.5 The partition, and the witnesses The arc is laid out as `Model/` (definitions only, theorem-free), `/Statement.lean` (definitions and a `def Statement : Prop`, never a proof), `/Proof.lean` and `Helpers/` (generated, unaudited), with `scripts/check-arc-holes.py` rejecting proof holes anywhere in the arc, proofs in a statement file, and theorems in a model file. The audited surface is the model files, the four statement files and the witness instantiations; every `holds` depends on the three standard axioms. The witnesses (`LeanDagTest/MahiMahi/`) are four validators with one Byzantine: `full4`, six fully connected rounds, on which every predicate is settled by `decide` at `w = 4` and `w = 5` and the `w = 3` predicates agree with the core's; `twin4`, two twins in one voter's cone, where the vote goes to the least and only the least; `aim4`, the aiming pattern, with the common core, `goodAt` at both wave lengths and the statement hypotheses pinned; `multi`, three leaders per round; and the clause witnesses of §17.3. ## 18. Black Marlin: a refutation *(modules `LeanDag/BlackMarlin/`, witness `LeanDagTest/BlackMarlin/Divergence.lean`; the protocol is Black Marlin [Amo+25], a partially synchronous DAG protocol that uses neither reliable broadcast nor a common coin, and elects an anchor in every round)* Black Marlin commits three rounds after proposal at the core's committee `n ≥ 3f + 1`, and without a certificate round. It elects one anchor per round rather than one per wave, and the test it applies to the anchor `B` of round `r` has two clauses: `B` carries support from `n − f` distinct validators at round `r + 1`, and some anchor of round `r + 1` both references `B` and carries support from `n − f` validators at round `r + 2`. **This arc is a refutation, and is scoped to be one.** Definition 1's Agreement does not hold of the protocol as presented: §18.3 exhibits an execution, machine-checked at `n = 4`, `f = 1`, in which two reliable validators output different blocks for one author and round and neither ever outputs the other's. Its Total order fails too, and on blocks of *reliable* authors, for a reason that does not involve the twins at all (§18.4). §18.5 locates both failures in the paper's own argument. **What the arc therefore carries** is the rule (§18.1), the descent and the order it delivers (§18.2) — the machinery the refutation reads — and the two refutations with the execution behind them. What it carried before, and no longer does: the commit rule's own safety results, its liveness above the structural condition of §6, the round rule and its responsiveness, Definition 1's Agreement as a statement, the view-relative order results, and a repair. A scheme this development has refuted is not one to integrate with the target properties of §16, so the arc has no carrier and states none of them; `audit-conformance.py` records it as the one rule with none. **The repair is gone with the rest, and this is what it established.** Making the descent prefer a *supported* anchor closes the execution of §18.3, and every internal claim about it was proved. What was never proved, and is argued not to be provable, is that a validator can run it: support is a quorum over the **universe**, a validator reads a view, and a view carrying a quorum at the round above shares only `n − 2f` authors with an anchor's supporters — `f + 1` at `n = 3f + 1`, short of the `2f + 1` the test wants. Deciding from what is held selects the wrong block; waiting until the quorum is held need never complete. The first loses safety and the second liveness, so no member of that family is deployable, and carrying its development was not worth the weight. It is laid out under the statement/proof partition (§17.5); `docs/black-marlin.md` carries its design rationale. ### 18.1 The rule The DAG layer is the core's, unchanged. The paper's validity predicate is `ValidWrt` (§3.2), and the paper's support function is the core's `supporters`, which counts authors rather than blocks; the paper's side condition on it — that a supporter's block references no second block of the same author and round — is validity's distinct-creator clause and needs no restatement. ```lean def Supported (U : BlockUniverse Validator BlockId Payload) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (supporters U L (r + 1)).card def Committed (U : BlockUniverse Validator BlockId Payload) (L : BlockId) (r : ℕ) : Prop := IsAnchor U r L ∧ Supported U L r ∧ Linked U L r ``` `Linked U L r` is the nonemptiness of `linkers U L r`, the round-`(r+1)` anchors that reference `L` and are themselves supported — a `Finset` of witnesses rather than a bare existential, so that the rule is decidable on a concrete DAG. The rotation is a class with one field, `Rotation.anchor : ℕ → Validator`, rather than the core's `Slots`: the protocol is indexed by rounds, and every clause names round `r + 1` explicitly, which under `Slots` would be a hypothesis `slotRound (k + 1) = slotRound k + 1` carried through every statement. **BM7** reconciles the two — under `Slots.uniformSingle 1` an anchor block of round `r` is a leader block of slot `r`, and conversely. A validator applies the rule to its own DAG, so `SupportedIn`, `LinkedIn` and `CommittedIn` are the same definitions with each block set intersected against a `View`. There is no decision relation: Black Marlin has no skip verdict and no indirect rule, so an anchor the rule does not admit is delivered inside the causal history of a later anchor that it does admit, and the whole of what a validator decides is `CommittedIn`. ### 18.2 The delivered order, and the second job of the link clause `commit(B)` does not deliver `past(B)` in one piece. It descends through the undelivered anchors of `strong(B)`, flushing one `τ`-sorted segment per anchor round from the lowest up (Algorithm 1, L18–L32). That segmentation *is* the delivered order, and it is also what makes two validators' orders agree: a validator that committed at rounds `3` and `5` and one that committed only at `7` flush the same segments, because the second one's descent visits `5`, `4` and `3` on the way down. The record the descent leaves is modelled here, not the recursion that builds it, as §5 models `Decided` rather than an implementation. **BMD1** and **BMD1′** say a tie needs two things at once. The candidates at round `ρ` below a round-`(ρ+1)` block are that block's references, and `distinct_creators` allows one block per author, so a consecutive step has at most one candidate whoever anchors; and at a round whose elected validator is reliable the candidates are a singleton however deep the cone, non-equivocation giving it one block there. So a choice requires a **skipped anchor round** — the elected validator produced nothing, or its block is not referenced — **and** an equivocating anchor at the round the descent then lands on. Either alone leaves the descent determinate, and a skip costs only the propagation below it: BML3's inclusion, BMA3's agreement and the whole of §18.2 are statements about causal history rather than about segment boundaries. **BMD2** and **BMD3** turn that into agreement — two records agreeing at a round agree at the round below, and so throughout any stretch they both descend — and **BMD4** supplies the starting point, two records flushing directly committed anchors at one round flushing the same block by BM1. **BMD5 is the result.** Above a committed anchor sits a **supported** anchor — the link clause says so — and BM2 puts it in the cone of every block from three rounds up, so the round above a committed anchor is never the one skipped. The clause that makes adjacent committed anchors comparable (§18.3) is also what keeps the delivery order determinate around them. It is the same clause doing two jobs, and the second is not in the paper. **BMD6** is then the ledger: nothing output is dropped, records that agree output the same blocks, a block enters at exactly one round, and records that agree concur on which. The last two are Definition 1's Total Order at the granularity of segments; the order *within* a segment is `τ`, which the rule does not constrain and this arc does not model. **BMD3's stretch hypothesis is an artifact of taking the record as given**, and §18.3 removes it by computing the descent instead. What the arc has at this point is Validity for reliable authors (BML3 with BML4), agreement on the delivered **set** between committed anchors (BMA3), and agreement by segment between records that agree (BMD6). None of the three is Definition 1's Agreement or its Total order, both of which are about what validators output; §18.5 and §18.6 refute those. ### 18.3 The descent computed, and BMD3 closed L24 chooses among tied candidates by minimising `|round(A) − round(maxAnchor(strong(A)))|`. That quantity reads the candidate and **its own cone** alone, so every validator evaluates it identically and the descent from a block is a function of that block — the property a canonical choice needs, and one the Odontoceti and Mahi-Mahi arcs had to supply as a premise rather than find in their protocols. `Model/Descent.lean` transcribes `maxAnchor`, the metric and the descent, and defines the record of `commit(B)` as what the descent returns. **BME1** and **BME2** are the choice: an anchor of the universe strictly below `B`, at the highest anchor round its cone reaches, made whenever an anchor lies below. The second states the condition L20 should have guarded — it tests `𝒜 ≠ ∅` where L21–L24 need `maxAnchor(𝒜) ≠ ∅`, so `B′` is undefined when the undelivered remainder holds no anchor at all, which is the one defect in the pseudocode. **BME3** derives what `Flush` assumes: the computed record satisfies `isAnchor`, `step` and `dense`, so BMD6's ledger results apply to it unchanged. **BME4** is the suffix property — below a block the descent visited, a record *is* that block's record — and **BME5** reads agreement off it: two records reaching the same block at a round agree at **every** round below, with no hypothesis about the rounds between. That is BMD3 without its stretch condition, and it holds whether or not the descent skipped an anchor round. Two modelling choices are recorded rather than derived. `𝒟` is dropped, since the delivered set only removes what an earlier descent visited, so a validator's flushes over all its commits are one chain read from its highest commit down. And "break ties deterministically" is read as `≤`-least under a `LinearOrder` on identifiers, which the paper leaves open; nothing depends on which rule it is, only that it is shared and reads the candidate alone. On data, the computed record of the four-round model's round-3 anchor is its four anchors, by `decide`, and it is the record the hand-built flush of §18.2 carries. ### 18.4 The delivered sequence, and what Definition 1 has Definition 1 speaks about `ab-deliver` events — a list, not a set. §18.2 and §18.3 fixed the segment boundaries; this section models the sort `τ` of L26 and the filter of L27, so that what a validator outputs is a list. `TopoSort` asks of `τ` what the paper uses of it: that it is a function of the set, hence shared between validators, and that it respects causality. **BMO1** then checks the model against the algorithm — L26 flushes `τ(past(B) \ 𝒟)` and L30 emits `B`, and since every block of `history U B` is reachable from `B`, a topological sort of `history U B \ 𝒟` puts `B` last of its own accord, so the two are one list. **BMO4** is Definition 1's Integrity: no author-and-round is output twice, which is a property of the filter alone. **BMO7** is its Total order, and follows from **BMO2** — records that agree produce one list, and a single list cannot order a pair two ways. **BMO3** says the list only extends, so nothing output is retracted or reordered. **BMO6** is what lifts the earlier results from membership to events: **BMO5** says some block of a flushed author-and-round is output, and for a *correct* author there is no twin for the filter to prefer, so it is that block. With **BMO9** — a reliable author's block reaches an anchor two rounds up by BML3 and is therefore flushed — Definition 1's Validity holds for reliable authors as a statement about `ab-deliver`. **What these do not give.** BMO7 is Total order for records that *agree*, and BMO4 is Integrity for a single record. Neither says two validators' records agree, and they need not: for an **equivocator** the twin that is output depends on the segmentation, and a descent from a higher anchor takes the round-`ρ` anchor its own chain reaches, which need not be the anchor another validator committed at `ρ`. **BMO8** rules the divergence out wherever the two descents meet at a common block, and §18.5 and §18.6 exhibit an execution where they do not — costing Agreement and Total order respectively. On data, identifiers of the four-round model run downward along references, so sorting by identifier is a topological sort and `TopoSort` is not vacuous. The list delivered there is `[0, 1, 2, 3, 5, 4, 6, 7, 10, 8, 9, 11, 15]`, one sorted segment per anchor round with each anchor last in its own, settled by `decide`. ### 18.5 Agreement, refuted on data Two validators' records need not agree at a round where neither committed directly, and the arc exhibits an execution where two honest validators output **different blocks** of the same author and round, neither ever outputting the other's. That refutes Definition 1's Agreement — and with it Theorem 13 — for the protocol as specified, at `n = 4`, `f = 1`. Four validators with `0` Byzantine, seven rounds, the rotation anchoring rounds `0` to `6` by `3, 3, 0, 1, 2, 3, 1`, and validator `0` equivocating at round `2` with blocks `8` and `12`. Every step is settled by `decide`. ![**Agreement refuted, on data.** The execution of `LeanDagTest/BlackMarlin/Divergence.lean`. Validator `0` is Byzantine and equivocates at round `2`; three round-3 blocks support `8` and the round-3 anchor `14` links it, so the rule commits `8`, while `12` has one supporter and the rule never admits it. The round-4 anchor `19` omits `14` — legal, since a block needs three references of four — so its cone holds no round-3 anchor at all. A validator that missed the round-2 commit and commits `19` instead therefore descends past round `3` and meets both twins at round `2`, where L24's metric prefers `12`: one round from the nearest anchor of its own cone, against two for `8`. Each validator then bars the other's block at the filter of L27. Rounds `5` and `6` carry the support that makes `19` committed and are drawn without highlighting.](figures/black-marlin-divergence.svg) The rule commits `8` and only `8`: three round-3 blocks reference it and the round-3 anchor `14` both references it and carries three supporters, while the twin has one supporter, so BM1 is untouched. The round-4 anchor `19` **omits `14`** — a block needs `n − f = 3` of `4`, so a correct validator that has not received `14` builds without it — and therefore its cone holds no round-3 anchor at all. A descent arriving at `19` skips round `3` and meets both twins at round `2`, where L24's metric prefers `12`: block `8` omits the round-1 anchor from its own references and `12` includes it, so the metric reads `2` against `1`. So `flushRecord Udiv 8 2 = some 8` and `flushRecord Udiv 19 2 = some 12`, and the outputs part with the records: the first validator emits `8` and never `12`, the second emits `12` and never `8`. The second does flush `8` — it lies in the round-4 anchor's cone — and drops it at the filter of L27, that author and round having already gone out. The execution behind the two records asks for nothing beyond asynchrony. One validator runs `delivery(4)` with `17`, `18` and `20` in view, sees `14` supported, and commits `8`. The other lacks those three, so `delivery(4)` finds `14` unsupported and fails; the protocol never retries a round, so that validator commits nothing until round `6`, where `delivery(6)` admits the round-4 anchor and the descent takes `12`. Agreement is a safety property, which the paper states holds during asynchrony as well, and asynchrony is exactly the freedom to delay those three blocks. The DAG is buildable: each block references only blocks of the round beneath it that its author could have held, each honest one carries every block of that round it holds as L46–L48 require, and each carries a quorum of three distinct authors. **Nor can the protocol escape by not filtering.** Both twins are flushed by the second validator — `8` lies in the round-4 anchor's cone — and they carry one author-and-round between them. L27 must drop one, since emitting both would output two blocks for a single `(party, round)`, which Integrity forbids "at most once regardless of `B`". So the execution admits no reading satisfying both: filter, and Agreement fails; do not filter, and Integrity does. **Scope.** This does not contradict the commit rule. The twin is never committed by the rule, and BM1 through BM7 stand; nor is liveness affected. What it refutes is the atomic-broadcast Agreement property, and it locates the defect at the two steps the paper asserts without argument — Lemma 12's "by construction of the delivery function, party `j` must have also committed `B`" and Theorem 13's Agreement clause "therefore party `j` eventually ab-delivers(B, j, r)". It is not a slip in the pseudocode. The recursion is described three times — §4.4's prose, Figure 2's caption and §4.5's account of `maxAnchor` — and none of the three is support-aware, while the commit rule is and considers every twin. Equivocation is excluded only in the paper's complexity section, and there only to size the messages for the communication bound. And which twin loses the tie-break is fixed by the twins' own references, which their Byzantine author writes, so the configuration is chosen rather than met. §4.4 states the property this refutes in the same paragraph — "These conditions prevent honest parties from committing different blocks when the anchor party is Byzantine" — which holds of L16, and of the path that runs when L16 fails does not. A repair is visible — let the descent prefer a *supported* anchor among tied candidates, which anchor uniqueness makes unique — and it is side-condition on the record rather than as a change to the model. The same execution costs the protocol its Total order as well, on blocks that have nothing to do with the equivocation, which is §18.6. ### 18.6 Total order, refuted on blocks of reliable authors §18.5 refutes Agreement, and that refutation turns on which twin each record delivers. The delivered **sequence** fails for a reason that does not involve the twins at all, and the same execution exhibits it. Blocks `5` and `7` are authored by reliable validators, and neither has a twin — each is the only block of its author and round — so L27's filter never examines either. The two records deliver them in opposite orders: ```lean (Udiv.block 5).creator ∈ (Correct : Finset (Fin 4)) ∧ (Udiv.block 7).creator ∈ (Correct : Finset (Fin 4)) ∧ (deliverSeq Udiv vFlushFull divSort 5).idxOf 5 < (deliverSeq Udiv vFlushFull divSort 5).idxOf 7 ∧ (deliverSeq Udiv wFlush divSort 5).idxOf 7 < (deliverSeq Udiv wFlush divSort 5).idxOf 5 ``` What orders them is which segment they fall in. `5` lies in the cone of one twin and `7` in the cone of the other, so each record takes one of them with the twin it committed and the other only later. ![**Total order refuted, on data.** Above, the DAG of `LeanDagTest/BlackMarlin/Divergence.lean`: the equivocator's twins differ in one reference, `8` taking `5` and `12` taking `7`, drawn in colour. Below, the two `ab-deliver` sequences `commitSeq` — Algorithm 1's own recursion — computes from it, grouped by the invocation of `commit` that emitted each block. The first validator commits `8` at `delivery(4)` and `19` at `delivery(6)`; the second fails `delivery(4)` for want of `17`, `18` and `20`, and commits `19` alone, its descent reaching `12` and so `7`. Blocks `5` and `7` are authored by reliable validators and have no twins, so L27's filter never examines either, yet they come out in opposite orders. Block `7` is the anchor of round `1`, so an honest leader's block is a segment boundary for one validator and mid-segment for the other.](figures/black-marlin-order.svg) **Checked against Algorithm 1 itself.** `Flush` records a validator's boundaries as a function of round and `ledgerSeq` reads them off in round order, which is an abstraction: `commit(B)` is invoked afresh at each successful `delivery(r)` and threads `D` across invocations, so a later commit can descend below a boundary an earlier one passed and emit those blocks *after* blocks of a higher round. `Model/Recursion.lean` writes L18–L32 out directly, and the two validators run through it give ```lean vRec = [3, 0, 1, 2, 4, 5, 6, 8, 7, 9, 10, 11, 13, 15, 16, 19] ∧ wRec = [3, 1, 2, 7, 0, 4, 6, 12, 5, 9, 10, 11, 13, 15, 16, 19] ∧ vRec.idxOf 5 < vRec.idxOf 7 ∧ wRec.idxOf 7 < wRec.idxOf 5 ``` so the inversion is a property of the algorithm as written, not of the abstraction. The second validator's `Flush` reproduces its sequence exactly; the first's differs in one position and no more, the round-0 anchor `3` being flushed as its own segment at `delivery(4)` where a record with no round-0 boundary emits it inside the round-2 segment. The pair the order turns on is unaffected, and the witness checks that the two sequences agree once `3` is removed. `commitSeq` filters the anchor `B` as well as the blocks of its segment, which is what the paper's §4.4 requires in prose and what the pseudocode at L30 does not implement. That is the reading under which the refutations above stand, and §18.7 records what the literal one costs. **Where the defect sits.** The equivocation is a necessary condition and not the mechanism, and a controlled comparison separates the two. Hold the DAG fixed — same twins, same references, same everything — and change only the descent: ```lean flushRecord Udiv 8 2 = some 8 ∧ flushRecord Udiv 19 2 = some 12 ``` A support-preferring descent, run over the universe, gives the same equivocation and no divergence; removing the equivocation gives no divergence either. Both are necessary, and only one of them is a component that could have been written differently — but neither alternative is a rule a validator can run, for the reason the chapter opening records. **The commit rule's safety is not what is lost.** BM1 through BM7 stand, and `¬ Supported Udiv 12 2` — the rule never admits `12`, correctly. §4.4 of the paper claims "These conditions prevent honest parties from committing different blocks when the anchor party is Byzantine", and of L16 that is true. The failure is on the path taken when L16 *fails*: the second validator committed nothing at round `2`, and reached `12` by descending from a later commit. **So the protocol carries two notions of segment boundary and they disagree.** The commit rule admits an anchor only with a quorum of support. The descent admits any anchor in the cone, chosen by `maxAnchor` and L24's metric, and consults support nowhere. Where no anchor equivocates the two coincide, because `no_equivocation` leaves one candidate block in the universe — that is BMT1's whole proof. Equivocation is what makes them come apart, and it is then the descent's notion that governs what is delivered. **Three conditions, each necessary.** An equivocating anchor, or BMT1 applies. A *skipped* anchor round — `19` omits `14`, so `coneAnchors Udiv 19 3 = ∅` and the descent lands on the twins instead of stepping through round `3`, where `coneAnchors_subsingleton` would leave one candidate; the link clause forbids this above a *committed* anchor, and `14` is supported but not committed. And a support-blind choice among what it finds. The descent supplies two of the three. Where the equivocation is irreducible is one level further down: telling the twins apart needs support, and support is a fact about the universe that a view need not carry. **This is the more robust of the two failures.** It does not depend on which twin the filter prefers, so no rule for choosing among twins repairs it. What would repair it is a rule that makes the two descents agree, and the chapter opening records why no validator can run one. It also disposes of the reading that relaxes Integrity. Dropping L27, so that a *block* rather than an author-and-round is what may not be output twice, leaves the segmentation untouched, and the segmentation is what differs — the twins then come out in opposite orders as well, turning the Agreement failure into a second Total order failure. ### 18.7 Where the paper's proofs go wrong Every lemma of the paper's §5.1 is a statement about the test at **L16**. What a validator outputs is governed by the recursion of L18–L32, which selects anchors by `maxAnchor` and L24's metric and reads support nowhere. Those are different predicates, and the proofs substitute one for the other at a step that can be named. **Lemmas 2 to 6 are sound**, and this arc reproduces them with no hypothesis beyond `n ≥ 3f + 1`: Lemma 3 is BM1, Lemma 4 is BM3, Lemma 5 is BM2, Lemma 6 is BM5 (§18.2). Lemmas 7 to 10, the timing arguments, are replaced here by the structural condition rather than transcribed (§18.5), and nothing below turns on them. **Lemma 12 is false.** It claims that if honest `i` commits `B` then every honest `j` eventually commits `B`, and its proof ends: > If `B ∈ past(B′)` and `B ≠ B′`, then by construction of the delivery > function, party `j` must have also committed `B`. Two things go wrong there. First, the proof **equivocates on "commit"**. Lemmas 3 and 6, which it invokes, are about blocks satisfying L16; the recursion calls `commit(B′)` on blocks that never satisfied L16, which is the entire purpose of the recursion. The premises are in one sense and the conclusion in the other, and no lemma connects them. Second, the connecting claim is **asserted rather than argued**, and it does not hold: the descent visits one anchor per round, chosen by `maxAnchor` and L24's metric, so `B ∈ past(B′)` gives `j` no reason to descend through `B` rather than through a twin. §18.5's execution is exactly that — `i` commits `8` under L16, `j` commits `19` with `8 ∈ past(19)`, and ```lean flushRecord Udiv 8 2 = some 8 ∧ flushRecord Udiv 19 2 = some 12 ``` so `j` never commits `8` in either sense, and never delivers it. Lemma 11 is also stated unconditionally while its proof invokes Lemma 11, which holds only after GST. **Theorem 13, Agreement**, inherits the break and adds one of its own: > …and thus `B ∈ past(B′)`. Therefore, party `j` eventually > ab-delivers`(B, j, r)`, ensuring agreement. Reachability is not delivery. L27 drops `B` when `j` has already output a block of the same author and round, which is precisely the twin case, so the step omits the filter it needs to survive. **Theorem 13, Total order**, rests on a false premise. It reads — > the order of delivery of non-anchor blocks is determined by the order > in which the anchor blocks are committed (L16) — and that is false: the order is fixed by the anchors the *recursion* flushes, and those include blocks that never satisfied L16. In §18.6's execution one validator's boundary at round `2` is `12`, carrying one supporter, and the other's at round `1` is `7`, carrying two of four; neither is L16-committed, and they are what order `5` and `7` oppositely. The clause's two supporting citations are sound in the published version: Lemma 12 does say that every honest party commits the same set of blocks, and Lemma 6 does say that committed blocks lie in each other's past. The preprint (arXiv:2508.14716) numbers every lemma one lower, so there the citation names a Lemma 12 that does not exist and a Lemma 6 that is the `3∆` round-advance lemma; the DISC 2025 version, which this chapter follows, has both right. **Theorem 13, Integrity**, argues that the delivered set makes `j` output at most once per author and round. L30 sits outside the loop L27 guards, so the anchor `B` is `ab-deliver`ed whatever the delivered set holds, and on the literal pseudocode the first validator of §18.5's execution — having delivered `8`, then descending to `12` — delivers both, breaking Integrity with none of the preceding argument needed. The paper's §4.4 says in prose that the filter applies to every block, and the pseudocode does not implement it. This arc reads the prose, so Integrity holds here and the refutations above are of Agreement and Total order rather than of it. **Lemma 11 is not false so much as addressed to a different protocol.** It computes the probability that the anchors of rounds `r − 2` and `r − 1` are both honest as `(n − t)² / n²`, which treats the rotation as random. `RR(r)` is a deterministic round robin (the paper's §4.5), so an adversary reads the schedule and corrupts the anchors it chooses; there is no distribution to take an expectation over. What is available instead is stronger and needs no probability: at `n ≥ 3f + 1` under round robin, two cyclically adjacent anchors are reliable infinitely often, which is BML5 and a pigeonhole. That yields the recurrence but not the expectation, so the paper's headline of `4.25` rounds of communication in the average case, quoted in its time-complexity section, does not follow from anything proved. **The results of §5, and their status here.** | paper | what it says | status | | --- | --- | --- | | Lemma 2 | honest messages are valid on receipt | no message layer is modelled (§18.4) | | Lemma 3 | two L16-committed blocks of a round are one block | BM1, proved | | Lemma 4 | rounds below the top carry `n − f` authors | BM3, proved | | Lemma 5 | an L16-committed block is in every past two rounds up | BM2, proved | | Lemma 6 | committed anchors form a chain | BM5, proved | | Lemmas 7–10 | round advance and support after GST | replaced by the structural condition (§18.5) | | Lemma 11 | expected rounds to a commit | probability over a deterministic rotation; BML5 gives the recurrence without it | | Lemma 12 | `i` commits `B` iff `j` eventually commits `B` | **false** (§18.5) | | Thm 13, Validity | | holds for reliable authors, BMO9 | | Thm 13, Agreement | | **refuted** (§18.5) | | Thm 13, Integrity | | holds of the filter as the paper's §4.4 reads it; fails on the literal L30 | | Thm 13, Total order | | **refuted** (§18.6) | **What the pattern is.** Nothing in §5.1 is wrong about the commit rule, and the paper's §4.4 claims, correctly, that its conditions "prevent honest parties from committing different blocks when the anchor party is Byzantine", which is true of L16. The proofs go wrong where they carry a conclusion about L16 into a claim about what validators output, and the recursion — which is what determines output — is never given a lemma of its own. ## 19. Minnow: the minimal commit rule, and two defects *(modules `LeanDag/Minnow/`; the protocol is Minnow [KPT26], which proposes commit rules claimed minimal — no safe and live rule commits on less DAG — for eventual synchrony and for asynchrony)* Minnow separates a DAG-based atomic broadcast protocol into a communication component, which builds a round-based DAG, and a **commit rule**, which reads that DAG and returns the sequence of committed vertices. This chapter examines `crs*`, the rule proposed for the eventually synchronous model (the paper's Definition 9), as instantiated by S-Minnow. `crs*` commits a leader vertex `l` when two conditions hold: a **quorum** of `2f + 1` distinct processes point to `l` from the round above, and every leader slot that precedes `l`'s in the sequence `leaders` is **resolved** — some vertex of that slot lies in `l`'s causal past, or is concurrent with `l` and either committed or skipped, where *skipped* means `2f + 1` vertices of the round above carry no edge to it. **One sentence generates both defects.** The second condition resolves an earlier slot when *some vertex of it* lies in the candidate's causal past. That is a test of position, where a commit is a verdict: **resolved is not decided**. The disjunct therefore reaches without discriminating. Where a slot's process equivocates it holds two vertices, either of which may be the one in the way; one twin carries a later leader past the slot while the other is undecided, and the other then acquires its quorum and demands a place before what is already output. That is Safe-Commit, and §3.2 of the paper obtains Total-order and Agreement from Safe-Commit (§19.3). **The clause that does decide a slot cannot reach far enough.** The only verdict `crs*` reaches about a slot reads the single round above it: `2f + 1` processes pointing commits, `2f + 1` not pointing skips. At `n = 3f + 1` those leave a gap — a vertex pointed to by between `f + 1` and `2f` processes is neither — and `2f + 1` is the least threshold the skip clause can safely take, so the gap belongs to the rule's shape rather than to its constants. A Byzantine process that keeps its vertices in the gap is never decided, in any view (§19.4). **What that costs depends on the leader schedule.** An undecided slot does not block for ever, and the reason is the disjunct above: two rounds up the dead vertex lies in every causal past, resolving the slot without deciding it. So a schedule offering two consecutive rounds whose leader slots all belong to correct processes commits at the second of them. Round robin with one leader a round always offers such a pair; round robin with two or more can be denied it, for every `f`, by an adversary choosing which processes to corrupt. So Live-Commit fails for `crs*` paired with a multi-leader round robin rather than for `crs*` alone (§19.5) — and the escape that rescues it is the disjunct that costs safety. **Two readings must be settled before either defect can be stated.** Definition 9 is written twice in a way its own sentences do not support. Its second condition binds a vertex existentially, so at the letter a leader slot holding no vertex resolves nothing and a silent process blocks everything. Its skip clause counts vertices where the quorum clause two lines above counts processes, which would make one vertex committed and skipped at once. Neither is a defect of the rule so much as of its statement; §19.2 settles both, and no finding depends on how they go. Where the paper's own liveness argument — its Lemma 11 — reaches the case that fails, it says "we leave the details to a later version". ### 19.1 What is modelled, and how the model is held to the paper Everything below turns on the model being Minnow's and not a neighbour's, so this section sets out each modelling decision, the sentence of the paper it answers to, and what was checked. **A DAG is what the paper's §2 says it is.** Vertices carry a round, a creator and a set of edges, and ```lean structure ValidHere (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card ``` which is §2's "vertices are valid only if they reference at least `2f + 1` valid vertices issued in the previous round by distinct processes", and nothing more. **In particular there is no self-parent condition.** The rest of this development uses a `ValidWrt` that also requires a non-genesis block to reference a block by its own author. Minnow does not ask for that, and imposing it would not be a harmless strengthening: it would force the equivocating process of §19.4 to point at its own previous vertex, which is one of the pointers the counterexample counts. `Minnow.ValidHere` is therefore defined afresh rather than reused, and `Minnow.Dag` is a separate structure carrying it. **Equivocation is admitted, of faulty processes only**, because that is what §2 admits: "if a faulty process issues two valid vertices in the same round, then correct processes include both in their local DAG but when they issue their vertex for the next round, correct processes will choose (it does not matter how) only one vertex per process to add an edge to". Both halves are modelled. `distinct_creators` above is the second — a vertex points at one vertex per process. The first is `Minnow.Dag`'s `correct_single`, which is the core's `no_equivocation` with its correctness guard intact: a *correct* process follows its algorithm and issues one vertex a round, and only a faulty one may put two in a slot. Dropping the field altogether would admit DAGs the communication component cannot build; what Minnow drops relative to §17 and §18 is validity's self-parent clause, not this. **A slot is a pair, and may be empty.** §2: "a slot is a pair `(p, r)` that identifies a proposal, i.e., a vertex issued by process `p` in round `r` (but there may be no, or many such vertices if `p` is faulty)". `slotBlocks D (p, r)` is the vertices of the DAG with that creator and round, and the definitions below never assume it is a singleton or nonempty. §19.2 is what that assumption would have hidden. **The quorum clause counts processes.** Definition 9's witnesses are "`Ws*[1] is a set Q of 2f + 1 vertices issued by distinct processes`", and the clause is "`Quorum: l is pointed to by all vertices in Q`". So what is counted is distinct processes with a pointing vertex: ```lean def Quorum (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := quorumCard Validator ≤ (pointers D l ((D.block l).round + 1)).card ``` with `pointers D l r` the creators of the round-`r` vertices carrying an edge to `l`. `quorumCard Validator` is `n − f`, which is `2f + 1` at `n = 3f + 1`, and the witnesses check that arithmetic on the model rather than assume it. **The skip clause counts processes, in line with every other quorum.** Definition 9 writes "`Skip: or there are 2f + 1 vertices that do not have an edge to v′ in round r + 1`", but the quorum clause two lines above counts "vertices issued by distinct processes", and §19.2 shows the vertex reading to be unsound. So what is counted is distinct processes, none of whose round-above vertices points at `l`: ```lean def Skipped (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := quorumCard Validator ≤ ((creatorsOf D.block (verticesAt D ((D.block l).round + 1))) \ pointers D l ((D.block l).round + 1)).card ``` The literal reading is kept beside it as `SkippedByVertex`, used only to state what it costs. **The second clause is an existential over the slot's vertices, because that is what is written.** Definition 9: "`there is a vertex v′ in slot s′ in D such that v′ ⇝ ϕ(l) in D, or if ϕ(l) and v′ are concurrent:` …". So one vertex of a slot resolving it is enough, and the model does not quietly universalise: ```lean def CommittedAt (D : Dag Validator BlockId Payload) (L : ℕ → Slot Validator) : ℕ → BlockId → Prop | 0, l => Quorum D l | (k + 1), l => Quorum D l ∧ ∀ j ≤ k, ∃ v ∈ slotBlocks D (L j), Reaches D l v ∨ (Concurrent D l v ∧ (CommittedAt D L j v ∨ Skipped D v)) ``` `Reaches D l v` is `v ⇝ l`, `v` in `l`'s causal past, computed as membership of `l`'s cone; `Concurrent` is §2's "if there is no causal path from a vertex `v` to a vertex `v′` and vice versa". The recursion is on the position in `leaders`, matching §3.1's "a commit rule is a function that … follows the order defined by `leaders` and commits iteratively the leader vertices", and Definition 9's footnote 2, which says the recursive `v′ ∈ crs*(D)` may be written as a predicate over fresh variables in the past of `l`. Recursion on the index is well-founded, so `CommittedAt` is a definition and not an assumption. **What is deliberately not modelled, and why it cannot matter.** Three things are left out. The delivery of non-leader vertices in a committed leader's past; the deterministic sort that orders the output; and Algorithm 3's round advance, timer and message layer. None of them can make a leader commit that the pattern disables, because Definition 4 is explicit: "a leader vertex `l` is committed **if and only if** the pattern `P` is enabled for `l` in `D`". Every negative finding below is of the form "this leader is not committed", so it is a statement about the pattern alone, and the omitted machinery is downstream of it. **The two theorems the counterexamples rest on.** Both are read off Definition 9 and proved, not assumed. `quorum_of_committedAt` — a commit needs a quorum, at every position in `leaders`, since the quorum clause is a conjunct at each. And `not_committedAt_of_dead` — if every vertex of an earlier leader slot lies outside `l`'s causal past, carries no quorum and cannot be skipped, then the second clause is unsatisfiable for `l`. The second is the workhorse, and it is deliberately weak: it concludes nothing about what *is* committed, only that a particular leader is not. **Anti-vacuity.** Each witness checks that its DAG is valid by the rule above, by `decide` and not by construction; that the rounds are as full as claimed; and that the vertices which are *supposed* to be committable are — the correct processes' leader vertices in §19.4 all carry quorums, so what stops them is the second clause and not the first. Where a claim is that some vertex is *not* in a causal past, the corresponding positive — that an earlier one *is* — is checked beside it. **The leader sequence is round robin, not one chosen to suit.** §5: S-Minnow "instantiates the `leaders` function with a deterministic, pre-defined, sequence, e.g., round-robin, of `l` leaders each round". The witnesses of §19.2 to §19.4 take `l = 2` over four processes, so the leaders of round `r` are processes `2r` and `2r + 1` modulo `4`, and the sequence is checked on data. This matters for §19.4: the adversary chooses which processes to send its vertex to, and it is round robin that tells it which two to avoid. §19.5 runs the same DAG at `l = 1`, and the difference between the two is that section's subject. **The committee.** All four witnesses are at `n = 4`, `f = 1`, the smallest committee satisfying the paper's `f < n/3`, with process `0` faulty and the other three correct. ### 19.2 Two ambiguities in Definition 9, and how they must be read Definition 9 is written twice in a way its own sentences do not support. Neither is a defect of the rule, but a formalisation has to choose, and what it chooses fixes what the two sections after this one are about. So both are settled here, and this section closes by checking that neither defect depends on the choice. **The empty slot.** The second condition opens "there is a vertex `v′` in slot `s′` in `D` such that …", and all three ways of resolving the slot — a vertex of it in `l`'s causal past, a vertex of it committed, a vertex of it skipped — sit inside that existential. Taken at the letter, a slot holding **no** vertex satisfies none of them, and a process that falls silent would block every later leader for ever. That is not the reading to take. The skip disjunct does not need a `v′` at all: what it asks is that `2f + 1` vertices of the round above carry no edge *into the slot*, and where the slot is empty every vertex of that round qualifies trivially. Read so — a slot is resolved when some vertex of it lies in `l`'s causal past, or some vertex of it is committed, or `2f + 1` vertices of the round above have no edge into it — a silent leader is skipped at once, which is plainly what the clause exists to do. The count below gives an independent reason to state the skip clause over the slot rather than over a chosen vertex of it. The model implements the letter, because the written form is the safer thing to be held to: `CommittedAt` binds `v` existentially over `slotBlocks`. The choice is recorded rather than buried, and `LeanDagTest/Minnow/Deadlock` carries a DAG in which process `0` issues nothing and five leaders are blocked, so the consequence of the literal reading is on the record. The paper's own Lemma 11 shares the ambiguity silently, reasoning throughout about "a leader vertex `l′′` issued by a faulty process" and never about a slot holding none. **The count.** Definition 9's skip disjunct reads "there are `2f + 1` **vertices** that do not have an edge to `v′`". Two lines above, the quorum clause counts "a set `Q` of `2f + 1` vertices issued by **distinct processes**", and every other quorum in the paper is over processes. The vertex reading is not a matter of taste: it makes the rule unsafe. ![**What the vertex reading costs.** The quorum clause counts processes and the skip clause, at the letter, counts vertices. A faulty process issuing three vertices in one round contributes to the second without giving up its place in the first, and both clauses hold of one vertex at once.](figures/minnow-skip.svg) At `n = 4`, `f = 1`, both thresholds read `2f + 1 = 3`. 1. **Round `0`** carries one vertex per process, and the vertex in question is process `0`'s. 2. **Round `1`** carries six vertices, because process `0` issues **three**. Section 2 of the paper permits exactly this of a faulty process: "if a faulty process issues two valid vertices in the same round, then correct processes include both in their local DAG". Each of the six is valid — three edges, distinct processes, the round below — which is checked by `decide` and not by construction, as is the DAG itself, so no correct process equivocates in it. 3. **Of process `0`'s three vertices, one points** at the round-`0` vertex and two do not. Processes `1` and `2` point to it; process `3` does not. 4. **The quorum clause holds.** The processes with a pointing vertex are `{0, 1, 2}`, three of four: `pointers Dk 0 1 = {0, 1, 2}`, checked. 5. **The skip clause holds too, at the letter.** The vertices with no edge to it are the equivocator's other two and process `3`'s — three vertices, which is `2f + 1`. 6. So `Quorum Dk 0 ∧ SkippedByVertex Dk 0`, checked, and with it `CommittedAt Dk (fun _ => (0, 0)) 0 0 ∧ SkippedByVertex Dk 0`: the rule commits the slot, and the clause that exists to resolve slots the rule cannot commit skips it. The two clauses are read by different processes from different views. One holds the three pointing vertices and commits the slot, placing it in its output; another holds the three non-pointing ones, skips it, and commits the next leader without it. Neither output is a prefix of the other, which is what Safe-Commit forbids. A reading on which the rule is not safe cannot be the reading meant. **So the clause is over processes**, and then the two stop competing: `¬ Skipped Dk 0`, checked, the processes with no pointing vertex being `{3}` alone. `Skipped` in the model is this, and `SkippedByVertex` is kept only to state what the letter costs. **Neither defect depends on either choice.** In §19.4 every slot whose resolution matters holds exactly **one** vertex, and on a singleton slot the two readings of the skip clause coincide: "some vertex of the slot is skipped" and "`2f + 1` vertices have no edge into the slot" are the same sentence. That execution carries no equivocation at all, so the two counts are the same count throughout it. In §19.3 the slot that matters holds **two**, and both counts leave the twin undecided — `¬ Skipped Dpart 0` and `¬ SkippedByVertex Dpart 0`, both checked — while the slot itself is resolved through the causal-past disjunct rather than the skip clause, so no empty slot arises there either. Every finding of this chapter holds on the letter and on the charitable reading alike. ### 19.3 The safety defect: one twin resolves a slot, the other commits it The first disjunct of the second condition resolves an earlier slot when *some* vertex of it lies in the candidate's causal past — "there is a vertex `v′` in slot `s′` in `D` such that `v′ ⇝ ϕ(l)`". Nothing there asks what the rule will decide about that slot. Being in the way is not being committed. Where the slot holds one vertex the difference is immaterial, since the vertex in the way is the one any verdict will be about. Where the slot's process **equivocates** it holds two, and the disjunct is satisfied by whichever of them happens to lie in the way, which need not be the one later committed. So a validator can carry a leader past an earlier slot through one twin while the other twin is still undecided — and then watch the other twin acquire its quorum. ![**Resolved through one twin, committed as the other.** Process `0` equivocates at round `0`, and slot `(0, 0)` is the first leader slot. Three round-1 vertices point to the twin `0`, giving it a quorum in the whole DAG; the fourth is process `1`'s, the round-1 leader, and it points to the twin `4` instead. A view missing one of the three sees `0` undecided — two pointers, one short of a quorum; one non-pointer, two short of a skip — yet resolves the slot through `4` and commits the round-1 leader. The missing vertex then arrives.](figures/minnow-equivocation.svg) **The execution, step by step.** Four processes at `f = 1`, one leader a round by round robin, so the leader of round `r` is process `r` and slot `(0, 0)` precedes slot `(1, 1)`. 1. **Round `0`** carries five vertices, because process `0` issues two: `slotBlocks Dfull (0, 0) = {0, 4}`, checked, and process `0` is the faulty one, so `correct_single` is satisfied. 2. **Round `1`** carries four. Three of them point to the twin `0` and the fourth — process `1`'s, which is the round-1 leader — points to the twin `4`: `pointers Dfull 0 1 = {0, 2, 3}` and `pointers Dfull 4 1 = {1}`, checked. So in the whole DAG `0` carries a quorum and `4` does not, and no two vertices of the slot are both committable. 3. **A validator's view is missing one of the three.** `Dpart` drops vertex `5` and, to stay reference-closed, vertex `12`, the only one that cites it. It is a valid DAG by the paper's own rule, checked, and `Dpart.ids ⊆ Dfull.ids`. 4. **In that view the twin `0` is undecided.** `pointers Dpart 0 1 = {2, 3}` — two, one short of a quorum — and one process does not point at it, two short of a skip. So `¬ Quorum Dpart 0 ∧ ¬ Skipped Dpart 0`, checked, and `¬ SkippedByVertex Dpart 0` beside it. 5. **The slot is resolved all the same.** `Reaches Dpart 6 4` and `¬ Reaches Dpart 6 0`: the round-1 leader has the *other* twin in its causal past, which satisfies the first disjunct. And it carries a quorum of its own, `Quorum Dpart 6`. 6. So `CommittedAt Dpart sfLead 1 6`, checked: **the round-1 leader is committed with an earlier leader slot undecided.** 7. **Then vertex `5` arrives.** `Quorum Dfull 0`, so `CommittedAt Dfull sfLead 0 0` — the earlier leader is committed, and `leaders` puts it before the one already output. **This is Safe-Commit, in the form the paper spells out.** "If `P` is enabled for a leader vertex `l` in `D`, then for all leader `l′ < l` in `leaders` … if `P` is disabled for `l′` in `D` then it is also disabled for `l′` in `D′`". Here `P` is enabled for the round-1 leader in `Dpart`; the round-0 leader is *disabled* there and *enabled* in `Dfull`; and it precedes the other in `leaders`. The witness states exactly that conjunction. And §3.2 of the paper obtains Total-order and Agreement from Safe-Commit, so what fails with it is agreement on the output. **The equivocation is what does it**, and the witness checks the converse: with only one vertex in the slot, that vertex is not in the round-1 leader's causal past, carries no quorum in the partial view and cannot be skipped there, so the leader is blocked and nothing goes wrong. A single-vertex slot is either resolved by the vertex the rule will eventually decide, or not resolved at all. Two vertices break that tie between resolving and deciding. **Where the paper's proof of safety fails.** Lemma 10 — "`crs*` is safe" — ends in a case split. With `D ⊆ D′`, `vz` committed in `D`, and `vk` committed in `D′` and ordered before it, the proof concludes: "This is a contradiction because if `vk ⇝ vz` in `D` then `vk` is also committed by indirect commit in `crs*(D)`, or if they are concurrent then `vk < vz` by `leaders` and `vz` is not committed in `crs*(D)` because `Ps*(vk, D) = false`." Read `vk` as the twin `0`, `vz` as the round-1 leader `6`, `D` as `Dpart` and `D′` as `Dfull`. The execution is in the **second** case, and the case hypothesis holds — `Concurrent Dpart 6 0 ∧ ¬ Quorum Dpart 0`, checked. So the proof concludes that `6` is not committed in `Dpart`, and step 6 above checks that it is. **The step needs a quantifier Definition 9 does not have.** Its inference is `Ps*(vk, D) = false`, therefore `vz` is not committed in `D`, and that holds only if the Non-conflicting-leaders condition, applied by `vz` to `vk`'s slot, is a condition *on `vk`*. It is not. What the condition asks is that "there is a vertex `v′` in slot `s′` in `D` such that `v′ ⇝ ϕ(l)` …" — an existential over the slot's vertices, whose first disjunct is reachability alone, with no quorum and no verdict in it. The proof reads `∃ v′ ∈ s′` as though `s′` held one vertex. Here it holds two, and the other one satisfies the disjunct: `4 ∈ slotBlocks Dpart (0, 0) ∧ 0 ∈ slotBlocks Dpart (0, 0) ∧ Reaches Dpart 6 4`, checked. **The uniqueness the proof does establish is the wrong one.** It opens by observing that "if `Ps*(v, D) = true` for a vertex `v` in a DAG `D`, then there is no other vertex `v′` in any DAG `D′` that is in the same slot as `v` and such that `Ps*(v′, D′) = true`". That is correct — two quorums of `2f + 1` in one slot would share a correct process pointing at both twins — and it holds on the witness: `Quorum Dfull 0 ∧ ¬ Quorum Dfull 4`, checked, so nothing below refutes it. But it is uniqueness of the **committable** vertex of a slot, where the step needs uniqueness of the **resolving** vertex, and the causal-past disjunct does not read commitability. On this execution the two come apart in the worst way, the vertex that resolves the slot being exactly the one that can never carry a quorum: `¬ Quorum Dfull 4 ∧ Reaches Dpart 6 4 ∧ ¬ Reaches Dpart 6 0`, checked. Sharper: a leader slot has two ways to be filled. `Ps*` fills it *directly*, and that is what the opening observation makes unique. Definition 9's last clause fills it *indirectly* — everything in a committed leader's causal past is committed — and indirect commitment carries no uniqueness at all. When `6` commits in `Dpart` the twin `4` occupies slot `(0, 0)`; when `0` acquires its quorum in `Dfull` the twin `0` occupies it. The proof's uniqueness observation never reaches the second mechanism. **Two further steps of the same proof are unsupported.** The first case of the split says that if `vk ⇝ vz` then `vk` "is also committed by indirect commit", but Definition 9's clause commits "all **non-leader** vertices that are in the causal past of a leader vertex `l`", and `vk` is a leader vertex; the phrase §19.4 records as a delivery imprecision is a proof step here. And earlier, "since `vz` is also in the past of `2f + 1` processes in `D` then `vk` must also be in `D` (by the causality of the correct process in common)" does not follow from Minnow's validity, which imposes no self-parent condition (§19.1): a correct process's later vertex need not reference its own earlier one, so its causality carries nothing downward. That step happens to hold on this witness — `0` is in `Dpart` — so nothing here refutes it, but it is unargued rather than merely unstated. Lemma 10 is offered as a proof sketch, and the gap is not one of missing detail. The quantifier the step requires is not the quantifier Definition 9 carries, and closing it means the second condition must **decide** an earlier slot rather than resolve it — which is the escape §19.5 shows liveness to depend on. This is the same defect §18 finds in Black Marlin, where a descent chooses among an equivocator's twins by a test that does not read support. The mechanism is the same: **a slot with two vertices is resolved by whichever one is in the way, and being in the way is not being committed.** ### 19.4 The liveness defect: the dead zone ![**Neither committable nor skippable, every other round.** Process `0` sends each of its vertices to process `1` alone, so exactly two processes point to it: itself and process `1`. Three are needed to commit and three non-pointers to skip, and there are two of each. Round robin then supplies the rest: the other leader of the round is concurrent with the dead slot, and the next round's leaders are the two processes that never received the vertex.](figures/minnow-deadlock.svg) **The arithmetic first.** Let `a` be the number of distinct processes with a round-above vertex pointing at a leader vertex, out of the `n = 3f + 1` processes. The quorum clause needs `a ≥ 2f + 1`. The skip clause needs `2f + 1` non-pointers, so `a ≤ f`. Between them lies `f + 1 ≤ a ≤ 2f`, a window of width `f`, non-empty for every `f ≥ 1`. At `f = 1` it is the single value `a = 2`. A vertex there is neither committable nor skippable, and the count cannot change once the round is full. **The threshold cannot be lowered to close it.** Suppose the skip clause is read over distinct processes — as §19.2 requires — with threshold `s`. For two views not to disagree, no view may skip a vertex another commits. A view that skips holds `s` processes with no pointing vertex, of which at most `f` are faulty, so at least `s − f` *correct* processes have none; a correct process issues one vertex per round, so those have none in any view. A view that commits holds `2f + 1` processes with a pointing vertex, at most `f` faulty, so at least `f + 1` correct processes point. The two sets of correct processes are disjoint and there are `2f + 1` correct processes in all, so a disagreement requires `(s − f) + (f + 1) ≤ 2f + 1`, that is `s ≤ 2f`. So `s = 2f + 1` is the least safe threshold, and the window it leaves is forced. The dead zone is not a constant chosen badly; it is what a rule deciding a slot from the single round above must live with at `n = 3f + 1`. **The execution, step by step.** Four processes, process `0` faulty, six rounds, four vertices a round. The one habit of the faulty process is to send each of its vertices to **process `1` alone**. 1. **Round `r`.** Processes `0` and `1` hold process `0`'s vertex and reference it; processes `2` and `3` never received it and reference the other three. All four vertices of round `r + 1` are valid — three or four edges, distinct processes, the round below. 2. **The count.** `pointers Dm 0 1 = {0, 1}`, and likewise at rounds `3` and `5`: exactly two processes, checked. So `¬ Quorum ∧ ¬ Skipped` for each, checked, and the round is full — `(verticesAt Dm r).card = 4` for every round — so nothing later can move either count. 3. **The round's other leader.** Round robin makes slots `(0, r)` and `(1, r)` the leaders of round `r` for even `r`. The second leader sits in the same round as the dead slot, so there is no causal path between them; the causal-past escape is unavailable, and the slot is neither committed nor skipped. Blocked. 4. **The next round's leaders** are processes `2` and `3` — precisely the two that never received the vertex. It is not in their causal past either, so they are blocked by the same slot. 5. **Two rounds later** the vertex has entered every causal past, through the two round-above vertices that did point to it. That slot is resolved — `Reaches Dm 14 4` is checked, against `¬ Reaches Dm 14 8` for the newer one — but by then process `0` leads again, and its new vertex is dead in the same way. 6. **Nothing commits.** All ten leader positions over rounds `0` to `4` are checked individually. The three that are process `0`'s own vertices fail the quorum clause; the other seven fail the second clause. Every correct process's leader vertex carries a quorum — `Quorum Dm 1 ∧ Quorum Dm 6 ∧ Quorum Dm 7 ∧ Quorum Dm 9 ∧ Quorum Dm 14 ∧ Quorum Dm 15`, checked — so what stops them is the second clause and not the first. **How far a dead slot reaches.** One dead slot does not block everything by itself, and the reason is §19.3's sentence read the other way round: *resolved is not decided*. The first disjunct asks only that some vertex of the slot lie in the candidate's causal past, not that it be committed or skipped. Two pointers carry it there in two rounds, because a vertex references `2f + 1` of the `3f + 1` below it and so cannot miss both. So a dead slot is out of reach for exactly two rounds of leaders: its own — where there is no causal path either way — and the one above, whose leaders under round robin are the two processes that did not point to it. From two rounds up it lies in every causal past and resolves for everyone. On the model: `¬ Reaches Dm 1 0`, `¬ Reaches Dm 6 0` and `¬ Reaches Dm 7 0` against `Reaches Dm 8 0`, `Reaches Dm 9 0` and `Reaches Dm 14 0`, all checked. **One dead slot holds two rounds and no more**, which is why the figure shows the habit repeating — and round robin at `l = 2` over four processes puts the faulty process in a leader slot every other round, which is exactly the cadence that keeps a fresh dead slot within reach of every leader. §19.5 is what happens when the schedule does not supply that cadence. **The gap in the decision sequence is permanent, and by itself harmless.** The slot is undecided in every view, not merely in the full DAG: a partial view sees fewer pointers, so no quorum, and fewer non-pointers, so no skip, and as it grows the counts converge to two and two. So every correct process has the same permanent hole at the same position of `leaders`, and Safe-Commit is content — a leader disabled in one DAG stays disabled in every larger one. What defeats the rule under this schedule is not the hole but its recurrence. One thing the hole does cost belongs with the wording matters of §19.2 rather than with this one. Definition 9 commits "all **non-leader** vertices that are in the causal past of a leader vertex `l` for which `Ps*` is satisfied", and Definition 4 commits a leader vertex "if and only if the pattern `P` is enabled" for it. Taken together, a dead leader vertex is in the causal past of committed vertices and is never committed itself, so the output omits a vertex that others delivered depend on. The phrase surely means every vertex of the past; at the letter the dead slot leaves a hole in the delivered set and not only in the decision sequence. **No timing assumption helps.** Eventual synchrony guarantees that a vertex a *correct* process sends is received; it says nothing about one a faulty process withheld. Nothing in the execution turns on message delay, and the same DAG is available after the global stabilisation time as before it. **What this costs.** The paper's liveness property asks that "for every vertex `v` issued by a correct process `i`, `i` will eventually have a DAG `D` such that `v ∈ cr(D)`". On this DAG under this schedule `cr(D)` is empty: no leader is committed, so no vertex is committed, so no correct process's vertex ever appears in the output. Whether that survives a change of schedule is §19.5. **The paper's argument reaches this case and stops, whatever the schedule is.** Lemma 11 says a faulty leader "is eventually decided (committed or skipped) because either all correct processes will issue a vertex that does not point to `l′` (and thus `l′` is 'skipped') or there will be a vertex issued by a correct process in the causal future of `l′` that satisfies `P*`", and then: "we leave the details to a later version". The dichotomy has a middle: some correct processes point and some do not. That middle is the dead zone. No schedule closes it — the dead slot is decided in no view, however `leaders` runs — so the lemma is refuted independently of everything in §19.5. ### 19.5 The schedule the dead zone needs §19.4's execution is a DAG and a leader sequence, and the finding belongs to the pair. This section separates them: the DAG is held fixed, its Byzantine habit unchanged, and only `leaders` is varied. ![**The same DAG, two leader schedules.** The Byzantine habit does not change: every vertex process `0` issues reaches process `1` alone, and sits in the dead zone at two pointers of four. At `l = 2` the faulty process leads every other round, and a fresh dead slot is always within reach of every leader. At `l = 1` it leads one round in four, so three correct leader rounds follow every dead slot: the first is still blocked by it, and the two after that commit.](figures/minnow-schedule.svg) **A dead slot is out of reach for exactly two rounds.** A vertex in the dead zone has at least `f + 1` processes pointing at it from the round above. A vertex two rounds up references `2f + 1` of the `3f + 1` processes below it, so it misses at most `f`, and `f + 1 > f`: it must reference at least one of the pointers, and so has the dead vertex in its causal past. The same count carries upward, since a vertex at any higher round references `2f + 1` vertices each of which already reaches it. So from two rounds above, the slot is resolved for every candidate — not decided, but resolved, which is all the second condition asks. **Two consecutive correct leader rounds are therefore enough.** Suppose rounds `r` and `r + 1` have every leader slot owned by a correct process, the rounds are full, and the DAG is synchronous across them, so that each vertex references every correct vertex of the round below. Take a leader `l` of round `r + 1`. Every leader slot at a round `s ≤ r − 1` is resolved for it: a vertex of the slot with `f + 1` or more pointers lies in `l`'s causal past by the count above, and one with `f` or fewer has `2f + 1` non-pointers and is skipped. Every leader slot at round `r` holds one vertex by a correct process, which `l` references. Every leader slot at round `r + 1` before `l` is concurrent with it and committed, by induction along the order within the round. And `l` carries a quorum, since the `2f + 1` correct processes of round `r + 2` reference it. So `l` commits. The argument is stated here rather than proved in general; what is machine-checked is the instance below. **On the model, with the DAG of §19.4 unchanged.** `LeanDagTest/Minnow/Fair` takes `Dm` — the same vertices, the same edges, the same withheld vertex — and replaces round robin at `l = 2` with round robin at `l = 1`, offset so the round-1 leader is a process the Byzantine one did *not* send to, which is the case the schedule has to survive to be worth stating. 1. **The slot is still dead.** `pointers Dm 0 1 = {0, 1} ∧ ¬ Quorum Dm 0 ∧ ¬ Skipped Dm 0`, checked, exactly as in §19.4. 2. **The first correct leader after it is still blocked.** Process `2` never received the dead vertex, so `¬ Reaches Dm 6 0`, and the slot is neither committed nor skipped: `¬ CommittedAt Dm mFair 1 6`, by `not_committedAt_of_dead`. 3. **The second commits.** Two rounds up the dead vertex is in every causal past, through process `1`'s round-1 vertex, and the round-1 slot is directly referenced: `Reaches Dm 11 0 ∧ Reaches Dm 11 6 ∧ Quorum Dm 11`, and so `CommittedAt Dm mFair 2 11`, checked. 4. **And so does the next**, `CommittedAt Dm mFair 3 13`, so this is a chain and not one commit. `crs*` delivers a non-empty output on the DAG that §19.4's schedule starves. **Which schedules supply the pair, and which can be denied it.** The question is whether an adversary, choosing which `f` of the `3f + 1` processes to corrupt against a fixed rotation, can arrange that no two consecutive rounds have all-correct leader slots. - **At `l = 1` it cannot**, for any `f`. The `f` faulty processes cut the cycle into at most `f` runs of correct ones, and there are `2f + 1` correct processes, so some run has length at least `⌈(2f + 1) / f⌉ = 3`. Three consecutive correct leaders is more than the two the argument needs. - **At `l ≥ 2` it always can.** Two consecutive rounds span `2l` consecutive positions of the rotation, and corrupting one process every `2l` positions meets every such window; that needs `⌈n / 2l⌉` faulty, and at `n = 3f + 1` with `l ≥ 2` this is `⌈(3f + 1)/4⌉ ≤ f` for every `f ≥ 1`. Exhaustive search over `f ≤ 5` agrees, and names the placements: `{0}` at `f = 1`, `{0, 3}` at `f = 2`, `{0, 2, 6}` at `f = 3`. So the schedule §19.4 uses is not a contrivance — it is round robin as §5 proposes it, at a value of `l` the paper explicitly admits — but the finding is about that pair. At one leader a round, this defect does not arise. **What survives, and what does not.** Three things. The word *outright* does not: `crs*` is not unconditionally starved, and Live-Commit fails for the rule paired with a multi-leader round robin rather than for the rule alone. §19.4's execution stands as it is checked; its reach is what changes. Lemma 11 does not survive either way. It claims each leader slot is eventually decided, and the dead slot is decided in no view under any schedule. What a fair schedule restores is that undecided slots stop blocking — not that they get decided. And the escape runs through the disjunct §19.3 breaks. What resolves the dead slot two rounds up is a vertex that is never committed and never will be. That is the same reading of the same clause which, where the slot holds two vertices, lets one twin resolve a slot the other commits. The rule is live under a fair schedule by exactly the mechanism that costs it safety under an equivocating one, and no reading of Definition 9 separates the two. **What is not checked.** Whether the blocking construction generalises beyond `n = 4`, `f = 1`, `l = 2`. Denying the fairness window is not the same as blocking every leader: the leaders that precede a dead slot within its own round must be blocked by the previous round's dead slot, and that placement is a combinatorial question this arc does not settle. ### 19.6 What these amount to | finding | kind | what it costs | repair | | --- | --- | --- | --- | | the empty slot (§19.2) | wording | every later leader, at the letter | state the skip clause over the slot | | the count (§19.2) | wording | Safe-Commit, at the letter | count processes, as the quorum clause does | | resolving through a twin (§19.3) | defect | Safe-Commit, hence Total-order and Agreement | none stated here | | the dead zone (§19.4, §19.5) | defect | Live-Commit, for a multi-leader round robin | one leader a round, or any schedule offering two consecutive correct leader rounds | The first two are matters of wording, and a phrase repairs each. The last two are not, and both survive every reading of the first two. They fail in opposite directions, and the second condition of Definition 9 is where both live. Its skip clause cannot reach far enough: `2f + 1` is the least threshold it can safely take, so the window `f + 1 ≤ a ≤ 2f` cannot be closed from inside the rule, and a slot in that window is decided in no view, for ever. Its causal-past clause reaches without discriminating: a slot is resolved by whichever of its vertices is in the way, which under equivocation need not be the one the rule will commit. Both are reached by the paper's own arguments, and neither argument carries. Lemma 10 obtains its contradiction from `Ps*(vk, D) = false` where what the rule requires is that *some* vertex of `vk`'s slot resolve it, which under equivocation is a different vertex (§19.3). Lemma 11 obtains a decision from a dichotomy — all correct processes point, or none does — whose middle is the dead zone, and says "we leave the details to a later version" (§19.4). The two are not independent in the way the constructions are. The constructions share nothing — §19.4 uses no equivocation and §19.3 no dead zone — but the clauses do. What rescues liveness where the skip clause fails is the causal-past clause resolving a slot it has not decided, and that is precisely what costs safety where a slot holds twins. `crs*` cannot give up the first without losing liveness under every schedule, or keep it without losing safety under equivocation. What the rule lacks in both cases is a way to decide a slot from evidence *later* than the round immediately above it — a verdict that every validator reaching a given point agrees on, whichever vertices of the slot it happens to hold. That is what the indirect decision rules of §17 supply, and their cost in rounds is what Minnow's minimality argument is trying to avoid. §18 finds the same shape of defect in Black Marlin, by the same mechanism: a slot with two vertices resolved by a test that does not read support. ## 20. FinWhale: the two-round commit rule, and its committee *(modules `LeanDag/FinWhale/`; the protocol is FinWhale [LF26], Mysticeti's rule with a second commit path that decides one round above the leader block, at a committee of `n = 3f + 2p − 1`)* FinWhale adds one rule to Mysticeti. A leader block of round `r` is committed as soon as `n − p` distinct validators reference it from round `r + 1` — one round of votes, where a certificate quorum needs two. The slow path, the skip rule, the anchor and the indirect decision are unchanged, and the two paths feed one relation: a slot is directly committed when either fires. What changes is the committee, which becomes `n = 3f + 2p − 1` with `1 ≤ p ≤ f`, and block validity, which acquires a fourth clause about the leader two rounds below. The arc asks three questions. Is the fast path consistent with the slow one and with the skip rule on a DAG whose leader equivocates? What supplies a fast commit, given that FinWhale's pacemaker is reactive and so has no waiting floor from which reference coverage could be read? And what does a validator running the protocol guarantee, stated with no verdict assignment, view or well-formedness condition in it? §20.3 to §20.5 answer them, §20.6 states the four guarantees, and §20.7 shows that a Mysticeti DAG under the denial-of-service condition of §8 satisfies FinWhale's validity rule and, on the core's reactive schedule, is live — so a deployment of that shape obtains the whole arc. Every statement the paper makes, on the reading taken here, is proved: its Lemmas 2 to 13 and Theorems 14 and 15 on the safety side, Lemmas 18 to 20, 22, 23 and 25 and Theorems 21, 24 and 26 on the liveness side. Two of its proofs do not establish their statements, one argument has to be redirected through a different clause of its own rule, and two of its claims are not established at all. §20.8 gives each with its repair. ### 20.1 The committee, and what `p` is `Params` fixes `p` with `1 ≤ p ≤ f` and `n + 1 = 3f + 2p`, additively so that no truncated subtraction reaches `omega` (`params_arith`). Three quantities follow, and the arc keeps them apart: | Quantity | Value | Where it is used | |:---|:---|:---| | `quorumCard` | `n − f` | block validity: how many parents a block carries | | `spQuorum` | `2f + p` | the slow path: certificates, votes, the skip rule | | `fastCard` | `n − p` | the fast path: votes for a commit one round up | `spQuorum_eq_ceil` checks that `2f + p` is the paper's `⌈(n + f + 1)/2⌉`. The three are ordered `spQuorum ≤ quorumCard ≤ fastCard` (`spQuorum_le_quorumCard`, `quorumCard_le_fastCard`), and neither inequality is strict in general: at `p = 1` the slow-path quorum and the validity quorum coincide, at `p = f` the validity quorum and the fast-path threshold do. Because a fast commit clears the slow-path quorum, every slow-path argument applies to it unchanged instead of being duplicated for it. `p` is a threshold parameter and not a second class of fault. There is one fault set, `Faults.byzantine`, bounded by `f`, and `p` never partitions it. Safety assumes nothing about `p`: FW1 below takes a fast commit and the standing bound `|byzantine| ≤ f`, and holds however many validators actually failed. Only fast-path liveness assumes `|byzantine| ≤ p` (`fastCommit_of_reactive`), and there as a counting step — the correct validators all vote past GST, they number `n − |byzantine|`, and that clears `n − p` exactly when `|byzantine| ≤ p`. Above `p` failures the fast path remains sound and does not fire. So `p` counts missing round-`(r+1)` votes whatever their cause, a crash or a withheld vote or a vote for a conflicting block, the threshold counting distinct authors that did vote and not asking why the rest did not. Setting `p = 1` gives `n = 3f + 1`, the core's committee, where the fast path fires only when every validator but one votes; `p = f` gives `n = 5f − 1`, where it tolerates the `f` failures the slow path does. ### 20.2 Validity, and the clause the fast path requires Mysticeti's validity and FinWhale's each have four clauses and share three of them. The core's fourth asks that a block reference its own author's previous block; FinWhale's asks a condition on the leader two rounds down, and drops the self-parent edge that the core requires and the paper's own block structure has (§20.7). ```lean structure ValidHere (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every edge points to the round immediately below. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- No two edges share a validator. -/ distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j /-- A non-genesis block carries `n − f` edges by distinct validators. -/ quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card /-- **FinWhale's clause**: `Clause.leaderExcluded` of the common layer. -/ leader_clause : Clause.leaderExcluded blk b ``` `Clause.leaderExcluded` (§2.4) is the fourth clause, shared with Optimal-Hydrozoan (§23.7): ```lean def leaderExcluded : Clause Validator BlockId Payload := fun blk b => ∀ v : Validator, (∀ i ∈ b.refs, ∀ j ∈ b.refs, ∀ x ∈ (blk i).refs, ∀ y ∈ (blk j).refs, (blk x).creator = v → (blk y).creator = v → x = y) ∨ (∀ i ∈ b.refs, (blk i).creator ≠ v) ``` The fourth clause is a disjunction at every validator `v`, and its two halves read different things. The first is about what the parents *reference*: no two of their references are distinct blocks of `v`. The second is about who *authored* them: `v`'s own block is not a parent. Stated at every validator rather than at the leader two rounds down, the clause mentions no schedule, which is what lets a `Dag` stand in a band (§2.4); at the leader it is the paper's condition, and it drops at most the `f` visibly equivocating validators' blocks. Writing both halves over authorship, or both over references, breaks the counting of §20.3, which needs the exclusion half to be about authorship and the consistency half about references. As a clause of the validity family (§2.3) it is `leaderClause`, and `ValidHere` is the family at the core's quorum with distinct creators and it. `ExposesEquivocation` names the failure of the first half in the form the counting takes: two parents of `b` vote for two conflicting blocks of the leader of round `(D.block b).round - 2`. The paper glosses the same condition twice, as a property of the parent set and as "the causal history contains multiple conflicting versions of the leader's block". At this depth the two agree, and the arc records why: the parents sit at round `r + 1` and their references at round `r`, so the round-`r` blocks in the causal history are exactly what the parents vote for, and `distinct_creators` gives each parent at most one of them. The parent-set form is the one validity constrains, so it is the one modelled. Two modelling decisions are recorded rather than assumed. References sit one round below, where the paper's block structure admits edges to earlier rounds as long as `n − f` are immediately below; this is the core's convention throughout the development, it strengthens validity, and every count the commit rules take is over the round immediately above a block, so the dropped edges carry no votes and no certificates. And `FinWhale.Dag` is the block record (§2.3) at `ValidHere`, with non-equivocation asked of the correct validators only, since equivocating blocks are admitted of faulty validators; a view of it is the record's view, and the DAG a rule reads a view as is the record's `View.toRecord`. ### 20.3 The fast path, and Lemma 4 The two thresholds and the evidence between them: ```lean def FastCommit (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := fastCard Validator ≤ (voters D l).card def FPEvidence (D : Dag Validator BlockId Payload) (b l : BlockId) : Prop := if ExposesEquivocationBy D b (D.block l).creator then F.f + P.p ≤ (parentsVoting D b l).card ∧ ∀ l' ∈ (D.ids : Finset BlockId), Conflicting D l l' → (parentsVoting D b l').card + 1 ≤ F.f + P.p else F.f + P.p ≤ (parentsVoting D b l).card + 1 def DirectCommit (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := FastCommit D l ∨ SPCommit D l ``` `voters D l` counts validators, not blocks, so an equivocator that votes twice counts once. The danger the rule creates is asymmetry: a validator seeing `n − p` votes commits at round `r + 1`, while one seeing fewer is still undecided and will read the slot later through an anchor. Safety needs every later reader to be forced to the same block, and the statement that forces it is **FW1**, the paper's Lemma 4: ```lean theorem lemma4 {b l : BlockId} (hb : b ∈ D.ids) (_hl : l ∈ D.ids) (hround : (D.block b).round = (D.block l).round + 2) (hfast : FastCommit D l) : FPEvidence D b l ``` Not some round-`(r+2)` block and not a quorum of them: every one. `Counting.lean` is the arithmetic of each branch. A block that has not seen the equivocation carries `n − f` parents by distinct validators and meets the correct voters — at least `2f + p − 1` of them, by `honest_voters` — in `f + p − 1` validators (`nonequivocating_voters`), which is what that branch asks. A block that has seen it may not reference the equivocating leader, by the second half of the leader clause (`exposed_not_parent`), so at most `f − 1` of its parents are Byzantine (`parents_byzantine_lt`); its correct parents number `n − 2f + 1`, of which at most `p` fail to vote (`honest_nonvoters`), leaving `f + p` voting for the committed block (`equivocating_voters`) and at most `f + p − 1` for any conflicting one (`conflicting_voters_le`). The second bound is where the leader clause is indispensable: a parent voting for a conflicting block either is not among the committed block's `n − p` voters, and there are at most `p` of those, or votes twice and is therefore Byzantine — and the clause has already removed the equivocating leader from the parent set, leaving at most `f − 1` Byzantine parents, so `p + (f − 1)` is the bound. **FW2** (`equivocating_margin`) is the tightness. At `n = 3f + 2p − 1` the equivocating branch reaches exactly the `f + p` the definition demands, and at `n = 3f + 2p − 2` it reaches `f + p − 1`, one short, for every `f` and `p` in range. The committee is the least at which Lemma 4 holds, which is a statement about this protocol rather than about the literature; the paper asserts optimality by citation instead (§20.8). ### 20.4 Safety: exclusion, consistency, and the order Above Lemma 4 the fast path has to be excluded against every competing verdict, and each exclusion is a separate count. **Against another commit of the same slot.** `direct_commit_unique`: two blocks of one slot cannot both be directly committed, by either path, since `voters_of_directCommit` extracts a slow-path quorum of voters from a commit of either kind and Lemma 8 (`lemma8`) meets two such quorums in a correct validator, which votes once. **Against a skip.** The direct skip rule has two conditions — a quorum of round-`(r+1)` validators declining to vote for every block of the slot (`SPSkip`), and a quorum of round-`(r+2)` blocks that are FP-evidence for nothing in the slot (`NonFPEvidence`) — and they need different arguments. The first (`no_skip_of_quorum`) meets voters and non-voters in `f + 1` validators and needs one of them correct, a correct validator's single round-`(r+1)` block either referencing the leader block or not. The second (`no_skip_of_fpEvidence`) counts by author, and distinctness is not enough there: a Byzantine author may write one block of each kind and satisfy both counts. It needs a *correct* author, whose single round-`(r+2)` block cannot be FP-evidence for a block and for nothing at once. That is the one place in the skip argument where correctness rather than distinctness is consumed. Under a fast commit the second condition is unsatisfiable outright (`no_nonFPEvidence_of_fastCommit`), which is Lemma 7's fast-path premise. **Against an indirect commit of something else.** The indirect rule decides a slot from a committed anchor above it, which either reaches an SP-certificate for a block of the slot or reaches a quorum of FP-evidence blocks for one. Both may hold at once for conflicting blocks, and the paper resolves the choice "according to a deterministic rule" without giving one. That is the shape of the defect §18 reports in Black Marlin, and FinWhale escapes it for a reason worth isolating: **the tie-break's input is the anchor's causal history and nothing else**. `IndirectCommit` is a predicate of the anchor, the slot and the candidate, with no view in it, and a validator holding the anchor holds everything the anchor reaches, views being closed under references (`indirect_view_independent`). Two validators committing the same anchor therefore feed the same data to the same rule; Black Marlin's descent failed because its two validators descended from *different* anchors. The pattern arises only where nobody could have decided directly (`no_indirectCommit_of_directCommit`, `no_indirectCommit_of_directSkip`), and in the converse direction a direct commit leaves a trail every anchor from round `r + 3` upward reaches (`indirectCommit_of_directCommit`), so the rule always has a candidate. `reaches_fpEvidence_quorum` supplies that at every height, where the paper's proof justifies it at `r + 3` only; `chooseLeast` is a tie-break satisfying `ChooseSound`, and `indirectCommitOn_iff` restates the rule over a computed `Finset` so a model can settle it by `decide` rather than exhibit a path. **FW3** is FinWhale's laws for the shared decision relation (`finWhaleLaws`, §3.5): the rule is the instance whose direct commit and skip are the two rules on the validator's view, whose one rung is `IndirectCommit`, and whose tie is the identifier order. The exclusions above are the laws — `direct_commit_unique`, `no_directSkip_of_commit`, `indirectCommit_of_directCommit`, `no_indirectCommit_of_directCommit`, `no_indirectCommit_of_directSkip` — each lifted from the view to the universe by a view being a sub-DAG (`directCommit_restrict`), and the skip rule shown to grow with the view (`directSkip_mono`: a candidate a larger view adds is referenced by no block of the smaller one, so the no-evidence blocks' parents all decline to vote for it and none is evidence for it). **FW4**, the paper's Lemma 12, is then the relation's agreement (`FinWhaleProperties.agree`): two verdicts on one slot, from any two views, agree. **The pass is a procedure, and it lands in the relation.** `slotVerdict` decides one slot from the verdicts above it, `passFrom` threads that down from the horizon to slot `0`, and `decOf` is the result, a function of the DAG and the tie-break. `wellFormed_decOf` discharges the five fields of `WellFormed` off one equation (`decOf_eq`), `mem_slotBlocks_of_decOf` that a committed verdict names a block of its slot, and `decOf_of_gt` that nothing above the horizon is decided. `decided_of_wellFormed` is what connects the procedure to the relation: every slot a well-formed assignment decides — the direct rules on the validator's view, the least candidate at the anchor (`chooseLeast`, sound and least by `chooseSound_least` and `chooseLeast_least`) — it decides as the relation does, by a downward induction from the horizon. The paper's maximality argument is the relation's anchor trichotomy, consumed once for every rule. **FW5** is the order. `commitSeq` is a validator's committed leader sequence and `linearise` the delivery order — walk the sequence and after each leader append the blocks of its causal history not yet delivered. Lemma 13 makes two commit sequences prefix-comparable, `theorem14` that a longer leader sequence extends the delivery order rather than revising it, and `theorem15` that no block is delivered twice. The list a leader contributes is `histOf`, its causal history sorted by identifier; the paper asks only for "a deterministic sort", and `mem_histOf` and `nodup_histOf` are all Theorems 15 and 26 read of it. **FW6** is safety with the views and the pass supplied rather than assumed. A view is a reference-closed subset of the universe's blocks and a `Dag` in its own right (`View.toRecord`). Most of the vocabulary does not read the population — `parentsVoting`, `parentSet` and `SPCertificate` are computed from a block's references and are literally the same in a view — and closure carries a block into the view whenever anything in the view votes for it: a view holding a single round-`(r+2)` block holds every block a quorum of round-`(r+1)` validators votes for (`mem_view_of_voters`), that block's `n − f` parents meeting the quorum in `f + p` authors, one of them correct. FP-evidence is view-independent in consequence (`fpEvidence_restrict`). The skip rule is where the directions part: its first condition quantifies over the slot's blocks *as the view holds them*, so a view's direct skip is not a direct skip of the universe and a validator that has seen no block of a slot satisfies it for nothing. The exclusions it takes part in are therefore proved directly (`no_directSkip_of_commit_view`, `no_indirectCommit_of_directSkip_view`), both running through the second condition, whose quorum is what makes the missing block visible. `agreement_of_commits` is the capstone: two validators running the reverse pass on their own views deliver the same sequence, with nothing assumed about their verdicts — each pass lands in the relation, the relation's agreement makes the verdicts agree wherever both are decided, and Lemma 23 makes them decided. ### 20.5 Liveness on the reactive schedule FinWhale creates a round-`r` block when any of three conditions holds: **C1**, the local DAG has the round-`(r−1)` leader's block together with a quorum of voters for the round-`(r−2)` leader, or an SP-skip pattern for it; **C2**, the `2∆` timeout has expired; **C3**, the local DAG has `n − f` round-`r` blocks. The timeout is the fallback and not the rule, so the discipline is `ReactivePace`, whose `deadline` is a ceiling on waiting, rather than `ViewPace`, whose `waits` is a floor. One consequence shapes the rest: **reference coverage is unavailable**. A reactive builder omits whatever had not arrived when its exit fired, so `SynchronisedFrom` is false in general and nothing below uses it. **FW7** (`Creation.lemma18`, `Creation.lemma19`) derives the paper's two delivery lemmas from those conditions rather than assuming their conclusions. `Creation` records which condition created each block, and the three cases are separate arguments. C1 holds the leader's block by its own clause L1 and a quorum of voters by L2, whose other branch is refuted: a quorum declining to vote for a correct leader would have to be Byzantine, and `f < 2f + p`. C2 waited the full timeout, and the drift bound places every reliable block of the round below in hand before the build (`holds_of_timeout`) — the argument coverage would have supplied, made about one block instead of all of them. C3 holds `n − f` blocks of the round being built, which meet the reliable validators in two or more, so one is a reliable validator other than the builder and built strictly earlier; induction on build time makes its block a vote, and a DAG closed under references holds whatever that block cites. What stays assumed of the algorithm is parent selection, `Creation.selects_leader` and `Creation.selects_votes`, which the paper states for C1 and uses for all three. Both are guarded, and the guards are not decoration: a clause obliging a builder to reference every version of the leader's block it holds is satisfiable by no valid DAG, `distinct_creators` admitting one reference per author (`not_refs_conflicting`), so unguarded it would render vacuous exactly the executions the safety half is about. **FW8** is what the two paths then commit. `Creation.lemma20` assembles the certificates — every reliable validator's round-`(r+2)` block certifies a reliable leader's block, and they number `n − f ≥ 2f + p` — and `Creation.theorem21` is the fast path, where at most `p` validators are actually Byzantine, the reliable validators number at least `n − p`, and their votes alone are a fast commit. The same results hold off the schedule's wait clauses directly (`spCommit_of_reactive`, `fastCommit_of_reactive`), which reuses the core's reactive certificate stage unchanged; that reuse rests on an inequality worth naming, that **Mysticeti's certificate is FinWhale's SP-certificate**, the slow-path quorum `2f + p` being no larger than the validity quorum `n − f` (`spCertificate_of_certifies`). **FW9** is Definition 1's latency, which the paper states and does not prove. Theorem 21 establishes that the fast commit exists; the claim is that it happens within two message delays. The reactive schedule is where that can be said, because its exit is not bounded below by the timeout. `fastCommit_latency` gives both at once: under `δ`-propagation past GST the votes are built within `Δ + δ + 2·proc` of round entry — the collapsed spread, one delivery, two processing steps — with the timeout nowhere in the bound. `no_timeout_of_fast` is its companion: where actual delivery undercuts the timeout, the fallback branch is never taken and the round advances at network speed. **FW10** (`lemma22`) is the rotation. Any window of `3f + 3` rounds contains three consecutive rounds with correct leaders. The statement holds at every `p`, by two arguments rather than the paper's one (§20.8): `three_correct_of_roundRobin` is the cyclic half, counting incidences — if every cyclic triple held a Byzantine leader, each Byzantine validator would answer for at most three of the `n` triples, giving `n ≤ 3f` against `3f + 1 ≤ n` — and uses the fault bound alone; `three_correct_window` is the pigeonhole half for `3f + 3 ≤ n`, where the window lies inside one cycle so its leaders are distinct and `f + 1` disjoint triples would need `f + 1` distinct Byzantine validators. **FW11** is termination. `lemma23` is a statement about one DAG and not about time: a slot below a committed triple is decided. Read as "any undecided slot eventually gets decided" it would contradict the finiteness Lemma 12 consumes, since nothing above some `N` is decided in a DAG that stops; growth enters through the hypothesis instead, a larger DAG carrying a triple further up. `committed_triple` supplies the triple from Lemma 22 and the liveness interface, and `all_decided` composes the two, covering the slots before the stabilisation round as well, since only the triple has to sit past it. `CommitsCorrectLeaders` is that interface — every correct-led slot below the horizon carries a slow-path commit *whose certificates are reliable validators' blocks*, named rather than merely existent because a validator's view has to see them — and `commits_of_creation` and `commits_of_reactive` are its two suppliers, with nothing above them learning which one ran. With every slot decided, Lemma 12's agreement becomes equality (`theorem24`), and `theorem26_of_selfParent` closes validity: a correct validator's blocks form a chain under the paper's own block structure (`reaches_of_same_creator`), and round robin names that validator a leader once a cycle (`exists_round_led_by`), so its block reaches a committed leader whatever else a builder chose to reference. Coverage would give this in one round, and coverage is what the reactive schedule does not have. **And a view is a validator's holdings.** `PaceCore.holds` is what a validator has at an instant, and its two store clauses are exactly a view's, so `holdsView` reads the holdings as the record's view and `View.toRecord` makes them a DAG the rules run on. `held_of_pace` is what the network delivers: past GST every reliable block of every round up to the horizon has arrived by one instant, `settled`. Byzantine authors are not covered and no schedule covers them, which is why the interface names its certificates as reliable validators' blocks. `all_decided_of_pass` is the result with nothing about the validator assumed. ### 20.6 What a validator guarantees Everything above is stated over whatever it needs — a verdict assignment and its well-formedness, a view and its closure, a horizon and a bound. That is the right shape for a proof and the wrong shape for a reader. `Run` is the layer that removes it: one execution, with the blocks, the schedule and network that carried them, the rotation, the self-parent edge, and the liveness input of §20.5; the tie-break among an anchor's candidates is `chooseLeast`. Three definitions read a validator off it — `Run.view` is what it holds once the network has delivered, `Run.verdicts` the reverse pass on that view, `Run.delivers` the sequence it outputs — and **FW12** is the four properties in those terms and nothing else. | theorem | statement | |:---|:---| | `Run.agreement` | two correct validators deliver the same sequence | | `Run.totalOrder` | one's sequence is a prefix of the other's, at any two horizons | | `Run.integrity` | no block is delivered twice | | `Run.validity` | a correct validator's block is delivered | Their hypotheses are which validators are correct and how far the horizon reaches: `max k stable + (3f + 5) ≤ liveHorizon`, the window Lemma 22 needs plus the two rounds an anchor sits above, past the round the network stabilised. No verdict assignment, view, well-formedness or finiteness condition appears in any of them. Nothing is proved at this layer that was not proved before — `Run.agreement` is Theorem 24, `Run.totalOrder` Theorem 14 over Lemma 13 with the relation's agreement (`Run.verdicts_agree`), `Run.integrity` Theorem 15, `Run.validity` Theorem 26 — and five short facts sit between them and the machinery: that a validator's holdings are a view, that its verdicts follow the pass, that a committed verdict names a slot block, that nothing above the horizon is decided, and that it holds every reliable block below the horizon. Fast termination is not among the four, because it is not a statement about delivery: Theorem 21 says a commit pattern exists in the DAG and `fastCommit_latency` says when, and both belong with the schedule. ### 20.7 Deploying over a DoS-protected DAG FinWhale's validity rule and Mysticeti's are not ordered: FinWhale adds the leader clause and drops the self-parent edge, which the core requires and the paper's own block structure has. Both differences disappear under the denial-of-service condition of §8. `DoSValid` — a block may not cite an author its own causal history convicts of equivocating — is the leader clause with three of its narrowings removed: it holds of every author, at unbounded depth, and permanently. **FW13** is the implication: ```lean theorem leaderClause_of_dosValid (hdos : DoSValid U) {b : BlockId} (hb : b ∈ U.ids) : Clause.leaderExcluded U.block (U.block b) ``` `Dag.ofDoSValid` builds the DAG from it: three validity clauses are the core's, the fourth is that theorem — now at every validator rather than at the leader, which is what takes the schedule out of the DAG — and non-equivocation is the universe's. The leader schedule is a parameter of the *execution* rather than of the DAG, since the DoS condition does not know which validator leads. Two things follow. The self-parent edge comes with core validity, so `selfParented_ofDoSValid` is a theorem and Theorem 26 loses its one remaining hypothesis. And the DAG and the universe are one object rather than two readings of one, so the `ids_eq` and `block_eq` conditions every liveness result carries hold by `rfl`, and `Run.ofDoSValid` asks for three fewer fields than `Run` does. **FW14** carries that to liveness. `Run.ofDoSValidReactive` takes such a universe together with a `ReactiveM` over it, at one slot per round under a round-robin schedule, and returns a `Run` — so `commits_of_reactive` supplies the liveness input and the four guarantees of §20.6 hold with no further hypothesis. Nothing in it is bounded above: the theorem is universal in the horizon, so every correct-led slot between the stabilisation round and `N` commits, at every `N`, and §20.5's rotation result makes correct-led slots recur. **The two disciplines do not collide, and the reason is specific to this route.** A reactive builder's citation obligations are confined to reliable authors — `ReactivePace.vote_or_wait` is guarded by the leader being reliable, `ReactiveM.cert_or_wait`'s fallback by the author being in `T` — and a correct validator is never exposed (`citable_of_correct`), so nothing the schedule requires is anything the condition forbids. The block-creation discipline of §20.5 has no such guard: `selects_votes` obliges a builder to reference a block by the author of *any* held vote, reliable or not, and that clause and `DoSValid` are not jointly satisfiable where a convicted equivocator votes for a reliable leader. Composing through `commits_of_creation` would need the selection clause guarded and the certificate confined to its witnesses first; through `commits_of_reactive` it needs neither. **And the composite cannot become vacuous.** It is inhabited where equivocation is live, which §20.9 exhibits, and `quorumCard_le_citable` shows why no height can exhaust it: an exposed author has equivocated, so it is Byzantine and one of at most `f`, while the `n − f` correct validators are never exposed — so the authors a block may cite always include a validity quorum. The margin is nil rather than small. At exactly `f` Byzantine validators the citable authors are the correct ones and no others, so every one of them has to be cited, which turns the question from what may be cited into what has arrived — and that is what the pacing structure supplies past GST. What this does not claim: `DoSValid` is strictly stronger than FinWhale specifies, the protocol admitting a DAG that cites an equivocating non-leader. The statement is about a deployment running FinWhale over a DoS-protected DAG, which is the deployment the storage bound of §8 argues for, and not about the paper's model. ### 20.8 What the paper should change No statement of the paper is false on the reading this arc takes. Four of its proofs do not establish their statements, and each fails on a case its argument does not reach; two further claims are made without proof. The table names the case first, because that is what has to be seen before a repair means anything. | finding | kind | the case the argument does not reach | repair | |:---|:---|:---|:---| | Lemma 22's window | proof | every `p ≥ 2`, least at `f = p = 2` | count incidences, or the pigeonhole at `3f + 3 ≤ n` (§20.5) | | the C3 case of Lemmas 18 and 19 | proof | `p = 1`, at every `f` | induct on block-creation time (§20.5) | | Lemmas 6 and 7 through SP-skip | argument | a validator holding no block of the committed slot | run through the Non-FP-evidence quorum (§20.4) | | Lemma 4's round index | wording | none — the count is unaffected | name round `r + 1` | | Definition 1's latency | unproved claim | — | `fastCommit_latency` (§20.5) | | optimality of `n = 3f + 2p − 1` | unproved claim | — | the tightness of Lemma 4 (§20.3) | **What is checked, and what is read.** The repairs are all in the library: Lemma 22 at every `p` (`lemma22`), Lemmas 18 and 19 by induction on build time (`Creation.lemma18`, `Creation.lemma19`), the exclusion routed through the Non-FP-evidence quorum (`no_directSkip_of_commit_view`), the latency (`fastCommit_latency`) and the tightness of Lemma 4 (`equivocating_margin`). So is the arithmetic of each diagnosis — `window_margin` for the window against the cycle, `c3_reachable` for where C3 becomes usable, `c3_margin` for what the extra member of `H` costs — and the case Lemmas 6 and 7 do not reach is exhibited on data (§20.9). What is **not** machine-checked, and cannot be, is that the paper's proofs depend on that arithmetic: that is a reading of their text, given here in enough detail to be checked against the paper directly. **Lemma 22 covers `p = 1` only.** The proof concludes from "a window of `3f + 3` rounds contains a full cycle of `n` rounds plus the first two rounds of the next cycle". *Where it breaks* (`window_margin`). The difference `3f + 3 − n` is `4 − 2p`, and does not involve `f`: the window is a cycle plus two rounds at `p = 1`, exactly one cycle at `p = 2`, and `2p − 4` rounds short of one at `p ≥ 3`. The least broken instance is `f = 2`, `p = 2`, where `n = 9` and the window is `9` rounds — one cycle, where the sentence reads off eleven. At `f = 3`, `p = 3` the window is `12` against a cycle of `14`. *Why the statement survives.* It quantifies over every window of that length, so a window that is a smaller part of the cycle is a stronger claim, not a weaker one. It holds because `f` does not grow with `p`: more validators, the same number of Byzantine ones, so correct leaders are denser. §20.5 gives the two arguments, and `3f + 3 ≤ n` is exactly `p ≥ 2`, so they partition the parameter space with no gap at the boundary and no overlap. **The C3 case of Lemmas 18 and 19 does not close at `p = 1`.** A C3-triggered validator built because it already held `n − f` blocks of the round it was building, so it waited on nothing and never read the leader. The proof recovers the lemma by finding, among those blocks, one whose author did not itself use C3, by pigeonhole against a set `H` of validators that cannot have used it. *Where it breaks* (`c3_reachable`). `H` is one member too large. A validator with `j` honest validators ahead of it holds at most `j + 1 + f` blocks of its own round — `j` from those ahead, its own, and at most `f` from Byzantine authors — so C3's `n − f` becomes reachable as soon as `j ≥ n − 2f − 1`. The validators that certainly cannot use it are the fastest `n − 2f − 1`, where the proof takes `n − 2f`. *That one member is the whole margin* (`c3_margin`). Write `b` for the number of validators that actually fail. A C3-triggered validator holds at least `n − f − b` blocks by honest authors out of `n − b` honest validators, so the intersection with `H` is at least `|H| − f` — independent of `b`, the two counts moving together. At the proof's `|H| = n − 2f` that is `2p − 1`, positive at every `p`; at the correct `|H| = n − 2f − 1` it is `2p − 2`, which is zero at `p = 1`. *The instance.* At `f = 1`, `p = 1`, `n = 4`, C3 asks for three blocks of the round being built. The fastest honest validator holds at most `0 + 1 + 1 = 2` and cannot use C3; the second holds at most `1 + 1 + 1 = 3` and can. So `H` is the single fastest validator where the proof counts two, and the intersection bound is `1 − 1 = 0`: the pigeonhole names no block at all, at the smallest committee the protocol admits. *The repair.* Induction on block-creation time needs no set and no threshold count. A C3-triggered validator holds `n − f` blocks by distinct authors, none of them its own, of which at most `f` are by faulty authors — leaving at least `n − 2f ≥ 2` honest authors, each of which built strictly earlier, so the induction hypothesis applies to one of them. It is uniform in `f`, `p` and `b`, and works at any C3 threshold above `f`. *And note the direction.* This case fails where Lemma 22's proof succeeds and succeeds where that one fails, so no single argument in the paper covers the whole range of `p`. **Lemmas 6 and 7 cannot run through the SP-skip condition.** That condition quantifies over "each leader block of `s` (if any) in the local DAG of `vj`". *Where it breaks* (§20.9 exhibits it). On the case the lemma is about: a validator that has received **no** block of the committed slot. Its SP-skip condition is then satisfied for nothing — there is no block of the slot for a quorum to decline to vote for — so nothing about the committed block follows from it, and the contradiction the proof wants has no block to run on. The parenthesis "(if any)" is what admits the case, and the argument above it assumes the case away. *The repair.* The second condition supplies the missing block. One of the `2f + p` Non-FP-evidence blocks carries `n − f` parents, which meet the committed block's `2f + p` voters in an honest validator whose single round-`(r+1)` block both votes for it and is that parent — so the committed block is in the skipping validator's DAG after all, and the same round-`(r+2)` block is FP-evidence for it by Lemma 4 or Lemma 2. As the proofs stand, a reader is directed to the route that does not work. **Lemma 4's proof names the wrong round.** Its equivocating case says the block "does not reference the `r − 1` block of the Byzantine leader". The parents of a round-`(r+2)` block sit at round `r + 1`, and the block excluded is the leader's round-`(r+1)` one. No case is lost — the count of at most `f − 1` Byzantine parents is what the argument uses, and it is unaffected — but the sentence does not name the block it excludes. **Two claims are not established.** Definition 1's latency is stated and not proved; §20.5 proves it. And the optimality of `n = 3f + 2p − 1` is asserted by citation, where a tightness result is available and is the paper's own (§20.3). **What the proofs use without saying.** Five gaps of a different kind: not proofs that fail on a case, but steps whose premises are never stated. Each is given with the case that exposes it. - **Parent selection is specified for C1 only.** Lemmas 18 and 19 both need "what is held is selected", and the case that needs it is a block created under C2 or C3 — which is the case those lemmas exist for. It belongs in the protocol description, as a property of the selection algorithm subject to leader-consistency. - **Two properties of the network are assumed.** That a block enters a DAG only after its creator made it, which any timing argument needs; and that honest validators do not create one round simultaneously, which only the `H` counting needs and which the induction above dispenses with. - **A1′ is stated where it cannot be used.** The case it addresses — that excluding the leader's block may leave `n − f − 1` parents — is a constraint on block validity, and every count in the safety proof rests on block validity; but A1′ appears only in the parent-selection prose. §20.7 shows the corner closes by counting under the denial-of-service condition. - **Lemma 12 presupposes a maximum.** "The latest leader slot for which such inconsistent decisions were made" needs the decided slots to be finite. The case is a DAG with no bound: without finiteness there is no latest slot, and the induction has no base to run from. - **Lemma 23's "eventually" does not survive a literal reading.** The case is a fixed DAG: nothing above its reach is decided in it, so eventual decision cannot be a statement about one DAG. §20.5 states the per-DAG form, with growth moved into the hypothesis. Two things are worth adding rather than repairing. The deterministic tie-break among an anchor's candidates is safe here, and §20.4 says why — the rule reads the anchor's causal history and no view — which is worth stating in the paper, since the failure mode is live in a neighbouring protocol (§18). And Lemmas 16 and 17 are the exception in the other direction: they are neither confirmed nor contradicted here, because timeouts and message delivery are not modelled, and `PaceCore` stands in their place. ### 20.9 The partition, and the witnesses `LeanDag/FinWhale/Model/` holds every definition of the protocol and nothing else, in thirteen files: the committee and the thresholds, block validity and the evidence the fast path counts, the two halves of the skip rule, the direct verdicts, the indirect rule, verdicts and the tie-break, the reverse pass as a procedure, the committed sequence and the delivery order, views, the rotation and the self-parent clause, the block-creation conditions, the interfaces the layers pass between them, and `Run`. `scripts/check-arc-holes.py` enforces the split over the arc, as it does for §17 and §18: no proof holes anywhere, and no theorem in a `Model/` file. The layer is closed — every `Model/` file imports only other `Model/` files, three core modules and Mathlib — so the protocol can be read without reading a proof. Four definitions sit outside it, each because it takes a proof as an argument: `Run.verdicts` and `Run.delivers` need the holdings to be a view, and the two `ofDoSValid` constructions need the bridge's own theorems. What the arc does not carry is the other half of the discipline of §17.5: results here are stated where they are proved. `LeanDagTest/FinWhale/Model.lean` settles every definition by `decide` on concrete executions before anything is proved from it, at `f = 2` and `p = 2`, so `n = 9`, the slow-path quorum is `6`, validity asks for `7` parents and the fast path for `7` votes. At `p = 1` the first two coincide and the gap between them would go untested. Three executions, because the rules exclude one another: `Dfast`, where the round-0 leader block is committed by both paths, every round-2 block is FP-evidence for it and a round-3 block reaches a certificate in one step; `Dequiv`, with a second round-0 block by the Byzantine leader, where neither version is committed or skipped and the two branches of FP-evidence separate — one block has seen both versions and carries the `f + p = 4` parents the equivocating branch asks, one has seen a single version and falls under the branch asking `f + p − 1 = 3`, and one has seen both and carries too few of either; and `Dskip`, where all nine validators decline and no anchor reverses the skip. Validity forces the first of those to drop the equivocator's own round-1 block, which is `exposed_not_parent` on data. The reverse pass runs on them through its own well-formedness rather than by evaluation, the pass recursing down from a horizon the kernel does not unfold, and has a second witness on verdicts rather than blocks, where slot `0` is decided indirectly from an anchor the model shows is slot `3` and nothing else. `LeanDagTest/FinWhale/Pace.lean` carries the liveness results to a schedule. FinWhale's smallest committee is `f = 1` and `p = 1`, which is `n = 4` — the committee the development's own pacing witnesses are built over — so `Ugrow N` is a FinWhale DAG at any leader schedule once the leader clause is checked. The same execution carries a `ReactiveM` where both wait clauses hold by their exit and neither fallback is needed, and a `Creation` with two triggers in play, validator `3` catching up by C3 on blocks of its own round and everyone else building by C1, so both interesting cases of §20.5 are exercised and `commits_of_creation` yields the liveness interface with no coverage assumption anywhere. `fastCommit_latency` runs at `δ = 2`, and `fwRun` assembles the lot into a `Run`, off which agreement and integrity are read — which is what keeps FW12 from being vacuous. `LeanDagTest/FinWhale/Equivocation.lean` is where the DoS condition is active rather than merely satisfied. Sixteen blocks over four rounds at `n = 4`: validator `0` equivocates at round `0`, the round-`1` blocks split over the two versions, and a round-`2` block citing two of them reaches both — so three of them are *exposed* to the equivocator and may not cite its round-`1` block, where validity alone would admit it. One round-`2` block has seen a single version and cites it, which makes the prohibition a property of the citing block rather than of the author. The reactive structure exists over that execution, both wait clauses holding by their first disjunct, and the round-`1` leader is committed by both paths — so `Run.ofDoSValidReactive` closes over an execution where equivocation is live, and is not vacuous. The counting bound of §20.7 is met there with nothing to spare: block `10` has convicted validator `0`, the three correct validators are all that remain, `n − f = 3` is exactly what validity asks, and its parent set is those three. The run reaches round `3`, which carries the liveness interface and is short of the `3f + 5` window agreement asks for; the tall execution above supplies that, and cannot supply this, nothing equivocating in it. `LeanDagTest/FinWhale/DoS.lean` witnesses §20.7 on the denial-of-service arc's own execution. `Utwin` has validator `0` equivocating at round `0`; taking that validator as the leader of every round makes its equivocation the leader clause's own case, and `DfwTwin` is the FinWhale DAG built from it. Its slot `0` holds two conflicting blocks, a round-2 block has both versions among its parents' votes and so exposes the equivocation, and that block's parents are authored by the other three validators — the leader clause's second branch, forced by the DoS condition rather than assumed. ## 21. Barnacle: the adaptive leader count *(modules `LeanDag/Barnacle/`; the design record is `barnacle.md`; the protocol is Barnacle, a control loop on the number of leaders per round of a DAG-based protocol, layered on Mysticeti, Odontoceti, Nemo-Nemo, Orcaella, Mahi-Mahi, FinWhale, Hydrozoan and Optimal-Hydrozoan)* Multi-leader rounds cut queuing latency and are unused in production because of head-of-line blocking: one slow leader's slot is not decided by the direct rule, and every later slot of the sequence waits. Barnacle adapts the number of leaders per round to observed conditions. Every `interval` rounds, upon committing the first leader whose round exceeds `lastRound + interval` — the *anchor* — a validator takes the anchor's causal history over the last `interval` rounds as its *window*, counts the slots the direct rule decides within it, compares the count with the number expected under the current leader count, and moves the count up by one or down by `2^backoff` — additive increase, multiplicative decrease. The new count applies to the rounds after the anchor. The paper abstracts the protocol it runs on as four assumptions, A1–A4: rounds and slots, causal completeness, a direct decision predicate local to a wave, and safety and liveness for every fixed configuration. This chapter proves the mechanism safe and live over an explicit interface rendering A1–A4, instantiated at every rule with a carrier, and states what the paper's own schedule needs of its base protocols. Its results carry **BN**-labels; the arc is laid out under the statement/proof partition of §17.5. The structural observation that shapes it is that the paper's algorithm **decides under the count in force and only then switches**: the verdicts of one configuration's range are derivations of the base relation against one fixed schedule, so the dependency between configurations is well-founded by induction, and the fixpoint machinery of §13 is not needed. ### 21.1 The interface A base rule is data — universe and view types, a direct commit predicate, a decision relation parametric in the schedule — and laws: ```lean structure Laws (R : BaseRule Validator BlockId Payload) : Prop where full_ids : ∀ U, R.viewIds (R.full U) = R.ids U historyView_ids : ∀ U A (hA : A ∈ R.ids U), R.viewIds (R.historyView U A hA) = historyFrom (R.block U) A agree : Properties.Agree R.toDagRule commitsDirect : Properties.CommitsDirect R.toDagRule (fun {U} V L r => R.DirectCommitIn V L r) candidates : Properties.CommitsCandidate R.toDagRule ``` A2 is carried by `BaseRule` itself, which extends `DagRule` and so has the fields `viewSound` and `viewComplete`; the two view laws pin the full view to the universe and the history view to the history; the other three are the properties of §16 read at the rule's carrier — `agree` is the safety half of A4, and the two candidate properties tie the direct predicate to the relation. The liveness half of A4 is a clause on a schedule, below. Each rule's fault class lives on its instantiation and none on the interface, which is what lets Nemo-Nemo — whose safety needs no fault class — instantiate it without one. **Every rule instantiates it the same way.** Each commit rule of this development is an anchored rule (§3.2), and `ofAnchored R` (`Barnacle/Model/Anchored.lean`) reads a base rule off any anchored rule: the record as universe, `View.full` and `BlockRecord.historyView` for the two views, `R.wave + 1` for the wave length — the gap an anchor must clear — and `R.Commit` for the direct predicate; `ofAnchoredOn R I` is the same over the records satisfying an invariant, for Orcaella and Optimal-Hydrozoan. Its laws are proved once, `ofAnchored_laws`, from the carrier's properties (§16.1). `liveOfAnchored R rel` adds the notion of a good DAG, `Timed.Good` at the carrier and the rule's fault model — a quorum synchronised from `Rnd` and populating every round to `N` — and its descent laws are `descent_of_support` at the rule's support. So each of the eight instantiations below is its anchored rule, its fault model, its support and one committee inequality, and nothing else. A **configuration** (`Config`) is the data a reconfiguration installs: how many slots each round has, who leads each of them, and how many rounds until the next reconfiguration. Its `Slots` instance, `C.sched`, places slot `κ` at the round `C.roundOf κ` that the widths put it in, and nothing forces the widths to be equal from round to round. The paper's own configurations are the uniform ones, `Config.uniform`, whose schedule is `Sched m` — the pipelined schedule of §3 with slot `(r, l)` led by `getLeader (r + l)`, the paper's `GetLeader` — and `Config.uniform_sched` is that identity. The window count reads the anchor's history as a view and counts the slots of the interval's rounds with a directly committed candidate (BN2 makes it a function of the universe and the anchor): ```lean def observed (R : BaseRule Validator BlockId Payload) (C : Config Validator) (U : R.Universe) (A : BlockId) : ℕ := if hA : A ∈ R.ids U then ((Finset.Ico (C.cum ((R.block U A).round - C.interval)) (C.cum ((R.block U A).round + 1))).filter (fun κ => R.SlotDirect C.sched U (R.historyView U A hA) κ)).card else 0 ``` ### 21.2 Safety, for any update rule A run closed to height `K` (`PartialRun`) starts from a genesis configuration `C₀` and holds `K` configurations — start round, configuration, back-off, anchor — each **within the parameters** (`Config.InBounds`: no round wider than `maxLeaders`, an interval of at least one round and at most `maxInterval`), with every slot of each range decided against the configuration's schedule, the anchor the least committed slot past the threshold, and the next configuration the update rule's. **BN3** — two runs over one universe from one genesis configuration, from any two views, to any two heights, agree: ```lean def PartialRunAgreement (R : BaseRule Validator BlockId Payload) (P : Params) (upd : UpdateRule R) (C₀ : Config Validator) : Prop := Anchored R upd → ∀ (U : R.Universe) (V₁ V₂ : R.View U) (K₁ K₂ : ℕ) (R₁ : PartialRun R P upd C₀ U V₁ K₁) (R₂ : PartialRun R P upd C₀ U V₂ K₂), ∀ k, k ≤ min K₁ K₂ → R₁.start k = R₂.start k ∧ R₁.cfg k = R₂.cfg k ∧ R₁.backoff k = R₂.backoff k ∧ (k < min K₁ K₂ → R₁.anchor k = R₂.anchor k ∧ ∀ κ, R₁.start k < (R₁.cfg k).roundOf κ → (R₁.cfg k).roundOf κ ≤ R₁.start (k + 1) → R₁.vdct k κ = R₂.vdct k κ) ``` The theorem carries no synchrony or fairness hypothesis, and its one condition on the update rule is the one that makes the question well-posed. An update rule takes the validator's own view — ```lean abbrev UpdateRule (R : BaseRule Validator BlockId Payload) : Type := Config Validator → ℕ → (U : R.Universe) → R.View U → BlockId → Config Validator × ℕ ``` — which is what a validator has, and nothing then makes two of them agree: a rule reading its view freely could hand two correct validators different configurations and break the sequence outright. `Anchored` rules that out, asking that the step not depend on which view computes it. It is no restriction in practice, and BN2 is why: the window a rule measures on is the anchor's causal history, which every view holding the anchor holds whole and restricts identically, so a rule computing from its own copy of that history qualifies. The AIMD rule does, by not reading the view at all (**BN7e**). The generality is therefore over rules a validator can run, rather than over functions of an object no validator has. The ledger follows (**BN5**): two runs read one committed sequence as far as both reach, a run's ledger to a lower height is a prefix of its ledger to a higher one, and no block appears twice — the paper's Agreement, Total Order and Integrity. **BN14** adds the fourth property the rest of this development proves of a commit rule and the paper does not claim: a good author's block is *delivered*. A run commits an anchor at each configuration it closes, at the round the next one starts, and `LiveRule.Delivers` — coverage read as reachability — puts every good block two rounds below that anchor in its causal history, whoever authored the anchor. §20 reaches the same property through the self-parent edge: a correct validator's blocks form a chain, round robin makes it a leader again, and that leader block commits. The route here is shorter and asks less. It needs no self-parent clause, which this interface does not have and which not every base protocol carries; it needs no rotation hypothesis; and it does not need the author's *own* slot to commit, which under multiple leaders would restrict delivery to authors both inside the good set and scheduled. The author need never lead again. Mysticeti and Odontoceti satisfy the law from the core's persistence result (T3) and coverage; Nemo-Nemo does not, its own persistence lemma concluding from a block exactly two rounds above where the core's concludes from every block at two rounds or more — a gap one descent closes, and the only thing between the crash rule and the same property. Under the constant rule the arc collapses onto the genesis configuration (**BN6**) — the base development itself when that configuration is uniform at one leader — and the AIMD rule keeps its count in `[1, maxLeaders]`, carries its leaders and its interval across, and is the integer test the implementation runs (**BN7**). Safety and liveness are unconditional in the count — BN3 holds for every update rule and BN8 at whatever count the run reaches — which is the right design and leaves one thing unsaid: nothing in either forbids the measurement reading a window in which *every* scoring slot committed as unhealthy, and so nothing forbids the rule driving the count to one and holding it there. The mechanism would be safe, live and inert. **BN12** closes that. A window is *healthy* when every slot of every scoring round is directly committed on the anchor's history, and the scoring rounds are determined by the evidence: a commit at round `r` rests on round `r + waveLength − 1`, so the anchor at `ra` can carry it only from `d ≥ waveLength − 1` for `r = ra − d`, and the round at `d = waveLength − 1` has the anchor as its only certifier and never scores. That leaves `waveLength ≤ d ≤ interval`, and `expected` is the number of slots those rounds offer — `cum (r + 1 − waveLength) − cum (r − interval)`, which at one width `m` is the paper's `(interval − waveLength + 1) · m`. So `expected` is exactly the count of a healthy window, and BN12a says the measurement reaches it. BN12b then reads the threshold: at `num ≤ den`, which every deployment satisfies, a healthy window raises the count by one and resets the back-off. What BN12 does not claim is that a *good DAG* makes a window healthy; that needs the anchor's history to carry the good validators' blocks below it, a property of the base protocol rather than of the mechanism, and slots led outside the good set do not commit in any case, so the bound there is partial rather than `expected`. **BN12d is the complement, and it is a condition on deployments.** Below one wave there is no window round old enough to have been decided, so `expected` is zero at every anchor, the test `num · expected ≤ den · observed` passes whatever the DAG did, and the rule raises the count and resets the back-off — always. The loop is open. Nothing in `PartialRun` forbids such an interval, and nothing should: safety and liveness are indifferent to it, and the run is parametric in the rule. What closes the loop is `waveLength ≤ interval`, which is the hypothesis BN12a already carries and which a deployment owes the measurement. **There is no total run.** Every configuration commits an anchor at its own round and a universe is finite, so a run with a configuration for every `k` would inject `ℕ` into the universe's ids. There is no total run structure; the paper's *sequence of configurations* is what every prefix of it agrees on, and its safety theorem is agreement of prefixes. ### 21.3 Liveness, and what it asks of the base protocol Liveness is structural, as everywhere in this development, and carries a horizon. A live rule adds one field, `Good U Rnd N` — the rule's own notion of a DAG good from a round to a horizon, for the rules here a reliable quorum synchronised from `Rnd` and populated to `N` — and A4's liveness half is a clause on a schedule with a commit gap `c`. It is stated over any view **caught up to the horizon**, `CoversUpto U V N`, which is what a validator that has received everything up to `N` holds; the full view satisfies it at every `N` (`coversUpto_full`), so the whole-universe reading is the special case: ```lean def LiveRule.LiveOn (R : LiveRule Validator BlockId Payload) (S : Slots Validator) (c : ℕ) : Prop := ∀ (U : R.Universe) (V : R.View U) (Rnd N : ℕ), R.Good U Rnd N → R.toBaseRule.CoversUpto U V N → (∀ κ, Rnd ≤ S.slotRound κ → S.slotRound κ + c + R.waveLength ≤ N → ∃ v, R.Decided S V κ v) ∧ (∀ r, Rnd ≤ r → r + c + R.waveLength ≤ N → ∃ κ, r ≤ S.slotRound κ ∧ S.slotRound κ ≤ r + c ∧ ∃ L, R.Decided S V κ (some L)) ``` The gap is also the margin a slot needs above it: a slot the direct rule does not settle is decided by a committed anchor a wave above, whose own wave must fit under the horizon, so no rule decides every slot up to `N − waveLength`. **BN8** is the paper's Configuration Progress and Liveness in the only form a finite universe admits: a run whose current configuration lies past the synchrony round extends by one, needing the clause at its own configuration only. The run is on a validator's own view rather than on the universe — any view caught up to the horizon — so the statement is about what a validator reaches and not only about what exists — ```lean def ProgressStmt (R : LiveRule Validator BlockId Payload) (P : Params) (upd : UpdateRule R.toBaseRule) (C₀ : Config Validator) (c : ℕ) : Prop := ∀ (U : R.Universe) (V : R.View U) (Rnd N K : ℕ), R.toBaseRule.CoversUpto U V N → ∀ (Rn : PartialRun R.toBaseRule P upd C₀ U V K), R.LiveOn (Rn.cfg K).sched c → R.Good U Rnd N → Rnd ≤ Rn.start K + 1 → Rn.start K + P.maxInterval + 1 + 2 * c + R.waveLength ≤ N → Nonempty (PartialRun R.toBaseRule P upd C₀ U V (K + 1)) ``` — and from a synchrony round at genesis, runs of every height exist under the horizon `K · (maxInterval + 1 + c) + c + waveLength`, for `maxInterval` the bound a run's configurations keep their intervals under. The safety law is consumed inside the liveness proof: the verdict chosen for the anchor's slot and the commit the clause supplies for it are identified by `agree`. ### 21.4 The clause, discharged for the paper's schedule The paper assumes A4 of its base protocols. Under its own rotation, `getLeader (r + l) = (r + l) % n`, and two or more leaders, the route this development takes to liveness — a run of consecutive correct-led slots spanning a wave (`FairRunOn`, §6) — has no instance at `n = 4`, `f = 1`, `m = 2`: three consecutive rounds name every validator. What exists is a run of correct-led *heads*, the first slots of consecutive rounds, and a head committed a wave above a slot decides it with no eligible slot between. Two facts of the base protocol carry this. They are what a protocol owes a schedule mechanism on the liveness side, so they are stated as a property, over a bare `DagRule` at a gap `g` and a goodness predicate: ```lean structure Descent (R : DagRule Validator BlockId Payload) (Good : R.Universe → ℕ → ℕ → Prop) (g slack : ℕ) : Prop where goodLeaders : ∀ (U : R.Universe) (Rnd N : ℕ), Good U Rnd N → ∃ T : Finset Validator, Fintype.card Validator ≤ T.card + slack ∧ ∀ (S : Slots Validator) (V : R.View U) (κ : ℕ), R.CoversUpto U V N → Rnd ≤ S.slotRound κ → S.slotRound κ + g ≤ N → S.leader κ ∈ T → ∃ L, R.Decided S V κ (some L) indirect : ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (i j : ℕ) (A : BlockId), S.slotRound i + g ≤ S.slotRound j → R.Decided S V j (some A) → (∀ i', i < i' → i' < j → S.slotRound i + g ≤ S.slotRound i' → R.Decided S V i' none) → ∃ v, R.Decided S V i v ``` `LiveRule.Descent R slack` is this at the rule's own `waveLength` and `Good`, so a protocol discharges it in its own folder and the mechanism reads it without naming the protocol. `goodLeaders` is A4's direct half — after stabilisation a good leader's slot commits, on any view caught up to the horizon, the commit's evidence lying a wave below it — with the good set missing at most `slack` validators; `indirect` is A3's indirect rule; the eligibility gap of every rule here is its wave length. A run of heads is the schedule clause, one for every configuration that leads its rounds' first slots the same way — `C.head ρ = C.lead ρ 0`, which the AIMD rule carries across unchanged: ```lean def HeadsRun (head : ℕ → Validator) (T : Finset Validator) (g c₀ : ℕ) : Prop := ∀ r, ∃ ρ, r ≤ ρ ∧ ρ + g ≤ r + c₀ ∧ ∀ i, i < g → head (ρ + i) ∈ T ``` **BN9.** From the descent laws, a stretch of consecutive decided slots with a committed top decides everything a wave below it; `waveLength` consecutive good-led heads decide every slot up to a wave below the first and commit it; a run of heads with gap `c₀` makes the configuration's schedule live with gap `c₀`; and round-robin on `n` validators has such runs, with `c₀ = n + waveLength − 1`, for every good set missing at most `slack` whenever `waveLength · slack + 1 ≤ n` — by pigeonhole: if no window of `waveLength` consecutive residues lay inside the good set, choosing for each start a residue outside it within its window would inject `Fin n` into `Fin waveLength × Tᶜ`. The bound is sharp: Mysticeti's committee bound `3f + 1 ≤ n` is exactly this at `waveLength = 3`, `slack = f`. **BN10.** Every rule with a carrier instantiates the interface — Mysticeti, Odontoceti, Nemo-Nemo, Orcaella, Mahi-Mahi, FinWhale, Hydrozoan (§22.7) and Optimal-Hydrozoan (§23.7) — and the four discussed here each satisfy the laws and the descent laws — Odontoceti's indirect law commits the least candidate with a thick link, the canonicity clause of §10; Nemo-Nemo's good set is any synchronised majority, which misses `n − majority` validators, so its slack is that and not `f`, and its liveness under the mechanism consumes the crash bound nowhere; Orcaella's universe is the subtype bundling `HonestNoEquiv` (the interface's `agree` has no slot for §14's one new hypothesis), its statements hold at every admissible indirect threshold, and its slack is `fb + fc` — the only place the mixed bound enters, through the reliable set being the fully-correct class at quorum `n − fb − fc` — and each is **live under round-robin at every configuration whose heads are the rotation**: Mysticeti with gap `n + 2`, the two-round rules with gap `n + 1`. For Mysticeti: ```lean def RoundRobinLive : Prop := ∀ (n : ℕ) (hn : 0 < n) [Faults (Fin n)] (BlockId Payload : Type) [DecidableEq BlockId] (C : Config (Fin n)) (hC : C.head = roundRobin n hn), (mysticetiLive (Validator := Fin n) (BlockId := BlockId) (Payload := Payload)).LiveOn C.sched (n + 2) ``` This is the paper's A4 for its own schedule, assumed there and proved here. **BN11.** The mechanism's conclusion names neither a protocol nor a schedule. A live rule with agreement and the descent laws at slack `slack`, under a schedule whose good sets have runs of `waveLength` heads within `c₀`, reaches every height whose horizon fits under `N`, on any view caught up to `N`. Round-robin is one such schedule, at `c₀ = n + waveLength − 1` under the committee bound of BN9, and the rules above enter as instances of it — each supplying its agreement, its descent laws and its committee bound, and nothing further. ### 21.5 Findings for the paper - **The anchor's round is ambiguous in the pseudocode.** Algorithm 2 returns at the anchor and restarts from `r_committed + 1`; whether the slots of the anchor's round after the anchor are committed at all, and under which count, is not determined. The formalisation takes the text's reading — the previous count through the anchor's round — under which safety needs no further assumption about the base protocol: the paper's planned lemma that later committed leaders reach the anchor is not consumed. - **`expected` is right for a reason the paper does not give.** The window's decidable rounds, by the stated reason, number `interval − waveLength + 2`; the anchor's own round holds one block, so the round a wave below it has a single certifier in the window and never scores, and the count is the paper's `interval − waveLength + 1`. Settled on data. Stated over a configuration it is the number of slots those rounds offer, which needs no fixed width. - **A4 is not automatic under multiple leaders**, and holds for the paper's schedule by the heads descent (§21.4), at every configuration whose heads are the rotation, from the committee bound alone. - **Safety holds for any update rule**, a stronger and simpler statement than the paper's for AIMD; and the paper's count over leader *blocks* equals a count over slots, by agreement. - **The measurement has a property**, which the paper states and does not prove: `expected` is exactly the count of a window whose scoring slots all commit, so a healthy window is read as healthy and the count rises (BN12). Without it the loop is safe and live but may be inert. - **The interval owes the wave a bound.** At `interval < waveLength` the measurement is empty and the health test passes unconditionally (BN12d), so the loop runs open and the count climbs to the cap whatever the DAG does. The paper does not state the condition; the formalisation proves what happens without it. - **There is no total run**; the sequence of configurations is the family of its prefixes. - **The safety law is consumed inside liveness**, at the anchor's slot. - **The liveness clause needs a margin** above the slot, `c + waveLength`, without which it is unsatisfiable for every rule with a Byzantine leader. ### 21.6 Witnesses Every definition is exercised by `decide` before anything is proved from it, on `U7` and two new universes over four validators: `Usun`, eight rounds fully connected, on which a run's count rises from one to two at the anchor and two views' runs are identified through BN3, and `Usk`, `Usun` with one block referenced by its author alone, on which a slot is directly skipped past the threshold and the theorems' anchors skip it. `LiveOn` cannot be decided in general — it quantifies over every good DAG — and a *test rule* whose `Good` pins one universe makes it finite, so that Progress is applied on data and produces the healthy step. With BN11 the clause is a theorem rather than a hypothesis, so the **real** rule with its **real** `Good` runs on data too: the grown family `Ugrow` of §24 is good at every height by lemmas proved once (`ugrow_good`), and `real_runs` gives a run of every height `K` on it, at the horizon that height costs — `11K + 9` rounds at a four-round interval and Mysticeti's gap of `n + 2`. The family stops at its horizon, so the cost is exact, and the run's ranges are decided against its own configurations' schedules. The real theorems with the real `Good` produce verdicts on `U44`, eleven rounds; the heads descent produces the skipped slot's verdict on `Usk`; and the Nemo slack is attacked on a three-validator DAG good over a bare majority with an adversarial live validator outside it, the theorem's verdicts pinned and the slack shown exact. Two negatives are witnessed as well: a run of heads fails for a set violating the committee bound, and a good DAG one round further is not live at gap zero. ## 22. Hydrozoan: the dual-path rule under hybrid faults *(modules `LeanDag/Hydrozoan/`; the design record is `hydrozoan.md`; the protocol is Hydrozoan, the dual-path commit rule of the Hydrozoan paper — DagHydrangea in the reference implementation — under the hybrid fault model `n ≥ 3f + 2c + k + 1`)* Hydrozoan runs on an uncertified DAG under a hybrid fault model: `n` replicas of which at most `f` are Byzantine and at most *c* crashed, with `n ≥ 3f + 2c + k + 1` for a tunable slack `k`. A slot — one leader-decision instance — is proposed at its round, voted on one round up, and settled two rounds up. Three direct rules read it off the DAG: the fast path commits the leader's block on `qFast` votes at the voting round, `qFast = n − p` with `p = ⌊(c + k)/2⌋` the number of votes the path can do without; the slow path commits it on `qSlow` certificates at the decision round, a certificate being a decision-round block whose votes for the candidate come from `qCert` distinct authors; the skip rule discards the slot on `qFast` blames. A slot none of the three settles is decided from the nearest committed slot far enough above it, the anchor, by a graded rule: commit on a certificate in the anchor's causal history, else commit the least candidate with `qWeak` anchor-linked votes, else skip. This chapter proves the rule safe and live and grounds its liveness hypotheses. Its results carry **HZ**-labels; the arc is the fourth under the statement/proof partition of §17.5. The observation that shapes it is that **the two paths are held together by counting alone**: the paper's two-case consistency argument — a fast commit leaves a weak footprint every anchor sees, so the indirect rule can neither skip the slot nor commit a rival — is six inequalities on five thresholds, and the six hold for every fault configuration the class admits, with no cap on the slack. The seam is a theorem of the thresholds, and safety quantifies over every schedule. ### 22.1 The model The fault model carries the bounds and the actual fault sets, and two pools are read off it: `Correct`, which availability and liveness count, and `NonByzantine`, which uniqueness counts — a crashed replica never equivocates. The thresholds are ```lean def p : ℕ := (F.c + F.k) / 2 def q : ℕ := Fintype.card Replica - F.f - F.c def qFast : ℕ := Fintype.card Replica - p Replica def qCert : ℕ := (Fintype.card Replica + F.f) / 2 + 1 def qSlow : ℕ := 2 * F.f + F.c + 1 def qWeak : ℕ := F.f + p Replica + 1 ``` with `p` derived and never an input. A block is its round, author and parent ids, resolved through a total lookup, so validity is a predicate on the pair; the predecessor condition is additive, which makes the genesis case derivable, and the quorum counts authors: ```lean structure ValidWrt (blk : BlockId → Block Replica BlockId) (b : Block Replica BlockId) : Prop where predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j quorum : 0 < b.round → q Replica ≤ (authors blk b).card ``` The universe is every block the DAG-building layer accepted — Byzantine ones included, since malformed emissions are filtered before entering any DAG — closed under references, valid throughout, and non-equivocating for non-Byzantine authors, a condition stated at the universe and not per view: two well-formed local DAGs holding different blocks of one honest author in one round is exactly that author equivocating. A view is a reference-closed subset of the universe's ids sharing its lookup. `Reaches` is the reflexive-transitive closure of the parent relation, the paper's *Link*. The schedule is abstract: ```lean class Slots (Replica : Type*) where slotRound : ℕ → ℕ leader : ℕ → Replica mono : Monotone slotRound unbounded : ∀ n, ∃ k, n ≤ slotRound k keyed : Function.Injective fun k => (slotRound k, leader k) ``` Several leaders in a round are several slots at one round; pipelining is a witness instance. A candidate for a slot is a block of the universe at the slot's round by its leader — several, if the leader equivocates. The direct rules count authors: ```lean def FastCommit (U : BlockUniverse Replica BlockId) (L : BlockId) (r : ℕ) : Prop := qFast Replica ≤ (supporters U L (r + 1)).card def SlowCommit (U : BlockUniverse Replica BlockId) (L : BlockId) (r : ℕ) : Prop := qSlow Replica ≤ (certifiers U L r).card def SkippedLeader (U : BlockUniverse Replica BlockId) (k : ℕ) : Prop := qFast Replica ≤ (slotBlames U k).card ``` Blames target the slot, so a vote for any equivocating copy is not a blame. A view reads each rule through `∩ V.ids` and can under-report it, never exceed it. The indirect rule's two rung tests ask for a certificate in the anchor's history and for `qWeak` anchor-reachable votes, the latter over an explicit witness set so that the trusted core never decides reachability: ```lean def CertifiedIn (U : BlockUniverse Replica BlockId) (A L : BlockId) (r : ℕ) : Prop := ∃ C ∈ certificates U L r, Reaches U A C def WeakLinked (U : BlockUniverse Replica BlockId) (A L : BlockId) (r : ℕ) : Prop := ∃ s : Finset BlockId, (∀ b ∈ s, b ∈ blocksAt U (r + 1) ∧ IsVote U b L ∧ Reaches U A b) ∧ qWeak Replica ≤ (creatorsOf U.block s).card ``` The decision relation is the shared one (§3.5) at Hydrozoan's data: the direct commit is the fast path or the slow path in view, the direct skip the blames, and two rungs, the anchor-linked certificate and then the weak quorum, the second tie-broken by the identifier order — the paper's *argmin digest*. It is order-free between the direct routes — any justifiable verdict is derivable, and safety proves the routes never disagree — while the strict grading inside the indirect rule is what the rungs encode: the weak rung fires only when no candidate has an anchor-linked certificate, the indirect skip only when both rungs are empty for every candidate, and the anchor is the nearest eligible committed slot with every eligible slot between skipped: ```lean def hydrozoanAnchored : AnchoredRule Replica BlockId Unit ValidWrt NonByzantine where wave := 2 Commit := fun U V L r => FastCommitInView U V L r ∨ SlowCommitInView U V L r Skip := fun U V S k => SkippedLeaderInView (S := S) U V k rungs := 2 Link := fun i U A L S k => match i with | 0 => CertifiedIn U A L (S.slotRound k) | _ => WeakLinked U A L (S.slotRound k) tie := fun i L' L => match i with | 0 => False | _ => L' < L ``` Undecided is the absence of a derivation. ### 22.2 Safety, from the thresholds alone **HZ1.** Six inequalities, one per row of the paper's threshold table, stated subtraction-free over every fault configuration: ```lean def CertUniqueness : Prop := Fintype.card Replica + F.f < 2 * qCert Replica def FastUniqueness : Prop := Fintype.card Replica + F.f < 2 * qFast Replica def FastStarvation : Prop := Fintype.card Replica + F.f < qFast Replica + qWeak Replica def SlowCollectible : Prop := qCert Replica ≤ q Replica def AnchorSeesSlow : Prop := Fintype.card Replica + F.f < q Replica + qSlow Replica def AnchorSeesFast : Prop := Fintype.card Replica + qWeak Replica ≤ qFast Replica + q Replica ``` The Hydrangea paper caps the slack — `k ≤ 2f + c − 4` for even *c* — and the class assumes only the committee bound; the table holds for every `k ≥ 0`, so nothing in the DAG argument needs the cap. **HZ2.** The direct rules never disagree about a slot: fast/fast, slow/slow and fast/slow agreement across any two views, certificate uniqueness at the universe, and commit/skip exclusion. The fast/slow pairing is the one the starvation row carries — a fast commit leaves every conflicting candidate at most `f + p` supporters, below `qWeak` and hence below `qCert`: ```lean def FastSlowAgreement (U : BlockUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U) (k : ℕ) (L₁ L₂ : BlockId), IsLeaderBlock U k L₁ → IsLeaderBlock U k L₂ → FastCommitInView U V₁ L₁ (S.slotRound k) → SlowCommitInView U V₂ L₂ (S.slotRound k) → L₁ = L₂ def CommitSkipExclusion (U : BlockUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U) (k : ℕ) (L : BlockId), IsLeaderBlock U k L → (FastCommitInView U V₁ L (S.slotRound k) ∨ SlowCommitInView U V₁ L (S.slotRound k)) → ¬ SkippedLeaderInView U V₂ k ``` **HZ3.** The two-case consistency argument as one statement — any two verdicts on one slot agree, across views and across the routes; it is the relation's agreement at Hydrozoan's laws (`hydrozoanLaws`), the laws being HZ2 for the direct pairings, the two rungs firing at any candidate of an eligible slot for a slow and a fast commit respectively, the starvation lemmas for the crossings, and antisymmetry of the order at the weak rung: ```lean def DecidedUnique (U : BlockUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U) (k : ℕ) (v₁ v₂ : Option BlockId), Decided U V₁ k v₁ → Decided U V₂ k v₂ → v₁ = v₂ ``` Its proof consumes the "anchor sees the fast footprint" row in a strengthened form, `qFast + q − n − f ≥ qWeak` rather than the table's `qFast + q − n ≥ qWeak`: a Byzantine author's block in the anchor's history may be its non-voting equivocation, so only the non-Byzantine overlap of a fast quorum and an anchor's parents contributes anchor-linked votes. The strengthened row holds under the committee bound and is a helper lemma; the paper's argument counts the weaker one. **HZ4.** The output guarantee. `commitSeq` is the committed leaders below a horizon in slot order, skips dropped — the shape of the paper's *ExtendCommitSeq* — and `ledger` flattens each through a linearizer, the paper's *LinearizeSubDags* abstracted to a function. Equal horizons give equal sequences, different horizons a prefix, and ledgers inherit the prefix; `DecidesBelow` demands a derivation for every slot below the horizon, so the claims speak exactly where replicas have produced output: ```lean def commitSeq (g : ℕ → Option BlockId) (n : ℕ) : List BlockId := (List.range n).filterMap g def DecidesBelow (U : BlockUniverse Replica BlockId) (V : View U) (g : ℕ → Option BlockId) (n : ℕ) : Prop := ∀ k < n, Decided U V k (g k) def PrefixConsistency (U : BlockUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U) (g₁ g₂ : ℕ → Option BlockId) (n₁ n₂ : ℕ), n₁ ≤ n₂ → DecidesBelow U V₁ g₁ n₁ → DecidesBelow U V₂ g₂ n₂ → commitSeq g₁ n₁ <+: commitSeq g₂ n₂ ``` ### 22.3 Liveness, through the slow path Safety assumed nothing of the network. Liveness is exactly as strong as a package of two predicates rendering "after GST" structurally: every member of a set *T* authors a block at a round, and from a round *R* on every *T*-authored block references every *T*-authored block of the round below. ```lean def PopulatedOn (U : BlockUniverse Replica BlockId) (T : Finset Replica) (r : ℕ) : Prop := ∀ v ∈ T, ∃ b ∈ U.ids, (U.block b).round = r ∧ (U.block b).creator = v def SynchronisedOn (U : BlockUniverse Replica BlockId) (T : Finset Replica) (R : ℕ) : Prop := ∀ n, R ≤ n → -- at every round n from R on: ∀ b ∈ U.ids, -- every existing block b ... (U.block b).round = n + 1 → -- ... sitting one round above n ... (U.block b).creator ∈ T → -- ... authored by a member of T, ∀ a ∈ U.ids, -- and every existing block a ... (U.block a).round = n → -- ... sitting at round n ... (U.block a).creator ∈ T → -- ... also authored by a member of T: a ∈ (U.block b).refs -- a is among b's parents ``` Both are *T*-relative, deliberately: liveness counts to quorums, and demanding all of `Correct` would void the theorems whenever one correct replica misses one round; `T ⊆ Correct` and `q ≤ |T|` are hypotheses of the consuming theorems. *R* is a round index, not GST — no clock and no `Δ` appear. `SynchronisedOn` is an assumption and recorded as one: a replica building on the first quorum it holds can miss a slow correct block forever, and what makes the property true in good periods is the protocol's waiting rule, whose derivation from delivery primitives is out of scope. `View.full` is the eventual view, the whole universe as a view; `View.CoversUpto` is the view a replica caught up to a horizon actually holds, and it is what the liveness statements conclude on — the eventual view is caught up to every horizon, so the whole-universe reading is the special case. **HZ5.** A quorum-sized correct *T*, synchronised from some *R* at or before the wave and populated through its three rounds, commits its correct leader through the slow path, and the verdict is derivable on any view caught up to the decision round: ```lean def CommitLiveness (U : BlockUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R k : ℕ), -- for any set T, round R, slot k: T ⊆ (Correct : Finset Replica) → -- T holds only correct replicas ... q Replica ≤ T.card → -- ... and is at least a DAG quorum, SynchronisedOn U T R → -- T is internally synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, PopulatedOn U T (S.slotRound k) → -- T fills the propose round ... PopulatedOn U T (S.slotRound k + 1) → -- ... the voting round ... PopulatedOn U T (S.slotRound k + 2) → -- ... and the decision round, S.leader k ∈ T → -- and the slot's leader is in T: ∀ V : View U, -- then, on any view caught up V.CoversUpto (S.slotRound k + 2) → -- ... to the decision round: ∃ L, IsLeaderBlock U k L ∧ -- a candidate exists, SlowCommit U L (S.slotRound k) ∧ -- the slow threshold is met, Decided U V k (some L) -- and its verdict is committed ``` The guaranteed path is the slow one, and that is the design decision the design decision the arc verifies: with *c* crashes and `f` silent Byzantine replicas only `q` voters are certain, and `q < qFast` in general, while `qCert ≤ q` and `qSlow ≤ q` make the slow path reachable by the guaranteed quorum alone. The fast path and the direct skip are stated in the same file and kept outside the liveness claim — performance facts, firing exactly when the actual faults fit `p` and needing all of `Correct`: ```lean def FastLatency (U : BlockUniverse Replica BlockId) : Prop := ∀ (R k : ℕ), -- for any round R and slot k: (F.byzantine ∪ F.crashed).card ≤ p Replica → -- ACTUAL faults fit p, Synchronised U R → -- all correct synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, Populated U (S.slotRound k) → -- correct fill the propose round ... Populated U (S.slotRound k + 1) → -- ... and the voting round, S.leader k ∈ (Correct : Finset Replica) → -- and the leader is correct: ∃ L, IsLeaderBlock U k L ∧ -- then a candidate exists ... FastCommit U L (S.slotRound k) -- ... and it fast-commits (2 rounds) ``` **HZ6.** The indirect rule does the rest, with no synchrony, population or fault hypothesis: once a nearest eligible committed anchor exists some rung fires, and *c* consecutive committed slots, long enough that the run's end anchors everything below — the relation's `SpansEligible`, which the pipelined schedule satisfies exactly at *c* ≥ 3 — decide every slot below the run. One committed slot does not suffice, since the slots just below it cannot use it as an anchor; a three-round run does. Both are the relation's own statements, `AnchoredRule.Total` and `AnchoredRule.DecidedBelowRun`, proved once from a choice at every nonempty rung (`total_of_least`, `decidedBelowRun_of_least`), which for the graded rule is `exists_least`: any certified candidate at the first rung, the least weak-linked one at the second. ```lean def Total (U : BlockRecord Validator BlockId Payload P honest) : Prop := ∀ (V : U.View) (k j : ℕ) (A : BlockId), R.Eligible (S := S) k j → R.Decided (S := S) U V j (some A) → (∀ i, k < i → i < j → R.Eligible (S := S) k i → R.Decided (S := S) U V i none) → ∃ v, R.Decided (S := S) U V k v def DecidedBelowRun (U : BlockRecord Validator BlockId Payload P honest) : Prop := ∀ (V : U.View) (b c : ℕ), 0 < c → R.SpansEligible (S := S) c → (∀ j, b ≤ j → j ≤ b + c - 1 → ∃ B, R.Decided (S := S) U V j (some B)) → ∀ i, i < b → ∃ v, R.Decided (S := S) U V i v ``` **HZ7.** The composition. A synchronised quorum whose members lead the *c* slots of a run and fill every round of the run's span decides every slot below it, and fairness — the schedule places *c* consecutive *T*-led slots past any point (`FairRunOn`, the one definition every rule reads) — places such a run past any slot and any round (`Slots.exists_run_past`): ```lean def FairRunOn [S : Slots Validator] (T : Finset Validator) (c : ℕ) : Prop := ∀ k, ∃ k', k ≤ k' ∧ ∀ i, i < c → S.leader (k' + i) ∈ T def RunsRecur : Prop := ∀ (T : Finset Replica) (c k R : ℕ), FairRunOn T c → -- given a fair schedule: ∃ b, k ≤ b ∧ -- a run location past k ... R ≤ S.slotRound b ∧ -- ... at or after round R ... ∀ i, i < c → S.leader (b + i) ∈ T -- ... with every slot T-led. def RunDecidesBelow (U : BlockUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R b c : ℕ), T ⊆ (Correct : Finset Replica) → -- a set of correct replicas ... q Replica ≤ T.card → -- ... of at least a DAG quorum, SynchronisedOn U T R → -- internally synchronised from R, 0 < c → -- a nonempty run of slots ... (hydrozoanAnchored Replica BlockId).SpansEligible c → -- ... every run's end anchoring all below, R ≤ S.slotRound b → -- lying at or after R, (∀ i, i < c → S.leader (b + i) ∈ T) → -- every run slot T-led, (∀ r, S.slotRound b ≤ r → -- and T fills every round from r ≤ S.slotRound (b + c - 1) + 2 → -- the run's propose round to its PopulatedOn U T r) → -- last decision round: ∀ V : View U, -- then, on any view caught up V.CoversUpto (S.slotRound (b + c - 1) + 2) → -- ... to that round: ∀ i, i < b → ∃ v, Decided U V i v -- all below decided. ``` The composed form — for every slot a bound past it with every slot below the bound decided on any view caught up to the run's last decision round — is `ledgerProgress` on the proof side; the audited content is the two Props. Fairness is a hypothesis on the schedule rather than a theorem about it because per-slot rotation does not provide it at the hybrid bound: liveness needs three consecutive correct-led slots, and crashed replicas spaced one every three, inside the bound, deny them (§22.5). ### 22.4 Grounding **HZ8.** The liveness arc consumes three kinds of assumed hypotheses, and the arc grounds them by exhibition. The wave-aligned rotation on `n` replicas — slot `k` at round `k`, the leader holding for a whole three-slot wave before the rotation advances — is fair with no premise beyond the fault model, since one correct leader's wave is a full run and the bounds guarantee a correct replica: ```lean def waveRobin (n : ℕ) (hn : 0 < n) : Slots (Fin n) where slotRound k := k leader k := ⟨k / 3 % n, Nat.mod_lt _ hn⟩ mono := fun _ _ h => h unbounded := fun m => ⟨m, le_refl m⟩ keyed := fun _ _ h => congrArg Prod.fst h def WaveRobinFair : Prop := ∀ (n : ℕ) (hn : 0 < n) [Faults (Fin n)], FairRunOn (S := waveRobin n hn) (Correct : Finset (Fin n)) 3 -- correct 3-runs recur. ``` For every quorum-sized *T* and horizon *N*, some universe authored by *T* alone is *T*-populated to *N* and *T*-synchronised from round 0 — the good period is a consistent scenario of the model at every scale, and the *T*-only clause is what earns the `q ≤ |T|` premise, since a *T*-only universe cannot validly populate a round below quorum size. And under the wave-aligned rotation, past every slot some universe commits a bound with every slot below it decided: ```lean def HypothesesRealizable : Prop := ∀ (Replica : Type) [Fintype Replica] [DecidableEq Replica] [LeanDag.Hydrozoan.Faults Replica] (T : Finset Replica) (N : ℕ), q Replica ≤ T.card → -- a quorum-sized T: ∃ U : BlockUniverse Replica ℕ, -- some universe is (∀ b ∈ U.ids, (U.block b).creator ∈ T) ∧ -- authored by T alone, (∀ r, r ≤ N → PopulatedOn U T r) ∧ -- populated to the horizon SynchronisedOn U T 0 -- and synchronised throughout. ``` The last is an achievability claim, satisfiability of the conclusion and not the route to it, and its universe is not constrained to correct authors (§22.5). ### 22.5 Findings for the paper - **The slack cap is not needed by the DAG argument.** The six inequalities the consistency argument rests on hold for every `k ≥ 0` under the committee bound alone (HZ1). Whatever Hydrangea's Theorem 1 caps the slack for, it is not slot safety. - **The fast footprint row is consumed in a strengthened form**, the non-Byzantine overlap `qFast + q − n − f ≥ qWeak`, because a Byzantine author's block in the anchor's history may be its non-voting equivocation (HZ3). The paper's argument should count that overlap. - **The guaranteed path is the slow one.** The fast path cannot be guaranteed above `p` actual faults; the liveness argument routes through the slow path, and the two-round latency is conditional on the actual fault count (`FastLatency`). - **Per-slot rotation is not fair at the hybrid bound.** A per-slot round robin guarantees three consecutive correct-led slots only when `n` exceeds three times the actual fault count, false inside the bound at `n = 5`, `f = 0`, *c* = 2 with the crashed replicas at positions 0 and 3 — the witness `slotRobin` starves every correct three-run, while `waveRobin` is fair unconditionally (HZ8). - **A fast commit can leave no slow path, structurally.** With equivocation, a slot fast-commits while no certificate for it exists anywhere in the universe (the `DirectSafety` witness), so the weak rung of the indirect rule is necessary, not a convenience. - **Two statements say less than their docstrings.** `PrefixAgreement`'s ledger claim holds for a memoryless per-leader linearizer while the paper's *LinearizeSubDags* is stateful; and `GroundedProgress` does not constrain its universe's authors, so a universe in which every faulty replica behaves satisfies it — the proof uses the correct-authored universe, the statement does not say so. Both are recorded in the docstrings rather than amended. ### 22.6 Witnesses Every definition is exercised by `decide` before anything is proved from it, on the seven-replica model `sevenReplicas` — `f = c = k = 1`, replica 0 Byzantine and equivocating, replica 1 crashed after genesis — and the tight instances at `f = 10`, *c* = 34 for five values of `k`, the three quorums pinned row by row as they fan out. The universes exercise equivocation and halting, a view that withholds, the fast path firing exactly at quorum beside a single certificate short of `qSlow`, the fast path with zero certificates anywhere, the hardening universe `U5` with ids outside the universe and an eligible skipped slot between candidate and anchor, and the output sequences and their prefixes. The liveness package is shown satisfiable, biting — a universe synchronised from round 2 and provably not from 0 — and compatible with validity; every `holds` is applied end to end, so that a silently strengthened hypothesis fails the build by arity or type, including at a proper-subset *T* whose actual faults undershoot the bounds, where `T = Correct` and synchrony over all of `Correct` are pinned false. The axioms tripwire pins every `holds` by `#guard_msgs` to its exact axiom list, within `propext`, `Classical.choice` and `Quot.sound`, a build failure on drift. ### 22.7 Through the properties *(modules `LeanDag/Hydrozoan/Helpers/{Carrier,Banded,Commit,Skippability}.lean`, `LeanDag/Hydrozoan/Properties/`, `LeanDag/Integration/HydrozoanMechanisms.lean`, `LeanDagTest/Barnacle/Rules/Hydrozoan*/`)* Hydrozoan composes with the mechanisms the way every other rule does (§16.1): a carrier `LeanDag.Hydrozoan.rule`, the four properties and a support at it (`hzSupport`, whose `Commits` law is the slow path, and a fast-path support at the stronger fault model), and every mechanism cell an instance of the generic theorem. The results carry **HI**-labels. **As a Barnacle rule.** Barnacle (§21) abstracts a commit rule as a record and proves the adaptive leader count against the record. Instantiating it is additive, and the instantiation is where Hydrozoan acquires multi-leader schedules, which its own arc does not supply. The Barnacle rule is `ofAnchored` at `hydrozoanAnchored` (§21.1), so its carrier is Hydrozoan's own (`LeanDag.Hydrozoan.rule`), and Hydrozoan's universe satisfies the causal-structure law as every block record does, by its own fields (`BlockRecord.causal`, HI3). The wave length is three, and the direct-commit predicate is the anchored rule's, the disjunction of the two commit paths. The laws are `ofAnchored_laws` at HZ3's laws (HI4). The live rule's good DAGs are `Timed.Good` at Hydrozoan's fault model, at slack `f + c` — the fully-correct class is what liveness counts — and its descent laws come from the support: `hzSupport` with its `Commits` law and `Indirect` through `descent_of_support`, since at wave length three the interface's spacing condition *is* Hydrozoan's anchor eligibility (HI5). Round-robin liveness is the one place a committee condition appears, `3(f + c) + 1 ≤ n`, reached by a route that mentions neither Hydrozoan's quorum nor its intersection argument. Optimal-Hydrozoan is mirrored in the same shape (§23.7). **The cut and the fill.** A deployed replica does not hold the DAG the network built. It prunes below a horizon, and it may have recovered from a crash by one message. Both are universe transformers, and Hydrozoan takes them from the block record (§2.3), its universe being the record at its own validity: the cut and the copy fill are the record's `BlockRecord.chop` and `BlockRecord.copyFill`, under no name of Hydrozoan's own, and what Hydrozoan supplies is that its validity has the four facts a predicate owes and does not read the creator. **The cut and the fill are cells of the record.** `Hydrozoan.onRecord` reads the carrier as records, and `Properties/Arcs/Record.lean` proves every verdict cell once at any such instance with `Banded` and `Agree`: verdicts survive garbage collection in both directions (`DagRule.OnRecord.decided_chop_iff`), on the base-slot premise `G ≤ S.slotRound d` alone — no synchrony, no fairness, no liveness (HI7); a replica that recovers by one message reaches the same verdicts as one that never crashed (`DagRule.OnRecord.decided_copyFill` and `decided_agree_copyFill`, HI9); and re-genesis is `DagRule.OnRecord.addGenesis` with its two verdict theorems. Nothing is written per cell for Hydrozoan; what it supplied is `Hydrozoan.banded`, `Hydrozoan.agree` and the instance. The fill needs **no quorum hypothesis**, where the core's corresponding result (SS5) needs one. The reason is a difference between the rules rather than a strengthening: the core's skip is stated per candidate, so a filled slot that gains a candidate demands a fresh justification, while Hydrozoan's skip counts blames at the slot and the count does not move when no old block references a fresh identifier. In the properties' vocabulary that is `LeanDag.Hydrozoan.banded`, and the fill cell is `Persist.of_banded` at the `Extends` witness `extends_copyFill`. Hydrozoan's prompt skip at the fill is `decided_none_fresh_hz` (§16.1), at the grade `qFast ≤ |T|` its `SkipsUnsupported` carries. Liveness across both is the generic `Support.live_of_truncates` and `Support.live_of_sustains` at `hzSupport`, and re-genesis is the record's `addGenesis`. **What a deployment gets.** What a reader wants is a statement about one replica's situation: the DAG the network built, a recovery performed by one message, a horizon below which nothing is retained, and the slot its numbering restarts at. That is the headline (§16.1) at Hydrozoan's rule: the stack `Stack.step (Rebased.of_sustains (onRecord.sustains_copyFill U sk)) (Stack.step (Rebased.of_truncates (onRecord.truncates_chop U hd)) Stack.nil)` is a `Rebased` by `Stack.rebased`, so `Hydrozoan.Properties.safety` says verdicts transport in both directions, any view of what the replica holds agrees with any view of the network, a commit is the slot's candidate and no block is committed twice; and `Hydrozoan.Properties.progress` says every slot below a fair run is decided and commits recur, on any execution meeting `hzSupport`'s `live` — certification of the candidates, which is Hydrozoan's slow path (HI10). The inclusion half of liveness is absent, because the model carries no self-parent clause. The threshold the protocol's `CommitLiveness` asserts and the headline does not is evidence in the DAG; it has no property, and what a recovered replica's liveness is about is the verdict (`docs/bespoke-links.md` D). **A finding.** **Hydrozoan's fill needs no quorum hypothesis** where the core's does, because its skip counts at the slot rather than per candidate — the same distinction Mahi-Mahi reports as a finding against the core (HI9). The Barnacle instantiation is exercised by `decide` on the arc's own seven-replica universe `U3` (`LeanDagTest/Barnacle/Hydrozoan.lean`) and its `holds` audited in `LeanDagTest/Barnacle/Axioms.lean`; the cut and fill are checked by the build and the audits of §16.2. ## 23. Optimal-Hydrozoan: the fast path at Hydrangea's bound *(modules `LeanDag/OptimalHydrozoan/`; the design record is `optimal-hydrozoan.md`; the protocol is Optimal-Hydrozoan, the theory-only variant of Hydrozoan (§22) whose fast path tolerates one more fault, by FinWhale's validity rule (§20))* Hydrozoan's fast path commits on `qFast` votes, `n − p` with `p = ⌊(c + k)/2⌋`, and Hydrangea's lower bound on two-round commits allows `⌊(c + k)/2⌋ + 1`. Optimal-Hydrozoan closes the gap at the same committee `n ≥ 3f + 2c + k + 1`: the allowance becomes `pOpt = p + 1`, the fast quorum `qFastOpt = n − pOpt`, and the slack the extra fault consumes is recovered at the decision round, block by block, by the device FinWhale uses at `n = 3f + 2p − 1`. A decision-round block that has seen the leader equivocate must not reference the leader's block — the leader-exclusion rule of the DAG-building layer — and a decision-round block is *fast evidence* for a candidate when it references `tPlain = n − 2f − c − pOpt` votes for it, or, if it witnessed an equivocation, `tEquiv = f + pOpt` votes with every rival below `tEquiv`. Quorums of `qCert` evidence blocks take the place of Hydrozoan's `qWeak` votes: the evidence rung of the indirect rule, and the direct skip, which asks for `qCert` blames and `qCert` decision-round blocks that are evidence for no candidate. The slow path, the certificates and the schedule are Hydrozoan's, and the arc imports the Hydrozoan arc read-only as a peer. This chapter proves the variant safe and live and grounds its hypotheses. Its results carry **OH**-labels and mirror HZ1–HZ8. The observation that shapes it is that **the seam consumes the validity rule exactly once**: a fast commit at `qFastOpt` makes every decision-round block evidence for the committed block — by the plain row if the block witnessed nothing, by the equivocation row and the exclusion rule if it did — so every eligible anchor reaches an evidence quorum, a block is evidence for at most one candidate, and two evidence quorums share a non-Byzantine author. The evidence rung is unique with no tie-break, and the arc's statements need no linear order on ids. The protocol is not implemented; the arc settles that an optimal protocol exists in the spectrum, and at `k = 2f + c − 2` every fault fits the fast path at `n ≥ 5f + 3c − 1`, Kuznetsov's `5f − 1` at *c* = 0. ### 23.1 The model `OptimalFaults` extends `Faults` with the paper's standing assumption `1 ≤ f + c`, under which the threshold table is guaranteed; the allowance and the two per-block thresholds are ```lean def pOpt : ℕ := p Replica + 1 def qFastOpt : ℕ := Fintype.card Replica - pOpt Replica def tPlain : ℕ := Fintype.card Replica - (2 * O.f + O.c + pOpt Replica) def tEquiv : ℕ := O.f + pOpt Replica ``` `tPlain` is a truncated subtraction on purpose: the table states the paper's identity as an equality that fails under truncation. A block witnesses an equivocation in a slot when two distinct candidates of the slot are each voted for by one of its parents, and the universe carries the rule: ```lean def WitnessesEquivocation (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) (k : ℕ) (b : BlockId) : Prop := ∃ L₁ L₂, IsLeaderBlock U k L₁ ∧ IsLeaderBlock U k L₂ ∧ L₁ ≠ L₂ ∧ (∃ j ∈ (U.block b).refs, IsVote U j L₁) ∧ (∃ j ∈ (U.block b).refs, IsVote U j L₂) ``` The round guard is derivable from the predecessor condition and stated so that the rule reads as the paper states it. Fast evidence is two implications rather than an `if`, so the core needs no decidability, and every consumer guards it with the candidate and round predicates: ```lean def FastCommitOpt (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) (L : BlockId) (r : ℕ) : Prop := qFastOpt Replica ≤ (supporters U L (r + 1)).card ``` `NoEvidenceQuorum` and the evidence rung's test are existential over a witness set of decision-round blocks, as Hydrozoan's `WeakLinked` is: ```lean def EvidenceLinked (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) (A L : BlockId) (k : ℕ) : Prop := ∃ s : Finset BlockId, -- some set of blocks such that (∀ b ∈ s, -- every block in it b ∈ blocksAt U (decisionRound Replica k) ∧ -- sits at slot k's decision round, IsFastEvidence U k b L ∧ -- is fast evidence for L, Reaches U A b) ∧ -- and lies in the anchor's history; qCert Replica ≤ (creatorsOf U.block s).card -- and they come from q_cert creators ``` `DecidedOpt` is the shared relation (§3.5) at Optimal's data (`optimalAnchored`): the Optimal fast path or Hydrozoan's slow path, the Optimal skip, and the certificate rung followed by the evidence rung, neither tie-broken. Its laws hold under leader exclusion at the schedule (`LeaderExcluded`), which every `OptUniverse` supplies from its validity (`OptUniverse.leader_excluded`), so the safety statements quantify over those universes; the rule predicates themselves are applied to the Hydrozoan universe beneath (`OptUniverse.toBlockRecord`). ### 23.2 Safety **OH1.** Three rows of Hydrozoan's table inherited and four Optimal rows, over every configuration the class admits — no cap on `k`: ```lean def CertFastExclusion : Prop := Fintype.card Replica + O.f < qCert Replica + qFastOpt Replica def EvidencePlain : Prop := qFastOpt Replica + q Replica = Fintype.card Replica + O.f + tPlain Replica ∧ 1 ≤ tPlain Replica def EvidenceEquiv : Prop := Fintype.card Replica + O.f + tEquiv Replica ≤ qFastOpt Replica + q Replica + 1 def FastUniqueness : Prop := 1 ≤ O.f → Fintype.card Replica + O.f < 2 * qFastOpt Replica ``` `CertFastExclusion` replaces the weak-rung starvation row; the two evidence rows replace the fast-footprint row, and the `+ 1` of `EvidenceEquiv` is the exclusion rule's dividend — the row that pins `n ≥ 3f + c + 2·pOpt − 1`. `FastUniqueness` carries the paper's `f ≥ 1` guard, necessary (at `f = 0` the row fails on a crash-only configuration with slack) and sufficient (with `f = 0` no replica equivocates). **OH2.** The five direct pairings, two of them Hydrozoan's claims on the underlying universe, the other three reading the Optimal rules — the `qCert` blames of the skip against the `qFastOpt` fast voters and against the `qCert` votes inside a certificate: ```lean def CommitSkipExclusion (U : OptUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U.toBlockUniverse) (k : ℕ) (L : BlockId), IsLeaderBlock U.toBlockUniverse k L → (FastCommitOptInView U.toBlockUniverse V₁ L (S.slotRound k) ∨ SlowCommitInView U.toBlockUniverse V₁ L (S.slotRound k)) → ¬ SkippedLeaderOptInView U.toBlockUniverse V₂ k ``` **OH3.** Any two verdicts agree, across views and across the routes, with no order on ids — the relation's agreement at Optimal's laws (`optimalLaws`), which hold under the universe's exclusion: ```lean def DecidedUnique (U : OptUniverse Replica BlockId) : Prop := ∀ (V₁ V₂ : View U.toBlockUniverse) (k : ℕ) (v₁ v₂ : Option BlockId), DecidedOpt U V₁ k v₁ → DecidedOpt U V₂ k v₂ → v₁ = v₂ ``` This is the paper's direct-decision lemma together with the indirect/indirect case, and slightly more: the paper's indirect lemma covers two decisions from the same anchor, the claim also two views whose nearest eligible committed anchors differ. **OH4** is prefix agreement over `DecidedOpt`, `commitSeq` and `ledger` reused; its docstring records that the linearizer abstraction is memoryless where the paper's is stateful. ### 23.3 Liveness, and the guaranteed skip **OH5.** `CommitLiveness` is Hydrozoan's — the slow path is unchanged and so is the guaranteed commit — harvested as `DecidedOpt`. What the arc adds is that a slot whose leader produced no candidate is skipped by the guaranteed quorum alone: `qCert ≤ q ≤ |T|` blames, and every decision-round block vacuously evidence for nothing, with no synchrony and no fault-count hypothesis: ```lean def SkipLiveness (U : OptUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (k : ℕ), -- for any set T and slot k: T ⊆ (Correct : Finset Replica) → -- T holds only correct replicas ... q Replica ≤ T.card → -- ... and is at least a DAG quorum, PopulatedOn U.toBlockUniverse T (S.slotRound k + 1) → -- T fills the voting round ... PopulatedOn U.toBlockUniverse T (S.slotRound k + 2) → -- ... and the decision round, (∀ L, ¬ IsLeaderBlock U.toBlockUniverse k L) → -- and no candidate exists: ∀ V : View U.toBlockUniverse, -- then, on any view caught up V.CoversUpto (S.slotRound k + 2) → -- ... to the decision round: SkippedLeaderOpt U.toBlockUniverse k ∧ -- the slot skips directly, DecidedOpt U V k none -- and the verdict is output ``` In Hydrozoan the same skip needs `qFast` blames, which only `Correct` can supply when the actual faults fit `p`, and is opportunistic. The restriction to candidate-less slots is necessary: with a candidate present, `f` Byzantine votes for it make every correct decision-round block evidence whenever `f ≥ tPlain`, which at the minimal committee is exactly *c* = `k` = 0 — FinWhale's attack, exhibited on data (`OA`) — and the slot resolves indirectly. `FastLatency` at `pOpt` stays outside the statement, one more actual fault than Hydrozoan's admits: ```lean def FastLatency (U : OptUniverse Replica BlockId) : Prop := ∀ (R k : ℕ), -- for any round R and slot k: (O.byzantine ∪ O.crashed).card ≤ pOpt Replica → -- ACTUAL faults fit pOpt, Synchronised U.toBlockUniverse R → -- all correct synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, Populated U.toBlockUniverse (S.slotRound k) → -- correct fill the propose round ... Populated U.toBlockUniverse (S.slotRound k + 1) → -- ... and the voting round, S.leader k ∈ (Correct : Finset Replica) → -- and the leader is correct: ∃ L, IsLeaderBlock U.toBlockUniverse k L ∧ -- then a candidate exists ... FastCommitOpt U.toBlockUniverse L (S.slotRound k) -- ... and it fast-commits ``` **OH6** is totality and the descent over `DecidedOpt`, the relation's theorems at the rule's rung choices; without a tie the evidence rung fires on any candidate clearing it, and that this is at most one is slot agreement's business. **OH7** is `RunDecidesBelow` over `DecidedOpt` with Hydrozoan's `FairRunOn` and `RunsRecur` verbatim. ### 23.4 Grounding **OH8.** `WaveRobinFair` is Hydrozoan's. Realizability quantifies over the schedule and exhibits an `OptUniverse`: ```lean def HypothesesRealizable : Prop := ∀ (Replica : Type) [Fintype Replica] [DecidableEq Replica] [OptimalFaults Replica] [Slots Replica] (T : Finset Replica) (N : ℕ), q Replica ≤ T.card → -- a quorum-sized T: ∃ U : OptUniverse Replica ℕ, -- some Optimal universe is (∀ b ∈ U.ids, (U.block b).creator ∈ T) ∧ -- authored by T alone, (∀ r, r ≤ N → PopulatedOn U.toBlockUniverse T r) ∧ -- populated to N SynchronisedOn U.toBlockUniverse T 0 -- and synchronised throughout. ``` Thedef HypothesesRealizable : Prop := ∀ (Replica : Type) [Fintype Replica] [DecidableEq Replica] [LeanDag.Hydrozoan.Faults Replica] (T : Finset Replica) (N : ℕ), q Replica ≤ T.card → -- a quorum-sized T: ∃ U : BlockUniverse Replica ℕ, -- some universe is (∀ b ∈ U.ids, (U.block b).creator ∈ T) ∧ -- authored by T alone, (∀ r, r ≤ N → PopulatedOn U T r) ∧ -- populated to the horizon SynchronisedOn U T 0 -- and synchronised throughout.: ```lean def GroundedProgress : Prop := ∀ (n : ℕ) (hn : 0 < n) [OptimalFaults (Fin n)], letI : Slots (Fin n) := waveRobin n hn -- under wave-aligned rotation, ∀ k : ℕ, ∃ b, k ≤ b ∧ -- past any slot k, ∃ U : OptUniverse (Fin n) ℕ, -- some Optimal universe (∀ i ∈ U.ids, (U.block i).creator ∈ Correct) ∧ -- of correct authors only: ∀ V : View U.toBlockUniverse, -- on any view caught up to V.CoversUpto (b + 4) → -- ... the decision round, it (∃ L, DecidedOpt U V b (some L)) ∧ -- commits b ∀ i, i < b → ∃ v, -- with every slot below DecidedOpt U V i v -- decided. ``` ### 23.5 Findings for the paper - **The evidence rung needs no tie-break**: two candidates cannot both clear it, so the *argmin digest* of the paper's *DecideFromAnchor* is vacuous, and uniqueness is a theorem (OH3). - **The skip is a liveness claim for candidate-less slots and not otherwise** (OH5), landing one round later than Hydrozoan's; the paper's remark on FinWhale's attack is exhibited on four replicas. - **`f ≥ 1` on fast/fast agreement is exact** — necessary at `f = 0` with slack, sufficient by non-equivocation (OH1). - **The validity rule is implied by the good case**: leader exclusion holds in every *T*-only synchronised universe, so "the DAG-building layer never blocks after GST" is a consequence of synchrony and validity there, not a further assumption (OH8). - **The always-fast point** `k = 2f + c − 2`, `n ≥ 5f + 3c − 1`, sits on the arc's four- and seven-replica witnesses. ### 23.6 Witnesses Every definition is exercised by `decide` before anything is proved from it, on `Fin 4` with `f = 1`, *c* = `k` = 0 — FinWhale's minimal instance, where Hydrozoan has no usable fast path and this arc commits on three of four votes — on the crash-only `Fin 3`, on `Fin 7` lifted from Hydrozoan's model, and on `Fin 20`, the reference implementation's mixed configuration where every quorum is distinct. The rule is exhibited on a sixteen-block universe in which the Byzantine leader equivocates and a decision-round block witnesses it, under one slot per round and under two, and on the same table with one block more, a valid Hydrozoan universe over which no `OptUniverse` exists (`UbadX`). A thirty-block universe derives all six routes, in particular the evidence rung with no certificate anywhere; the seam's headline universe has witnessing decision blocks; `OA` is FinWhale's attack on the skip. Liveness is applied end to end — the slow path where the fast one cannot fire, the fast commit at exactly `pOpt` actual faults where Hydrozoan's premise is false, the guaranteed skip, the ladder verdicts cross-checked against the direct ones through slot agreement, a steady-state universe and its variant synchronised from round 1 and provably not from 0, the sub-quorum negative — and every `holds` is pinned by `#guard_msgs` in `Axioms.lean`. The witness headers record what a four-replica committee cannot exercise and defer it to a committee of five or more. ### 23.7 Through the properties *(modules `LeanDag/OptimalHydrozoan/Carrier.lean`, `LeanDag/OptimalHydrozoan/Helpers/Banded.lean`, `LeanDag/Integration/OptimalMechanisms.lean`, `LeanDagTest/Barnacle/Rules/OptimalHydrozoan*/`)* Optimal-Hydrozoan has a carrier of its own, `OptimalHydrozoanProperties.optimalRule`: its records, read by the relation through their projection to Hydrozoan's universe (`toDagRuleVia OptUniverse.toBlockRecord`), the four properties and a support (`optSupport`, and a fast-path support at the optimised threshold), and every mechanism cell an instance of the generic theorem (§16.1). **Leader exclusion is a clause of validity.** `OptUniverse` is the block record at `ValidOpt`, Hydrozoan's validity with `Clause.leaderExcluded` (§2.4): a block whose parents have voted for two distinct blocks of one replica references nothing by that replica. The clause names no schedule — it reads two levels of references and nothing else — which is what a DAG-building layer can enforce, and it yields exclusion at every schedule (`OptUniverse.leader_excluded`), the form the relation's laws hold under. FinWhale's validity carries the same clause (§20.1). Because it is a clause, the universe is `Mechanised` and `CopyStable` by the family's instances, so the cut, the copy fill and re-genesis preserve it with nothing proved per mechanism: the carrier reads as records with every map the identity (`OptimalHydrozoanProperties.onRecord`), and every verdict cell of the cut (HI7), the copy fill (HI9) and re-genesis is `Properties/Arcs/Record.lean` at that instance. The core's `skipFill`, whose self reference grafts the recovering replica's anchor onto the donor's references, adds an edge and is not used here. Liveness across both is `Support.live_of_truncates` and `Support.live_of_sustains` at `optSupport`. **As a Barnacle rule** (HI6). `ofAnchoredVia` at the same projection; the laws are the relation's under the exclusion every record supplies. **What a deployment gets** is the headline at the rule (HI10): `OptimalHydrozoanProperties.safety` across any stack of the two mechanisms and re-genesis, and `OptimalHydrozoanProperties.progress` on any execution meeting `optSupport`'s `live`. The inclusion half of liveness is absent, as for Hydrozoan, because the model carries no self-parent clause. ## 24. Satisfiability Every structure carrying conditions is exhibited satisfiable by a concrete model over four validators at `f = 1`. This is a substantive component of the development rather than a testing exercise: an unsatisfiable hypothesis renders every theorem above it vacuous, and vacuity is not otherwise detectable. | Model | Satisfies | |:---|:---| | `Ugrow N` | `Populated` and `Synchronised`, at every horizon `N` | | `ugrowHonest` | `Delivery` with a genuinely partial view: the Byzantine validator withholds, and a quorum nonetheless survives | | `ugrowTimingPace` | `ViewPace` in lockstep, with a rated `2^n` backoff | | `ugrowSkewPace` | `ViewPace` with nonzero drift and delay, its spread pinned at the collapse bound `Δ + proc` (CU1) | | `ugrowStuckPace` | `ViewPace` genuinely stuck: `top = 0` under a horizon of `5`, round `1` unpopulated | | `ugrowLag` | `ViewPace` from a spread of ten, collapsing to exactly `Δ + proc` (CU4) | | `ugrowSkewCorrect`, `ugrowSkewDelivery` | a pacing structure over `Correct`, and the `Delivery` it induces (V19) | | `Dtwin` | `UniformBudget Dtwin 3` and `ByzBudget Dtwin 0`: the acceptance budget, on a schedule with a real equivocation | | `rrSlots` | `Slots`, round-robin, satisfying `FairWithin T (f+1)` and `BoundedSpacing 3` | | `waveRobin n` | `Slots` at *every* `n`: the wave-aligned rotation, whose `FairRunOn Correct 3` and `SpansEligible 3` are theorems with no premise beyond the fault model (L12) — the one schedule family not pinned to a committee | | `Mysticeti/Model.lean` | six `BlockUniverse` instances exercising the safety definitions | | `Ucrash N`, `ucrashMsg` | `SkipMsg`: a crashed line, the message against it, and the fill (SS7) | | `Usun`, `Usk`, `U44`, `U3` | `PartialRun` with a rising count, a skipped slot past the threshold, the real `Good` with the theorems yielding verdicts, and a bare majority under attack (BN3, BN8, BN9, BN10) | | `ucrashJump` | `JumpMsg`: the compact core of `ucrashMsg`, elaborating to the same fill (SS11) | | `demotePolicy`, `run7` | a genuinely adapting policy and its total runs, at the core's carrier (AL8) | | `Uhyb4`, `Uhyb9` | `HybridFaults`, `HonestNoEquiv`: one crash at four validators; the tight hybrid committee (H9) | | `UtightA`, `UtightB` | the one-short committee: agreement refuted at every threshold (H10) | | `Unemo` | `CrashFaults` at the tight crash committee: three validators, one halted line, every decidable rule settled by `decide` (NN9) | | `skTight` | the fill of `Ucrash` at which coverage is refuted (I4) | | `Ucut`, `Uregen`, `urecover` | the severed validator, its re-genesis, and the catch-up message over it | Three of the models are tight, which is what renders the constants meaningful. - `ugrow_not_populated_succ` establishes `¬ Populated (Ugrow N) (N+1)`: the family reaches round `N` and stops, so the horizon is exact. - `ugrowSkewPace` lies on the boundary of the wait bound. Its spread is `2`, its `delay` is `2`, its `proc` is `0` and its `timeout` is `4`, so that the spread equals the collapse bound `Δ + proc` and `2Δ + proc = timeout`: every inequality of the wait bound holds with equality, and the accumulated honest floor of the rush bound (CU5) is met with equality on the same schedule. The constant is therefore exact rather than a conservative estimate. - `rrSlots_fairWithin` gives the window `f + 1 = 2`, and `f + 1` is forced, the validators outside the reliable set being permitted to occupy consecutive positions in the rotation. One negative observation should be recorded. A model exhibiting *round spread* — correct validators separated by many rounds — while still committing is impossible at `f = 1`, since `|Correct| = 3 = n−f` exactly, so that every correct validator is required for a quorum and none may lag. Such a model requires `f ≥ 2`. This is the combined fault budget of §4.2 appearing as a concrete obstruction rather than as an inequality. The conditions of §§7–15 are witnessed in the same style, at their own boundary instances: chain quality on `Ucens` (CQ8) — the one model that is simultaneously CQ1's tightness witness (`missingAt = {3}` at every layer of the committed cone, exactly `f`) and the censorship exhibit (`Synchronised` fails at every round while the commit stands); `DoSValid` satisfiable and biting (`Uexcl`, with the exclusion chain and a commit after it), the budget satisfiable at its exact constant (`UniformBudget Dtwin 3` with `ByzBudget Dtwin 0`), the horizon computed and its statute of limitations exhibited (`chop Uexcl 2`, `chop Umerge 1`), the attested base sandwich tight at the bottom (`Base Utwin 1 0 = {1,2,3}`), and every Odontoceti rule and all four `Decided` constructors at `n = 6, f = 1` (`Uodo`, `Uskip`, `Utwin6`), including the two-twin configuration that motivates the canonicity premise (`utwin6_both_pass` (O11)). The reactive schedule is witnessed at §11.4's `ugrowReactive`, and Safe Skip on `Ucrash` (SS7): the fill's reference sets and cardinality computed by `decide`, the gap populated, the filled candidate skipped, and `decided_fill_of_persist` applied to the full view. The jump message is witnessed on the same family (SS11): `ucrashJump` carries `ucrashMsg`'s four names and no line, its elaboration reproduces the hand-written line (`ucrashJump_line_eq`), and the two denotations agree (`ucrashJump_denote_eq`). Adaptive leaders are witnessed on `demotePolicy` (AL8): the same DAG under a reassigned leader commits a different block on both rules, a skip two slots below moves a later slot's leader off the base rotation, and two views' partial runs are constructed and shown identical by AL3. The hybrid model is witnessed at both of §14.7's committees (H9), and the one-short committee's two attack universes carry the tightness refutation (H10). The crash arc is witnessed on `Unemo` (NN9), §15.5's three-validator committee with one halted line: the crashed slot is settled by `indirectSkip` off two adjacent commits, `Populated` fails past the horizon, and the round-robin fairness clause is proved rather than assumed. The integration constructions are witnessed on the scenario they all describe: validator `3` of `Ucrash`, crashed after its genesis block and severed by a horizon at round `1`. `Ucut` is the truncation and `ucut_severed` confirms by `decide` that the validator has no block in it at all; `Uregen` adds the derived genesis, with its uniqueness clause and `dosValid_addGenesis` checked over it; and `urecover` builds the catch-up message anchored on that block, so the composite recovery of §16.5 is exhibited end to end rather than assumed composable. The refutation of §16.3 is witnessed on the same family (`ucrash_not_synchronisedOn`), which is what makes it a refutation rather than an unsatisfiable hypothesis. --- ## 25. Mechanisation The development comprises approximately 78,000 lines of Lean 4 (v4.32.2) against Mathlib, of which some 57,000 constitute the library and 21,000 the models of §24 and the witness files of the arcs. A full build reports no errors. **Axiom audit.** Every principal result — among them `reaches_of_quorum_support`, `exists_common_correct_ancestor`, `AnchoredRule.decided_agree` (M6, once for every rule), `AnchoredRule.commitSeq_agree` (M7), `AnchoredRule.outputAt_agree` (M9), `ViewPace.populatedOn` (V17), `commits_recur_on`, `ViewPace.commits_recur_via_pace`, `all_decided_below_of_fairRun` (L10), `card_history_le'`, `dos_resistance`, `decided_chop_iff`, `decided_agree_chop`, `card_retained_le`, `bootstrap_agree`, `chop_chop`, `Odontoceti.odontocetiLaws`, `OdontocetiProperties.safety` and `Odontoceti.all_decided_below_of_fairRun`, `chain_quality`, `committed_of_correct_block`, `decided_fill_of_persist` (SS5) and `decided_fill_agree_of_properties` (SS6), `SkipMsg.skipFill_eq_of_core` (SS9) and `JumpMsg.denote_eq_of_core` (SS10), `run_agree` (AL3) and `run_exists_of_support` (AL5), `Hybrid.hybridLaws` (H6), `HybridProperties.safety`, `hybrid_bound_necessary` (H10), `Nemo.nemoLaws` (NN5), `Nemo.outputAt_agree` (NN6) and `Nemo.all_decided_below_of_fairRun` (NN8), and `MysticetiProperties.safety` and `MysticetiProperties.liveness` (I7) — depends on exactly `propext`, `Classical.choice` and `Quot.sound`, which constitute the whole axiom set of Lean 4. No result depends on `sorryAx`, on any bespoke axiom, or on `native_decide` and the extended trusted base it entails. **The common layer, `Common/`.** What every rule is defined in terms of; no module here names a rule. | Module | Contents | |:---|:---| | `Common/Counting.lean` | the one counting fact: two sets whose sizes sum past `n + m` meet outside any set of at most `m` (`exists_mem_inter_notMem`), and its contrapositive | | `Common/Validators.lean` | the fault model (`n ≥ 3f+1`); T0 | | `Common/Slots.lean` | the slot schedule every rule runs on; its constructors (`uniform`, `uniformSingle`, `identity`, `waveRobin`) | | `Common/Schedule.lean` | conservativity of the pipelined schedule | | `Common/Leader.lean` | what a schedule says about a record: the candidates of a slot (`IsLeaderBlock`, `leaderBlocksAt`), an honest leader's single candidate, the voting round, and the blamers of a slot (`slotBlamers`, `slotBlames`, `slotBlamesIn`) with their congruence across schedules | | `Common/Block.lean` | `Block`, `ValidWrt`; T0′ | | `Common/BlockRecord.lean` | the block record and its view, and a view as a record (`View.toRecord`); `chopBlk`; what a validity predicate owes the mechanisms (`Mechanised`, `CopyStable`), and the validity family `ValidAt` with its clauses, which discharges it for every rule | | `Common/Record/Chop.lean`, `Common/Record/Fill.lean`, `Common/Record/Genesis.lean` | the cut, the fill and re-genesis, built once at the record | | `Common/BlockDag.lean` | `BlockUniverse` and `View` as the record at `ValidWrt`; the core's validity is mechanised; T1 | | `Common/CausalHistory.lean` | `Reaches` at any block record; T2, T6a | | `Common/Support.lean` | counting vocabulary at any block record, in the record and in a view (`supportersIn`, `blamesIn`); what a view holds (`heldAuthors`, `HoldsAtLeast`); the certificate stack over a vote relation (`carriedVotes`, `CarriesVotes`, `certificatesAt`); the hitting lemma, propagation and coverage at any quorate record; the counting bounds under non-equivocation | | `Common/Participation.lean` | `PopulatedOn` and `SynchronisedOn`, at raw block data and at any block record | | `Common/Ledger.lean` | the ledger at any block record: `commitSeq`, `ledgerSet`, `OutputAt`; monotonicity, uniqueness, and agreement of agreeing assignments | | `Common/History.lean` | causal history as a `Finset`, at any block record; a block of a set in an anchor's cone (`LinkedVia`) and the votes in the cone (`coneSupporters`) | | `Common/Persistence.lean` | T3 | | `Common/CommonCore.lean` | T3a, T3c | | `Common/Anchored.lean` | the anchored decision relation every rule is an instance of: `AnchoredRule` (wave, direct commit and skip on a view, graded rungs, ties), `EligibleAt`, `Decided`, what a rule owes (`Laws`), and agreement, monotonicity and the ledger once | | `Common/Anchored/Band.lean` | the carrier of a rule (`toDagRule`, `toDagRuleOn`), the four properties at it, the band laws and the band induction once, the indirect property | | `Common/Anchored/Bounded.lean` | the bounded relation, its congruence across schedules, totality at an anchor and the descent below a committed run | **The core, `Mysticeti/`.** The pipelined rule and its route, as one instance of the common layer. | Module | Contents | |:---|:---| | `Mysticeti/Rule.lean` | the commit rule; M1–M5; the core as an anchored rule (`coreAnchored`) and its laws (`coreLaws`), M6–M9 being the relation's | | `Mysticeti/Liveness.lean` | L0, L4–L6; the committed-run results at the core's schedule shapes | | `Network/Quorum.lean` | the DoS capstones, production bundled with the storage bound | | `Mysticeti/ViewPace.lean` | the route (§6.9): the structure, V1, V4, coverage, production, the spine, and the quantitative results L8a, L9, L11 | | `Mysticeti/Quantitative.lean` | the rated hypotheses; L8b | **The arcs** (§§7–23). All but `Integration/` consume the core read-only; §16 weakens one hypothesis of §12, for the reason given there: | Module | Contents | |:---|:---| | `DoS/Exposure.lean` | `ExposedIn`, `DoSValid`; exposure ≤ `f` per cone | | `DoS/Acceptance.lean`, `DoS/Counting.lean` | the acceptance rule; view size from history size | | `DoS/SelfParent.lean`, `DoS/Adoption.lean`, `DoS/Pedigree.lean` | the adoption collapse; pedigrees; the general per-cone bound | | `DoS/Density.lean` | histories are almost all of the correct past | | `DoS/Novelty.lean` | the novelty budget; the budget sandwich; `dos_resistance` | | `DoS/Composition.lean` | the two conditions composed; the pool freezes | | `DoS/Exclusion.lean` | liveness survives exclusion; the correct backbone | | `GC/Chop.lean` | the core's cut as the record's; per-slot verdict invariance | | `GC/ChopDecided.lean` | the cut, the induced schedule and their fields; the verdict transport is `decided_chop_iff` in `Properties/Arcs/GC.lean` | | `GC/Window.lean` | windowed novelty and stores; `card_retained_le` | | `GC/AttestedBase.lean` | the inexact certificate, sandwiched | | `GC/Bootstrap.lean` | window completeness; the joiner's view; `bootstrap_agree` | | `GC/Horizon.lean` | `chop_chop`; heterogeneous-horizon agreement; the depth rule | | `Odontoceti/Rules.lean` | the two-round rules; the arithmetic core O1–O4′ | | `Odontoceti/Decision.lean` | Odontoceti as an anchored rule (the thick link, the order as its tie) and its laws | | `Odontoceti/Liveness.lean` | O7–O10 | | `Reactive/Basic.lean` | the reactive dichotomy; the vote; the fast path | | `Reactive/Mysticeti.lean` | the certificate stage; reactive liveness, three rounds | | `Reactive/Odontoceti.lean` | reactive liveness, two rounds, from the core alone | | `SafeSkip/Data.lean` | the message; the two readings of a filled block and what a reading owes | | `SafeSkip/Basic.lean` | the core's fill as the record's, its self-referencing block valid; production restored | | `SafeSkip/Invariance.lean` | conservativity at the rule layer; verdict invariance; agreement across a recovery | | `SafeSkip/Jump.lean` | the self-parent function; the derived line; the jump message and its elaboration | | `Adaptive/Basic.lean` | epochs; the induced instance | | `Adaptive/Policy.lean` | the reassignment policy and its clauses | | `Adaptive/Run.lean` | the adaptive run; safety as uniqueness; conservativity; the agreed ledger | | `Adaptive/Liveness.lean` | the bounded descent; the fairness clause; existence | | `Properties/Record.lean` | a carrier on the record (`DagRule.OnRecord`), with its invariant and view maps; every mechanism's witnesses, once | | `Properties/Arcs/Record.lean` | every verdict cell of the cut, the fill and re-genesis, once, at any carrier on the record | | `Common/Record/Invariant.lean` | what an invariant a carrier adds owes the mechanisms (`Invariant.Mechanised`) | | `Adaptive/Joiner.lean` | the joiner across a cut: horizon-stability, the schedule transformers commute, agreement at the adaptive schedule | | `Integration/AdaptiveMysticeti.lean`, `Integration/AdaptiveOdontoceti.lean` | the two rules as instances of the adaptive mechanism: their runs, agreement and existence as corollaries of the generic theorems | | `Hybrid/Faults.lean` | the hybrid model; the derived instance; `HonestNoEquiv`; the counting core | | `Hybrid/Rules.lean` | the rules at the admissible interval; the arithmetic core H2–H5 | | `Hybrid/Decision.lean` | Hybrid as an anchored rule at threshold `k`; its laws under `HonestNoEquiv` | | `Hybrid/Liveness.lean` | the liveness chain at quorum `q` | | `Hybrid/Conservativity.lean` | the crash-free collapse onto Odontoceti | | `Integration/Preservation.lean` | `HonestNoEquiv` across the cut and the fill | | `Integration/Coverage.lean` | coverage under the fill, at the core: the extension theorems at the fill's witnesses | | `Integration/Joiner.lean` | the joiner at the core's cut | | `Integration/Retention.lean` | anchor retention; the outage bound; the severed chain | | `Integration/ReGenesis.lean` | re-genesis at the cut; convergence; the exposure condition | | `Integration/Exposure.lean` | the fill's cone growth; the enforceable exposure check | | `Integration/DeliveryFill.lean` | the fill's delivery layer, and the budgets over it | | `Integration/Margin.lean` | the budget without the author; severance and the fault budget | | `Integration/CommonTarget.lean` | fills against a common-core target | | `Properties/Carrier.lean` | `DagRule` with its three laws; `RebasedAbove` | | `Properties/Band.lean`, `Agree.lean`, `Candidate.lean`, `Commit.lean` | the four properties: `Banded`, `Agree`, `CommitsCandidate`, `Indirect` | | `Properties/Support.lean`, `Derived/LeaderCommits.lean` | the support, its two laws, `live`; `LeaderCommits` derived | | `Properties/Optional/` | `CommitsDirect`, `SkipsUnsupported`, `Quorate`, `SelfParent`, `NoEquiv` | | `Properties/Derived/` | `Persist`, `LocalTruncate`, `Descends`, `DecidedBelow`, progress | | `Properties/Extends.lean`, `Sustain.lean`, `Truncate.lean`, `Compose.lean` | the mechanism relations and their composition | | `Properties/Arcs/GC.lean`, `SafeSkip.lean`, `Mysticeti/Liveness.lean`, `Quality.lean` | the generic theorems: cut, fill, prompt skip, liveness across a mechanism, chain quality | | `Properties/Arcs/Stack.lean`, `Headline.lean` | any stack of mechanisms is one; the safety and liveness headlines | | `Timed/Coverage.lean` | the timed model: coverage, `OfCoverage`, the bridge into `live` | | `Timed/Extension.lean` | coverage under an extension: refuted for a set holding a novel author, preserved for any other | | `Mysticeti/Properties.lean`, `Odontoceti/Properties.lean`, `Nemo/Properties.lean`, `Hybrid/Properties.lean`, `MahiMahi/Properties.lean`, `FinWhale/Carrier.lean`, `Hydrozoan/Helpers/`, `OptimalHydrozoan/Carrier.lean`, `Reactive/MysticetiProperties.lean` | each rule's carrier, properties, support and headlines | | `Nemo/Record.lean`, `FinWhale/Record.lean`, `Hybrid/Record.lean`, `HydrozoanMechanisms.lean`, `OptimalHydrozoan/Record.lean`, `ReactiveMechanisms.lean`, `StackRules.lean` | the mechanism cells at each rule: witnesses and instances | | `Nemo/Basic.lean` | the majority quorum and its intersection; crash validity; the universe with universal non-equivocation | | `Nemo/Rules.lean` | the wave-two rules: the vote is the certificate; link integrity | | `Nemo/Decision.lean` | Nemo as an anchored rule with no direct skip; its laws, without hypotheses | | `Nemo/Liveness.lean` | the crash bound and its bridge; the commit half; the descent | | `MahiMahi/Model/Rules.lean`, `MahiMahi/Model/Decision.lean` | the rule at wave `w`: canonical support, certificates, the direct rules, and the wave as an anchored rule | | `MahiMahi/Model/Good.lean`, `MahiMahi/Model/Unpredictable.lean` | the committed candidates of a wave; the clause in both forms; agreement below a round | | `MahiMahi/Safety/`, `MahiMahi/Counting/`, `MahiMahi/Liveness/`, `MahiMahi/Synchrony/` | the four statements and their proofs (MM1, MM2, MM3, MM5) | | `MahiMahi/Helpers/` | the generated lemma layer | | `BlackMarlin/Model/Rules.lean`, `BlackMarlin/Model/Decision.lean` | the anchor rotation; support, the link, the commit rule; the same rules read from a view | | `BlackMarlin/Model/Round.lean` | the round rule of L38–L41, and the pacing structure it induces | | `BlackMarlin/Model/Ledger.lean` | the flush record of `commit`'s descent, and the ledger it defines | | `BlackMarlin/Model/Descent.lean` | `maxAnchor`, the tie-break of L24, and the record the descent computes | | `BlackMarlin/Model/Order.lean` | the sort `τ`, the filter of L27, and the list a validator outputs | | `BlackMarlin/Model/Repair.lean` | the support-preferring side-condition, and a descent that meets it | | `BlackMarlin/Safety/`, `BlackMarlin/Liveness/`, `BlackMarlin/Reactive/`, `BlackMarlin/Agreement/`, `BlackMarlin/Ledger/`, `BlackMarlin/Descent/`, `BlackMarlin/Order/`, `BlackMarlin/Repair/` | the eight statements and their proofs (BM1–BM7, BML1–BML5, BMR1–BMR6, BMA1–BMA4, BMD1–BMD6, BME1–BME5, BMO1–BMO9, BMP1–BMP13, BMV1–BMV3, BMT1–BMT3) | | `BlackMarlin/Helpers/` | the generated lemma layer | | `Barnacle/Model/Rule.lean` | the base-protocol interface: data and laws (A1–A4), the candidate predicate, update rules | | `Barnacle/Model/Schedule.lean`, `Barnacle/Model/Window.lean`, `Barnacle/Model/Run.lean` | the schedule of a configuration; the window count and the AIMD rule; the run and the ledger | | `Barnacle/Model/Live.lean`, `Barnacle/Model/Heads.lean` | the liveness clause with its gap; the descent laws and runs of heads | | `Barnacle/Model/Anchored.lean` | an anchored rule as a base rule and as a live rule, at a fault model | | `Barnacle/Window/`, `Barnacle/Agreement/`, `Barnacle/Healthy/`, `Barnacle/Validity/`, `Barnacle/Ledger/`, `Barnacle/Conservativity/`, `Barnacle/Aimd/`, `Barnacle/Progress/`, `Barnacle/Heads/` | the seven statements and their proofs (BN2, BN3, BN5, BN6, BN7, BN8, BN9) | | `LeanDagTest/Barnacle/Rules/` | the rules as base and, where stated, live rules, their laws and their liveness under round-robin (BN10) — instantiations of the generic results, so they sit with the witnesses that consume them | | `Barnacle/Helpers/` | the generated lemma layer | | `Hydrozoan/Model/Faults.lean` | the hybrid fault model, the five thresholds, the two pools | | `Hydrozoan/Model/Block.lean`, `Hydrozoan/Model/BlockUniverse.lean`, `Hydrozoan/Model/View.lean` | the shared block with no payload, and Hydrozoan's validity; the universe with non-equivocation for non-Byzantine authors; views; reachability | | `Hydrozoan/Model/DirectRules.lean`, `Hydrozoan/Model/IndirectRules.lean`, `Hydrozoan/Model/Decided.lean` | votes, certificates, the three direct rules, on the record's counting vocabulary; the rung tests; Hydrozoan as a two-rung anchored rule | | `Hydrozoan/Model/Liveness.lean` | the liveness package: population, synchrony, the eventual view | | `Hydrozoan/ThresholdArithmetic/`, `Hydrozoan/DirectSafety/`, `Hydrozoan/SlotAgreement/`, `Hydrozoan/PrefixAgreement/`, `Hydrozoan/DirectLiveness/`, `Hydrozoan/IndirectLiveness/`, `Hydrozoan/EventualDecision/`, `Hydrozoan/Grounding/` | the eight statements and their proofs (HZ1–HZ8) | | `Hydrozoan/Helpers/` | the generated lemma layer; `Record.lean`, the carrier on the record | | `OptimalHydrozoan/Model/Faults.lean`, `OptimalHydrozoan/Model/Universe.lean` | the allowance `pOpt` and the per-block thresholds; Optimal's validity, Hydrozoan's with `Clause.leaderExcluded`, and the universe at it | | `OptimalHydrozoan/Model/DirectRules.lean`, `OptimalHydrozoan/Model/IndirectRules.lean`, `OptimalHydrozoan/Model/Decided.lean` | fast evidence, the no-evidence skip, the evidence rung; Optimal-Hydrozoan as a two-rung anchored rule with no tie | | `OptimalHydrozoan/ThresholdArithmetic/`, `OptimalHydrozoan/DirectSafety/`, `OptimalHydrozoan/SlotAgreement/`, `OptimalHydrozoan/PrefixAgreement/`, `OptimalHydrozoan/DirectLiveness/`, `OptimalHydrozoan/IndirectLiveness/`, `OptimalHydrozoan/EventualDecision/`, `OptimalHydrozoan/Grounding/` | the eight statements and their proofs (OH1–OH8) | | `OptimalHydrozoan/Helpers/` | the generated lemma layer | | `Quality/Coverage.lean` | per-commit and ledger coverage (CQ1–CQ3) at the core, over `Arcs.coveredAt` | | `Quality/Inclusion.lean` | post-`R` inclusion (CQ5, CQ6) | | `Quality/Capstone.lean` | the windowed bounds and `chain_quality` (CQ7) | | `LeanDagTest/` | the models of §24 and the witness files of every arc | **The support graph, extracted.** The dependency structure of the development is not documented by hand: `scripts/DepGraph.lean` walks `Environment.constants` and records, for every declaration, the constants appearing in its type **and in its body** — for a theorem, that body is the proof term, so the edges are the real proof structure rather than the statement's signature. `scripts/depgraph.py` then takes its node set from Appendix A of this report, contracts paths running through unlabelled lemmas, takes the transitive reduction, and renders the result. The full graph is ≈900 declarations and ≈7,700 edges; the two views drawn here are the §6.10 figure and the one below. Two details govern the extraction. `ConstantInfo.value?` returns `none` for *imported* theorems in this Lean version, proofs being loaded lazily, so the proof term is reached by matching `.thmInfo` explicitly; otherwise only statement-level dependencies appear. Private declarations and compiler-generated auxiliaries are kept as pass-through nodes, since a labelled result frequently reaches another only through one of them. Mathlib and core constants are dropped, and safely: they never mention a constant of this development, so no path between two of its results can run through them. ![**The whole development.** The same extraction over every principal result, including the arcs. Reading the columns: the core account occupies the left half, and each arc attaches to it at the results it consumes rather than at the top — the garbage-collection operator (G1) sits beside the structural theorems it re-uses, and Odontoceti's counting core (O1–O4′) is independent of Mysticeti's, converging only at its own agreement theorem. Lean names are omitted for legibility; the full-detail rendering is in `docs/depgraph/`.](depgraph/support-full-compact.svg) The extracted edges are also an independent check on this report's prose, and three of its claims come out exactly as written. `P3′`, the self-parent clause, feeds C1′, B7, G1, SS1 and — since the rotation backbone — RS5, which is §2.2's assertion that safety never consumes it, that its one liveness consumer is §11.5's, and that it is indispensable to the denial-of-service, garbage-collection and safe-skip arcs. `L7 ← N2, P7, P9` reproduces the §4.4 table row for row — coverage drawing on the network column and the two protocol clauses, with non-equivocation nowhere in it, since the route's referencing clause is stated over any authored block and leaves nothing to identify. And `O5 ← O1, O1′, O2, O3, O4′` confirms that Odontoceti's agreement rests on exactly the four counting theorems plus twin uniqueness (§10.3), with `O4′` — the lemma the published argument lacks — evidently indispensable. The companion documents in `docs/` carry the design rationale in more detail than a report admits: `spec.md` (safety), `liveness.md` (liveness), `pipelining-and-multi-leader.md` (the schedule generalisation), `chain-quality.md` (§7), `dos-equivocation-and-growth.md` (§8), `garbage.md` (§9), `odontoceti.md` (§10), `adaptive-leaders.md` (§13), `hybrid-plan.md` (§14), `target-properties.md` (§16, whose opening part is the current statement of the properties), `mahi-mahi.md` (§17), `black-marlin.md` (§18), `minnow.md` (§19), `finwhale.md` (§20), `barnacle.md` (§21), `hydrozoan.md` (§22), `optimal-hydrozoan.md` (§23) and `bespoke-links.md` (the audit of what a mechanism reads), with `related.md` surveying the surrounding literature. Every statement in this report is drawn from the source. --- ## 26. Discussion The first four subsections concern the core account's central design choice — where the synchrony assumption lives; §26.5 draws the lessons of the extensions; §26.6 records what remains open. ### 26.1 Locating the synchrony assumption The synchrony assumption may be stated in terms of views: > beyond GST, if a correct validator holds a view `V₁`, then within Δ the views > of all correct validators contain `V₁`. This is a statement about the network, and as such it is complete: it says everything about delivery that the development requires. It is also attractive formally, `View` being already a first-class structure with inclusion already meaningful and every safety result already view-relative, and it yields L3 immediately, the common view being `View.full`. What it does not do is determine what blocks *look like*. Reference coverage is a property of blocks, and blocks are produced by validators according to the protocol, so it depends on the specification as well as on the network. This is not a deficiency in the assumption; it is a consequence of coverage being a derived property rather than an assumed one (§4.4). Three observations follow, and they are the content of the section. **The protocol must specify a wait, and this is a design obligation rather than a gap in the assumption.** Consider `f = 1` with validators `{A,B,C,D}`, all four correct, and instantaneous delivery, so that the network assumption holds in its strongest form. Suppose the specification directs a validator to build as soon as it holds `n−f = 3` blocks of the round below, and suppose `A`, `B` and `C` are marginally faster than `D`. Each of them then forms the quorum `{A,B,C}` and builds before `D`'s block arrives, so no block of theirs ever references `D`'s. Every block is valid, views converge perfectly, and `SynchronisedOn U Correct R` fails for every `R`. What this exhibits is a badly specified protocol under a well-behaved network. The remedy lies in the specification: P9 directs a validator to wait a full timeout rather than to build on the arrival of a quorum. The example is worth including because the incorrect rule is the natural one — the quorum is exactly what validity requires — and because it shows that promptness and coverage are in tension, which is what P9's two halves jointly resolve. The example does not show a view-shaped assumption to be *inadequate*. View convergence is adequate but **incomplete**: it is the network half of a two-part derivation whose other half is a protocol clause, and §6.9 proves both. What the counterexample shows is that the missing half cannot be supplied by strengthening the network, which is already as strong as it can be — delivery there is instantaneous. The gap is a *race* between arrival and building, which only the builder's own schedule can resolve. **The threshold the specification must meet is `2Δ + proc`, not Δ.** This is the one point at which a network parameter enters the protocol's constant, and it is the substantive quantitative result (§6.10). Validators enter a round at different times, so a wait must accommodate the propagation bound *and* the spread between validators; the pacemaker's catch-up rule contracts that spread to `Δ + proc` at the first fully-post-GST round (§6.11), so the threshold is the collapsed spread plus one delivery — with no hypothesis anywhere about how the validators started. At instantaneous entry, `proc = 0` and the threshold is `2Δ`. Because Δ is not known to an implementation, no constant can be fixed in advance. A backoff is the specification's response — a search for a sufficient constant, written into the algorithm — and its only relevant property is that the search terminates (§26.2). **The network guarantee must be indexed to the moment of building.** A block's references are fixed at its construction, so what bears on the derivation is not what a validator holds eventually but what it held when it built. `View.ids` is a finite set of identifiers with no temporal index, so a view-convergence statement cannot be applied directly. `Delivery.held : Validator → ℕ → Finset BlockId` supplies the index for the storage arcs — `held v n` denotes what `v` had in hand when building for round `n+1` — and `ViewPace.holds` supplies it for liveness, indexed by the instant, with `built` ordering the two. The requirement is the index, not the vehicle. This is an observation about formalisation, and it is the reason `SynchronisedOn` is stated on `refs`. ### 26.2 Why coverage is derived rather than specified Reference coverage could not have been made a clause of the protocol, which is the deeper reason it appears as a derived property. `SynchronisedOn` refers to `Correct`, a model-level object, so an instruction to reference every correct block of the round below would name two quantities a validator cannot determine: which of the blocks it holds are correct-authored, and whether all of them have arrived, a missing block being indistinguishable from one never published. What a validator can be directed to do is wait a fixed period, build upon whatever has arrived, enter any round it sees evidence of, and increase the period when progress fails. These are `waits`, `catchup` and the backoff — P9 and P11 together with R1 — all of them executable. The signal driving the backoff is the difficulty. Before GST no period is sufficient, and nothing permits a validator to detect this directly; what it observes is that commits have ceased. The feedback loop which delivers coverage is therefore driven by liveness failure, the very condition being proved away. This is a feedback loop rather than a circularity, but it means the argument cannot rest on modelling the loop's dynamics. The development accordingly does not model it. What the coverage derivation consumes is a threshold — the timeout remains above `2Δ + proc` from some round onwards — with no condition on shape, rate, or driving signal. §6.10 carries this to its conclusion: with Δ known, a constant timeout of `2Δ + proc` suffices and the loop disappears. ### 26.3 Consequences of the abstraction 1. The consensus argument is purely combinatorial, involving round indices and finite-set cardinalities. Under a message-level assumption every statement would carry instants. 2. The temporal content is confined to a single module and consumed through a single definition. 3. The condition admits two independent derivations (§6.7, §6.8) and a third, quantitative route (§6.11), against an unchanged statement. 4. The condition composes with the safety development, mentioning only `U.ids`, `U.block` and `refs` — the vocabulary that development already employs. ### 26.4 Costs Δ does not appear above the interface. Introducing it would require views indexed by an instant and every statement quantified over instants, for no proof content. The quantitative statements recover what is needed *below* the interface without propagating time upward. Coverage being derived rather than assumed does not make it unconditional. The derivation rests on view convergence, and in the absence of a time model the chain must terminate at a network assumption; what the reformulation achieves is to place that assumption where it belongs — on the network, as one clause over views — and to keep it out of every statement above. ### 26.5 Lessons from the extensions Three lessons generalise beyond the particular arcs. **Additivity is a measurement of abstraction.** Every extension of §§7–14 consumes what it builds on unmodified: the DoS arc took the delivery layer and the self-parent clause as found; garbage collection took every theorem verbatim because `chop U G` was arranged to *be* a `BlockUniverse`; Odontoceti took the whole DAG layer because its quorums are the `n − f` the development is parameterised by. When an abstraction is placed correctly, new developments read like instantiations; when it is misplaced, they read like refactors. The rule-parameterised treatment that §3.5 and §10.3 call for is the properties of §16.1, stated over a carrier that every rule instantiates, with the rules' own decision relations untouched: one proof per property and one line per rule. §16 is the exception, and a measured one. §12's fill hypothesis is stated as the fact `v1 ∈ Correct` is used for rather than as membership, because the composition with §14 is otherwise **unstatable**: §14 splits `Correct` into honest and available, and the crash-prone validator Safe Skip serves is honest but not correct. It is one field and one proof line, and it is a different act from a refactor. The rule it suggests is that existing code may be modified when a result cannot otherwise be stated, and not for elegance. The properties reach the frozen arcs through two record fields, a carrier law and a parent structure, each a fact every instance has. **Enforceability is a specification discipline.** The principal result of §8 (`dos_resistance`) quotes only conduct a validator can execute — an author-blind budget, a reference rule — and no condition that consults an identity oracle; the cost of author-blindness is a factor of `f` in a constant, never a theorem. The same discipline shapes §9: horizons are set by local rules, the attested base replaces agreement with `f+1` sampling, and every hypothesis of the bootstrap theorems is checkable by the party it binds. Conditions of this kind survive contact with implementations; conditions that quantify over `Correct` do not. **The value of mechanisation is concentrated where equivocation meets counting.** All four §10 findings — the canonicity gap, the missing uniqueness lemma, the blocks-versus-authors ambiguity, the exact-complement subtlety — live where an equivocating author interacts with a counting argument, precisely the territory that uncertified DAGs annexed when they discarded certificates, and precisely where hand proofs compress the most. The counterexample behind the canonicity gap fits in six validators and twenty-five blocks; what was needed to find it was not scale but the obligation to state the indirect rule precisely enough to fail to prove it. ### 26.6 Limitations The quantitative bounds are established (§6.10). The following remain open. **The backoff loop.** `Rated` and the threshold of R4 are stipulated as clauses of the specification; no realistic adaptive scheme is shown to satisfy them, and the feedback mechanism of §26.2 is not modelled. Moreover `ViewPace.timeout : ℕ → ℕ` is indexed by round and common to the reliable set, so that a per-validator backoff — in which validators increase their timeouts at different moments — cannot be expressed, let alone shown to converge. This requires a refinement of the structure. **Wall-clock latency.** `Delay(Δ)` is a duration, but the total elapsed time to a commit is not derived: converting a bound of the form "`3k + 8` rounds, each of at least `2Δ`" into elapsed time requires a lemma accumulating an upper bound on round duration — the catch-up deadline — across rounds, which is not present. `ViewPace.le_built` relates rounds to time in one direction only. **Byzantine leaders.** §6.10 bounds the wait until the next reliable leader, which sidesteps rather than answers the question of how distant an indirect anchor may be when the leader is Byzantine. **Leader predictability.** `Slots.leader` is an arbitrary function, so nothing distinguishes a schedule an adversary can predict from one it cannot, and targeted denial of service against a known future leader — the network sense, distinct from the storage-exhaustion sense §8 bounds — is invisible to the model. `FairWithin` constrains when reliable leaders occur, not whether they can be anticipated. **Block-level total order.** The blocks released by a single commit are not ordered among themselves, for the reason given in §5.6. **No liveness below P8.** Every liveness result here is conditional on P8, and nothing is offered for executions violating it. [QXS26] proves a *weak liveness* result which is not: without any restriction on round-jumping, every leader block created by an honest validator after GST acquires at least `f+1` certificates from honest validators, so it can never be indirectly skipped, only left undecided. The corresponding statement is not available here, and the obstruction is identifiable. Density (L0) yields `n−f` distinct authors at round `r+1` whenever the DAG reaches above it, hence `f+1` *correct* authors, and coverage makes each of their blocks reference the leader — so `f+1` correct **supporters** is within reach from L0 and `SynchronisedOn` alone, with neither `Populated` nor P8. A certificate, however, requires `n−f` distinct supporters, and `SynchronisedOn` is honest-to-honest (§6.6), so the remaining `f` cannot be obtained; reaching `n−f` supporters requires `n−f` correct authors at `r+1`, which is `Populated` again. [QXS26] bridges the gap with a *predecessor rule* — a validator creating a block must make it a supporter and a certificate where it can — for which this model has no counterpart, P7 constraining what a validator cites given what it holds but not obliging a quorum of supporters to exist. The supporter-level statement is therefore the available result, and it is not their theorem. **Certified DAGs.** The certified variant, in which a certificate round is explicit, is outside the scope of the present development. **The properties' reach.** Black Marlin (§18) commits by round with no slot-indexed relation and has no carrier, so none of §16.1 applies to it. The linearisation of a commit's cone is outside the properties by design. And the liveness headline's antecedent contains the reader's own view being caught up to a horizon, a delivery assumption each execution model owes; the timed model discharges it from view convergence and the reactive model from its wait clauses, and the headline is conditional on it. None of these affects whether the stated theorems are true; each concerns how much they say. --- ## 27. Related work **Hybrid fault models.** Orcaella [KS26] derives the tight committee `n ≥ 5f + 3c + 1` for two-round commitment under separate Byzantine and crash caps, proves its core vote-counting protocol safe and live, and instantiates it over an uncertified DAG (OrcDAG); the model descends from a line of mixed-fault designs (Hydrangea, Kudzu — surveyed in [KS26]) that provide optimistic fast paths with PBFT-style fallbacks. §14 machine-checks both directions of its Theorem 1 for the DAG rules — sufficiency at the generalized bound, for every admissible threshold, and necessity on data one validator short — and records the one point where the published DAG instantiation is incomplete: its indirect rule needs the canonical candidate selection the core protocol's view change already has (§14.3), the same repair §10 supplies for Odontoceti. The additive checkpoint arc is an assume-guarantee model: possibly forked per-validator histories and messages are execution inputs, and from them it machine-checks same-epoch checkpoint uniqueness, prefix consistency, resilient finality, and highest-checkpoint recovery under alive-but-corrupt signing faults. It does not derive an AbC-induced fork from OrcDAG or compose checkpoint safety with the DAG proofs. It treats `BaseSpec.lean` and `RecoverySpec.lean` as the human-reviewed trust boundary, with derivations isolated in the two `*Proofs.lean` modules. It treats authenticated Byzantine broadcast through its agreement, integrity, and correct-input delivery contract; the Dolev--Strong implementation and executable cryptographic parsing remain outside the model. **Crash-fault DAG consensus.** Nemo-Nemo [Ker+26] carries the uncertified-DAG rule to the crash-fault setting — `n ≥ 2f + 1`, a majority quorum, wave length two, no signatures — and argues that CFT systems built for wide-area networks should adopt the DAG structure BFT designs converged on. §15 machine-checks the DAG rules' two guarantees: agreement with no fault bound and no side conditions — non-equivocation *is* the crash model — and liveness at the classical bound, where the mechanisation sharpens the fairness requirement: the protocol's direct-skip quorum is the full stake, so with no failure detector every skip is indirect, a lone committed leader settles only the slot two rounds below it, and progress requires committed leaders at adjacent rounds (§15.4) — a clause round-robin satisfies by counting but an arbitrary schedule need not. The protocol's deadline deferral, multi-leader cohorts and WAN evaluation are performance mechanisms outside the model. **Certified and uncertified DAGs.** In a certified DAG — DAG-Rider, Narwhal with Tusk or Bullshark [DKSS22, SGSK22], Sailfish [SSKN25] — a block is disseminated by reliable broadcast and enters the DAG carrying a quorum of signatures, so a reader may assume any block it sees is non-equivocated and available. The uncertified variant descends from Hashgraph [Bai16] and Blockmania [DH18], receives its modern form in Cordial Miners [KNPS23], and reaches its lowest latency in Mysticeti [Bab+25], which removes the wave structure of Cordial Miners by assigning a leader slot in every round. The trade is the one described in §1.1: certification disappears from the critical path, and equivocation and availability become the reader's problem. The uncertified structure is reused with one parameter varied at a time: Mahi-Mahi [Jov+24] under asynchrony, committing several leader slots per round; Odontoceti [Van25] at `n = 5f+1`, obtaining a two-round commit at a weaker fault threshold; Starfish [PMV25] with erasure-coded dissemination; Bluestreak [PVM26] with a sparse reference structure, which abandons the rule that every block cites a quorum below and so falls outside the present model. Shoal++ [Aru+25] argues from the certified side that certification is not the cause of latency, and hybridises by committing anchors on `2f+1` uncertified proposals. **Where the synchrony assumption is located.** In each of the above the assumption is stated per message, in the sense of Dwork, Lynch and Stockmeyer [DLS88]: beyond GST a message between correct parties arrives within Δ. The consequence noted in §1.2 is that every subsequent statement is quantified over instants. The present formulation instead states the assumption on the DAG, and §6.7 and §6.8 derive it from the conventional one. The author is not aware of a prior structural formulation, though the property itself is asserted repeatedly: Mysticeti's Lemma 8 and Cordial Miners' Proposition 38 both claim post-GST synchronisation of honest validators, and it is exactly these claims that [PMV25] reports as gapped and [QXS26] refutes. **Chain quality and fairness.** The chain-quality property originates with the Bitcoin backbone analysis of Garay, Kiayias and Leonardos [GKL15] — the fraction of honest blocks in any window of the chain — and fairness claims for DAG protocols go back to Hashgraph [Bai16], whose "fair ordering" was informal. The order-fairness line (Kelkar, Zhang, Goldfeder, Juels [KZGJ20]) concerns transaction *ordering* rather than inclusion and is orthogonal to §7's guarantees. The §7 statements differ from the backbone form in the direction the DAG makes natural: coverage is per-flush and unconditional (a commit carries a quorum-forced sample of every round below it), and inclusion is individual and quantitative once synchrony holds — with a counterexample separating the two, which the author has not seen stated for this protocol family. **Mechanised consensus.** Safety-only verification of DAG protocols exists in TLA+ with TLAPS [Ber+24], covering DAG-Rider, Cordial Miners, Hashgraph, an Aleph variant and eventually synchronous Bullshark, with a modular separation of DAG construction from ordering. LiDO-DAG [QXS25] provides mechanised safety and liveness in Rocq for Narwhal, Bullshark and Sailfish — all certified. The work closest to the present development is [QXS26], which extends that framework to Mysticeti itself and is discussed at length in §4.1, §4.4 and §6.6. Two differences of method should be recorded. Theirs is an operational model: a transition system over traces, with segmented traces encoding the unreliability of timers before GST, and liveness reduced to safety properties of an abstract pacemaker by refinement. The account here is structural, and no theorem above §6.8 mentions time. The benefit of the structural style is visible in §6.6: the dependence of liveness on the round-jumping clause surfaces as a named hypothesis of a single lemma rather than as a condition inside a transition relation. The cost is that the theorems of [QXS26] cannot be stated here at all, "within bounded time" not being expressible in this vocabulary (§26.6). --- ## 28. Conclusion This report has given a machine-checked account of uncertified DAG consensus organised around one idea: state the liveness condition on the object the protocol actually builds. Eventual DAG synchrony — beyond some round, every correct block references every correct block of the round below — is a sentence about a graph, and above it the entire consensus argument is finite combinatorics: safety with no network assumption at all, liveness with no mention of time, and the temporal content of partial synchrony confined to two files beneath a `Prop`-valued interface, where the whole of the network's contribution reduces to one clause of view convergence and the structural condition is *derived* — three ways over (§6.7–§6.9). The same foundation carries every development after it unchanged — which is the strongest evidence the abstraction is placed correctly — and the developments are in turn carried by a second abstraction above them: four properties of a commit rule and a support, against which each mechanism is proved once and which nine rules show (§16.1). The denial-of-service account reuses the delivery layer and the self-parent clause; garbage collection reuses every theorem verbatim on the truncated universe, because truncation is arranged to be a universe; and Odontoceti reuses the entire DAG layer because its quorums are the `n − f` the development was already parameterised by. Each arc also returns something to the account of the trust boundary: enforceable storage bounds, horizons without consensus, and — in the one place the formalization diverged from a published argument by necessity — the observation that Odontoceti's agreement rests on a canonical candidate order that its paper never states. What remains open is catalogued in §26.6: the backoff dynamics, wall-clock latency, block-level total order, and liveness below the growth clause. Beyond those, two directions suggest themselves. The commit-free, evidence-based horizon rule sketched in the garbage-collection document would extend pruning into asynchrony; and the properties, having absorbed every mechanism here, are the interface a tenth rule would be written against — the cost of adding one is the band and the support, and everything else is a line. --- ## References - [Amo+25] I. Amores-Sesar, V. Grøndal, A. Holmgård, M. Ottendal. *DAG It Off: Latency Prefers No Common Coins.* DISC 2025, LIPIcs 356, 5:1–5:17. doi:10.4230/LIPIcs.DISC.2025.5. (The preprint, arXiv:2508.14716, numbers every lemma one lower; §18 follows the published version.) - [Aru+25] B. Arun, Z. Li, F. Suri-Payer, S. Das, A. Spiegelman. *Shoal++: High Throughput DAG BFT Can Be Fast and Robust!* NSDI 2025. arXiv:2405.20488. - [Bab+25] K. Babel, A. Chursin, G. Danezis, A. Kichidis, L. Kokoris-Kogias, A. Koshy, A. Sonnino, M. Tian. *Mysticeti: Reaching the Limits of Latency with Uncertified DAGs.* NDSS 2025. arXiv:2310.14821. - [Bai16] L. Baird. *The Swirlds Hashgraph Consensus Algorithm.* Swirlds Tech Report SWIRLDS-TR-2016-01, 2016. - [Ber+24] N. Bertrand, P. Ghorpade, S. Rubin, B. Scholz, P. Subotic. *Reusable Formal Verification of DAG-based Consensus Protocols.* arXiv:2407.02167. - [DH18] G. Danezis, D. Hrycyszyn. *Blockmania: from Block DAGs to Consensus.* arXiv:1809.01620. - [DKSS22] G. Danezis, L. Kokoris-Kogias, A. Sonnino, A. Spiegelman. *Narwhal and Tusk: a DAG-based Mempool and Efficient BFT Consensus.* EuroSys 2022. - [DLS88] C. Dwork, N. Lynch, L. Stockmeyer. *Consensus in the Presence of Partial Synchrony.* JACM 35(2), 1988. - [GKL15] J. Garay, A. Kiayias, N. Leonardos. *The Bitcoin Backbone Protocol: Analysis and Applications.* EUROCRYPT 2015. - [Jov+24] P. Jovanovic, L. Kokoris-Kogias, B. Kumara, A. Sonnino, P. Tennage, I. Zablotchi. *Mahi-Mahi: Low-Latency Asynchronous BFT DAG-Based Consensus.* arXiv:2410.08670. - [KZGJ20] M. Kelkar, F. Zhang, S. Goldfeder, A. Juels. *Order-Fairness for Byzantine Consensus.* CRYPTO 2020. - [KNPS23] I. Keidar, O. Naor, O. Poupko, E. Shapiro. *Cordial Miners: Fast and Efficient Consensus for Every Eventuality.* DISC 2023, LIPIcs 281. - [Ker+26] R. Kerur, P. Tennage, P. Jovanovic, D. Malkhi, A. Sonnino, I. Zablotchi. *Finding Nemo-Nemo: CFT DAG-based Consensus in the WAN.* 2026. - [KS26] L. Kokoris-Kogias, A. Sonnino. *Orcaella: Hybrid Fault Tolerance with Client-Selectable Finality Latency.* arXiv:2607.04789. - [LF26] O. Ladelsky, R. Friedman. *FinWhale: An Optimally Resilient Two-Round Terminating DAG Protocol.* arXiv:2606.26292v2, 2026. - [PMV25] N. Polyanskii, S. Mueller, I. Vorobyev. *Making Uncertified DAG BFT Provably Live with Linear Payload and Quadratic Metadata Communication* (Starfish). IACR ePrint 2025/567. - [PVM26] N. Polyanskii, I. Vorobyev, S. Mueller. *Bluestreak: Scaling DAG BFT by Sparsifying Metadata.* IACR ePrint 2026/898. - [QXS25] L. Qiu, J. Xiao, J.-Y. Shin, Z. Shao. *LiDO-DAG: A Framework for Verifying Safety and Liveness of DAG-Based Consensus Protocols.* PACMPL 9(PLDI), Article 203, 2025. doi:10.1145/3729306. - [QXS26] L. Qiu, J. Xiao, Z. Shao. *Mechanized Safety and Liveness Proofs for the Mysticeti Consensus Protocol under the LiDO-DAG Framework.* IEEE S&P 2026, 149–168. - [SGSK22] A. Spiegelman, N. Giridharan, A. Sonnino, L. Kokoris-Kogias. *Bullshark: DAG BFT Protocols Made Practical.* CCS 2022. - [SSKN25] N. Shrestha, R. Shrothrium, A. Kate, K. Nayak. *Sailfish: Towards Improving the Latency of DAG-based BFT.* IEEE S&P 2025. ePrint 2024/472. - [Tsi+23] G. Tsimos, A. Kichidis, A. Sonnino, L. Kokoris-Kogias. *HammerHead: Leader Reputation for Dynamic Scheduling.* arXiv:2309.12713. - [Van25] P. Vander Vos. *Odontoceti: Ultra-Fast DAG Consensus with Two Round Commitment.* MSc thesis, arXiv:2510.01216. --- ## Appendix A. Statement index Principal results only; the source is the reference for supporting lemmas. A label — `L4`, `CQ6`, `O5` — is a cross-reference handle: the prose, the consumption map of §4.8 and the support diagrams of §6.10 refer to results through them. The series are alphabetic by area: T and M for the safety core, L for liveness, V for the view-convergence family, CU for catch-up, RS for the reactive schedule, SS for safe skip, AL for adaptive leaders, H for the hybrid fault model, I for integration, MM for Mahi-Mahi, BM, BML, BMR, BMA, BMD, BME, BMO and BMP for Black Marlin, FW for FinWhale, BN for Barnacle, HZ for Hydrozoan, OH for Optimal-Hydrozoan, HI for the Hydrozoan integration, CQ for chain quality, C, D, B and E for the denial-of-service arc, G for garbage collection, O for Odontoceti; P, N and R name clauses of the trust boundary rather than results. Labels resolving to witness models rather than library theorems (V10–V12, CU1, CU4, C5, CQ8, O11, SS7, SS11, AL8, H9, H10, BN13) are excluded from the diagrams, which show the library; so are MM4, BM8, BML6, BMR7, BMA5, BMD7, BME6, BMO10, BMO11 and BMP14. Two labels are absent from the Barnacle rows below and are named here rather than left to be noticed: **BN1**, that `Sched m` is a lawful `Slots` instance at every count, is a fact about the schedule that the design record carries and no result of the arc consumes as a named step; and **BN4** was withdrawn — there is no total run, and what was to be stated of one is BN3 at equal heights (§21.2). Appendix C displays every indexed result in full, with every other theorem the body names. ### Safety | Label | Statement | Lean *(module)* | |:---|:---|:---| | T0 | two quorums share a correct validator | `exists_correct_mem_inter` *(Validators)* | | T0′ | two quorum-backed identifier sets share a correct author | `exists_correct_mem_creators_inter` *(Block)* | | T1 | non-equivocation, in usable form, at any record | `BlockRecord.eq_of_creator_eq` *(Common/BlockRecord)* | | T1a | the core's validity is the validity family at its quorum, with distinct creators and a self-parent | `ValidWrt.iff_validAt` *(BlockDag)* | | T6 | two quorum-backed sets of round-`n` blocks share a block | `BlockUniverse.exists_common_mem_of_quorums` *(BlockDag)* | | T2 | causal history is non-increasing in round | `round_le_of_reaches` *(CausalHistory)* | | T6a | causal history does not escape a view | `View.mem_of_reaches`, `View.exists_reaches_iff` *(CausalHistory)* | | T3 | persistence | `reaches_of_quorum_support` *(Persistence)* | | T3a | correct-support counting | `exists_correct_common_support` *(CommonCore)* | | T3c | a common correct ancestor | `exists_common_correct_ancestor` *(CommonCore)* | | M1 | no block is both committed and skipped | `not_directCommit_of_directSkip` *(Mysticeti)* | | M2 | a committed block's certificate is unavoidable from `r+3` | `exists_certificate_reaches_of_directCommit` *(Mysticeti)* | | M3 | a skipped block has no certificate anywhere | `certificates_eq_empty_of_directSkip` *(Mysticeti)* | | M4 | the indirect rule agrees with the direct | `indirect_agrees_with_direct`, `certifiedIn_iff_of_view` *(Mysticeti)* | | M5′ | certificate uniqueness | `eq_of_certificates_nonempty` *(Mysticeti)* | | M5 | at most one block per slot is directly committed | `eq_of_directCommit_of_creator_eq` *(Mysticeti)* | | M6 | agreement, once for every anchored rule | `AnchoredRule.decided_unique`, `AnchoredRule.decided_agree` *(Anchored)*; `coreLaws` *(Mysticeti)* | | M6′ | corollaries of agreement | `AnchoredRule.eq_of_decided_commit`, `AnchoredRule.not_decided_skip_of_decided_commit` *(Anchored)* | | M7 | the committed-leader sequence is agreed | `commitSeq_agree` *(Mysticeti)* | | M8 | the ledger is monotone and agreed | `ledgerSet_mono`, `ledgerSet_agree` *(Mysticeti)* | | M9 | a block enters at one slot, agreed | `outputAt_unique`, `outputAt_agree` *(Mysticeti)* | ### Liveness | Label | Statement | Lean *(module)* | |:---|:---|:---| | L0 | the DAG is dense below its frontier | `card_authorsAt_of_lt` *(Liveness)* | | L2 | decisions are monotone in the view | `AnchoredRule.decided_mono` *(Anchored)* | | L3 | decisions propagate to the full view | `decided_full` *(Liveness)* | | L4 | a correct leader is committed | `directCommit_of_leader_mem`, `decided_of_leader_mem`; the targeted interface `VotesAt`, `CertifiesAt`, `directCommit_of_certifiesAt` *(Liveness)*, `directCommit_of_votesAt` *(Odontoceti/Liveness)* | | L4′ | at `T := Correct` | `directCommit_of_correct_leader`, `decided_of_correct_leader` *(Liveness)* | | L5 | an absent leader is skipped | `decided_none_of_leader_absent` *(Liveness)* | | L6 | commits recur | `commits_recur_on`, `commits_recur` *(Liveness)* | | L8c | a slot resolves through its first eligible commit | `decided_of_first_eligible_commit` *(Liveness)* | | L8d | a committed slot above decides everything below (spaced schedules) | `decided_of_committed_above`, `all_decided_below_of_spacing` *(Liveness)* | | L8e | a committed run of eligible span clears everything below | `decided_below_of_committed_run` *(Liveness)* | | L10 | every slot below a fair run is decided (pipelined) | `all_decided_below_of_fairRun` *(MysticetiProperties, from `Support.decidedBelow_of_fairRun`)* | | L12 | a schedule satisfying L10's fairness and shape hypotheses exists at every committee: the wave-aligned rotation, with no premise beyond the fault model | `waveRobin`, `waveRobin_fairRun`, `waveRobin_spansEligible`, `waveRobin_fairSchedule` *(WaveRobin)* | | L7 | coverage from view convergence, drift-free | `ViewPace.synchronisedOn_of_converges`; the drift-parametric engine `ViewPace.synchronisedOn_of_driftOn` *(ViewPace)* | | V1 | the referencing clause, unfused from the network's | `ViewPace.covers_of_converges` *(ViewPace)* | | V4 | the bound factored out of convergence | `convergesWithin_iff_bounded` *(ViewPace)* | | V10 | the bound in `converges` is necessary for coverage | `bound_is_necessary_pace`, `ugapPace_convergesEventually` *(LeanDagTest.Mysticeti.Unbounded)* | | V11 | and its starting round is forced, not chosen | `gst_is_forced_pace` *(LeanDagTest.Mysticeti.Unbounded)* | | V12 | as is its reliable set: coverage over `T` derived, over `Correct` false | `reliable_set_is_forced_pace`, `ustarvePace_synchronisedOn` *(LeanDagTest.Mysticeti.Unbounded)* | | V17 | a partial build schedule, in which *stuck* is expressible: the structure, production, and the spine | `ViewPace`, `ViewPace.reached`, `ViewPace.populatedOn`, `ViewPace.commits_recur_via_pace`, `ugrowStuckPace_stuck` *(ViewPace, LeanDagTest.Mysticeti.ViewPace)* | | L11 | drift is derived, at the collapsed constant | `ViewPace.driftOn_of_catchup`, `ReactivePace.driftOn_of_catchup` *(ViewPace, Reactive/Basic)* | | L8a | the round of coverage, explicitly | `ViewPace.synchronisedOn_of_rate` *(ViewPace)* | | L8b | the committing slot, and its round | `commits_recur_within`, `commits_recur_by_round` *(Quantitative)* | | L9 | the wait bound | `ViewPace.directCommit_of_wait`, `ViewPace.decided_of_wait`, `ViewPace.directCommit_of_wait_two_delay` *(ViewPace)* | | V18 | liveness is local: a validator decides on its own view | `PaceCore.viewAt`, `PaceCore.viewAt_ids`, `PaceCore.history_subset_holds`, `PaceCore.holds_roundBlocks`, `PaceCore.decided_local_of_certifiesAt`, `ViewPace.decided_local`, `ViewPace.decided_of_local`, `ReactiveM.decided_local` *(ViewPace, Reactive/Mysticeti)* | | V19 | a pacing structure induces a delivery layer; the acceptance rule is derived | `ViewPace.heldOf_inj`, `ViewPace.toDelivery` *(PaceDelivery)* | | V20 | the converse of P7 on the trunk; liveness with bounded storage from one structure | `PaceCore.refs_held`, `ViewPace.refsAccepted_toDelivery`, `ViewPace.dos_resistance_of_pace` *(ViewPace, PaceDelivery)* | Gaps in the numbering (L1, L7a–L7c, V2, V3, V5–V9, V13–V16) are deliberate: those labels belonged to superseded formulations and are not reused. ### Chain quality (§7) | Label | Statement | Lean *(module)* | |:---|:---|:---| | CQ1 | a commit covers all but at most `f` correct authors, per round | `card_coveredAt_ge_of_decided` *(Quality/Coverage)* | | CQ2 | at least half of the correct validators, per round | `card_correct_le_two_mul_coveredAt_of_decided` *(Quality/Coverage)* | | CQ3 | ledger coverage, cumulative | `ledger_coverage` *(Quality/Coverage)* | | CQ5 | post-`R`, every correct block is in every later correct-led commit | `mem_history_of_decided_commit` *(Quality/Inclusion)* | | CQ6 | every correct block enters the agreed ledger | `committed_of_correct_block` *(Quality/Inclusion)* | | CQ7 | within a schedule window; the capstone | `committed_of_correct_block_within`, `committed_of_correct_block_by_round`, `chain_quality` *(Quality/Capstone)* | | CQ8 | the censorship boundary, on data | `Ucens` witnesses *(LeanDagTest/Quality/Model)* | ### Denial of service (§8) | Label | Statement | Lean *(module)* | |:---|:---|:---| | D11–D13 | exposure, and the DoS condition | `ExposedIn`, `DoSValid` *(DoS/Exposure)* | | C2 | at most `f` authors exposed per cone | `card_exposedTo_le` *(DoS/Exposure)* | | D14 | safety and the DoS condition do not interact | witness file *(LeanDagTest/DoS/SafetyUnderDoS)* | | D15a | at zero margin, references are exactly the correct validators | `creators_refs_eq_correct` *(DoS/Exclusion)* | | E1 | the correct backbone | `mem_history_of_correct` *(DoS/Exclusion)* | | C1′ | the general per-cone bound | `card_history_le'` *(DoS/Pedigree)* | | D25 | density: cones miss at most `f` per layer | `card_missingAt_le` *(DoS/Density)* | | C5 | the doubling construction (`2^(e−2)`) | `Udouble` witnesses *(LeanDagTest/DoS/Doubling)* | | C4 | the telescope | `card_history_le_of_stepNovelty` *(DoS/Novelty)* | | C3′ | the view gap is a constant, not a drift | `card_viewGap_succ_le` *(DoS/Novelty)* | | B6 | the budget sandwich | `UniformBudget.byzBudget`, `uniform_of_byzBudget` *(DoS/Novelty)* | | B7 | a correct block carries its author's whole accepted past | `viewUpto_subset_history` *(DoS/Novelty)* | | B4 | linear storage under the budget | `card_viewUpto_le` *(DoS/Novelty)* | | B | the capstone, enforceable conditions only | `dos_resistance`, `dos_resistance'` *(DoS/Novelty)* | | B5 | after exposure completes, the pool freezes | `card_viewUpto_le_of_allExposed'` *(DoS/Composition)* | ### Garbage collection (§9) | Label | Statement | Lean *(module)* | |:---|:---|:---| | G1 | truncation is a universe; the DoS condition crosses one way | `chop`, `dosValid_chop` *(GC/Chop)* | | G2 | per-slot verdict invariance, retired in favour of the band | `LocalTruncate.of_banded` *(Properties/Derived/Truncate)* | | G3 | the decision relation survives the cut | `decided_chop_iff` *(Properties/Arcs/GC)* | | G4 | cross-cut agreement, arbitrary joiner views | `decided_agree_chop` *(GC/ChopDecided)* | | G5 | liveness transfers | `populated_chop` *(GC/Window)* | | G13, G14 | windowed novelty; store correspondence | `novelty_chop_anti`, `viewUpto_chopD` *(GC/Window)* | | G6 | storage constant at lag `Λ` | `card_retained_le` *(GC/Window)* | | G6b, G7 | join and relay at the same constant | `card_joinIds_le`, `card_serve_le` *(GC/Bootstrap)* | | G10 | the attested-base sandwich | `correct_mem_base`, `exists_correct_attester_of_mem_base` *(GC/AttestedBase)* | | G11 | window completeness, tight at lag two | `accepted_mem_base` *(GC/Bootstrap)* | | G12 | bootstrap safety | `joinView`, `bootstrap_agree` *(GC/Bootstrap)* | | G8 | horizons compose; heterogeneous horizons agree | `chop_chop`, `decided_agree_horizons` *(GC/Horizon)* | | G9 | possession universalises in one round | `viewUpto_subset_viewUpto_succ`, `pruned_subset_peer_store` *(GC/Horizon)* | ### Odontoceti (§10) | Label | Statement | Lean *(module)* | |:---|:---|:---| | O1 | commit versus skip | `not_directSkip_of_directCommit` *(Odontoceti/Rules)* | | O1′ | twin uniqueness for direct commits | `eq_of_directCommit` *(Odontoceti/Rules)* | | O2 | a skipped leader fails the indirect test everywhere | `card_supporters_le_of_directSkip`, `not_thickLink_of_directSkip` *(Odontoceti/Rules)* | | O3 | support propagation: every anchor's cone is the certificate | `thickLink_of_directCommit` *(Odontoceti/Rules)* | | O4′ | a direct commit excludes every rival candidate | `eq_of_directCommit_of_thickLink` *(Odontoceti/Rules)* | | O5 | agreement, under canonicity | `Odontoceti.odontocetiLaws` *(Odontoceti/Decision)*, `OdontocetiProperties.agree` *(OdontocetiProperties)* | | O6 | safety, as the headline across any stack | `OdontocetiProperties.safety`, `OdontocetiProperties.liveness` *(OdontocetiProperties)* | | O7 | a correct leader commits in one step | `Odontoceti.decided_of_leader_mem` *(Odontoceti/Liveness)* | | O8 | a run of two spans eligibility | `AnchoredRule.spansEligible_of_identity` *(Anchored)* | | O9 | a committed run clears everything below | `Odontoceti.decided_below_of_committed_run` *(Odontoceti/Liveness)* | | O10 | liveness | `Odontoceti.all_decided_below_of_fairRun` *(OdontocetiProperties, from `Support.decidedBelow_of_fairRun`)* | | O11 | the thesis gap, on data | `utwin6_both_pass` *(LeanDagTest/Odontoceti/Model)* | **The reactive schedule** (§11): | Label | Statement | Lean | |:---|:---|:---| | RS1 | every reliable vote block votes | `ReactivePace.votes` *(Reactive/Basic)* | | RS2 | reactive liveness, three rounds | `ReactiveM.certifies`, `ReactiveM.directCommit`, `ReactiveM.decided` *(Reactive/Mysticeti)* | | RS3 | reactive liveness, two rounds | `Odontoceti.reactive_directCommit`, `Odontoceti.reactive_decided` *(Reactive/Odontoceti)* | | RS4 | latency tracks delivery; the timeout never fires | `ReactivePace.built_succ_le_of_fast`, `ReactivePace.no_timeout_of_fast` *(Reactive/Basic)* | | RS5 | inclusion without coverage: the rotation backbone | `SelfParent.reaches_of_creator` *(Properties/Optional/SelfParent)*, `FairToEach` *(Liveness)*, `ReactiveM.committed_of_correct_block` *(Reactive/Mysticeti)* | **Catch-up** (§6.11): | Label | Statement | Lean | |:---|:---|:---| | CU1 | drift does not contract past the collapse bound | `ugrowSkew_spread_constant` *(LeanDagTest.Reactive.Catchup)* | | CU2 | drift collapses to `Δ + proc`, from any spread | `PaceCore.drift_collapse` *(ViewPace)* | | CU3 | the deployment-free threshold `2Δ + proc` | merged into the main line: L7 (coverage) and L9 (the wait bound) | | CU4 | the collapse exhibited from a spread of ten | `ugrowLag_collapse`, `ugrowLag_decided` *(LeanDagTest.Reactive.Collapse)* | | CU5 | the rush bound: a valid block certifies the honest floor | `exists_reliable_parent`, `PaceCore.round_le_top_succ`, `ViewPace.exists_honest_floor` *(ViewPace)* | **Safe Skip** (§12): | Label | Statement | Lean | |:---|:---|:---| | SS1 | the fill is a universe; old blocks read unchanged | `SkipMsg.skipFill`, `SkipMsg.skipFill_block_old` *(SafeSkip/Basic)* | | SS2 | the gap is populated: production restored | `SkipMsg.skipFill_populatedOn` *(SafeSkip/Basic)* | | SS3 | the fill cannot conjure a commit | `decided_none_fresh` *(Properties/Arcs/SafeSkip)* | | SS4 | the rule-level sets are unchanged, for every candidate | retired: a consequence of `Banded` through `Persist.of_banded` *(Properties/Derived/FromBand)* | | SS5 | verdict invariance across the fill | `decided_fill_of_persist` *(Properties/Arcs/SafeSkip)* | | SS6 | agreement across a recovery | `decided_fill_agree_of_properties` *(Properties/Arcs/SafeSkip)* | | SS7 | the crash, the message and the fill, on data | `Ucrash` witnesses *(LeanDagTest/SafeSkip/Model)* | | SS8 | the donor line is unique given its tip | `SkipMsg.line_eq_lineOf` *(SafeSkip/Jump)* | | SS9 | the denotation is a function of the compact core | `SkipMsg.skipFill_eq_of_core` *(SafeSkip/Jump)* | | SS10 | receivers derive the same fill, locally | `lineOf_mem_view`, `JumpMsg.denote_eq_of_core` *(SafeSkip/Jump)* | | SS11 | the jump message and its elaboration, on data | `ucrashJump` witnesses *(LeanDagTest/SafeSkip/Model)* | **Adaptive leaders** (§13): | Label | Statement | Lean | |:---|:---|:---| | AL1 | the induced instance at any number of leaders per round, and the distinctness clause the policy then owes, at every count a mechanism may reach | `slotsOfKeyed`, `slotsOf`, `slotsOf_base` *(Adaptive/Basic)*, `Adaptive.PickKeyed`, `pickKeyed_one` *(Adaptive/Policy)* | | AL2 | the bounded relation; the embedding and the congruence | `AnchoredRule.DecidedWithin`, `AnchoredRule.DecidedWithin.toDecided`, `AnchoredRule.decidedWithin_congr_of_slotRound` *(Anchored/Bounded)*; `decidedWithin_congr` *(Integration/AdaptiveMysticeti)* | | AL3 | safety: the fixpoint is unique, unconditionally, for any rule with `Agree` | `partialRun_agree`, `run_agree` *(Adaptive/Run)* | | AL4 | conservativity at the constant policy | `Policy.const_run_decided` *(Adaptive/Run)* | | AL5 | liveness: the fixpoint exists, one epoch at a time, for any rule with a support satisfying Law 2 | `epoch_closes_of_support`, `exists_partialRun_of_support`, `run_exists_of_support` *(Adaptive/Liveness)* | | AL6 | the adaptive ledger is agreed | `run_commitSeq_agree` *(Adaptive/Run)* | | AL7 | every rule showing the properties instantiates the mechanism: Mysticeti, Odontoceti, Hydrozoan and Nemo-Nemo, synchronous or reactive | `Adaptive.run_exists_of_support` *(Adaptive/Liveness)* | | AL8 | adaptivity on data: the verdict moves with the assignment | `demotePolicy` witnesses *(LeanDagTest/Adaptive/Model)* | | AL10 | a verdict is local in the rounds as well as the leaders: every decided slot has a round below which any schedule agreeing on rounds and leaders decides it the same way, and a directly decided slot's is its own round plus a wave | `Properties.exists_roundLocal` *(Properties/Derived/FromBand)*, `AnchoredRule.banded_direct` *(Common/Anchored/Band)*, `Barnacle.cfg_local` *(Barnacle/Helpers/Schedule)* | **Hybrid fault tolerance** (§14): | Label | Statement | Lean | |:---|:---|:---| | H1 | the counting core: overlap past `n + fb` yields an honest member, the shared intersection lemma at `fb` | `exists_mem_inter_notMem` *(Common/Counting)* | | H2 | commit versus skip; twin uniqueness | `Hybrid.not_directSkip_of_directCommit`, `Hybrid.eq_of_directCommit` *(Hybrid/Rules)* | | H3 | a skipped leader caps at `2·fb + fc` supporters, below the interval | `Hybrid.card_supporters_le_of_directSkip`, `Hybrid.not_thickLink_of_directSkip` *(Hybrid/Rules)* | | H4 | link integrity: every anchor carries the interval's upper end | `Hybrid.thickLink_of_directCommit` *(Hybrid/Rules)* | | H5 | a direct commit excludes every rival candidate | `Hybrid.eq_of_directCommit_of_thickLink` *(Hybrid/Rules)* | | H6 | agreement and safety, at every admissible threshold | `Hybrid.hybridLaws` *(Hybrid/Decision)*, `HybridProperties.agree`, `HybridProperties.safety` *(HybridProperties)* | | H7 | liveness over the reliable-correct interface | `Hybrid.decided_of_leader_mem`, `Hybrid.all_decided_below_of_fairRun` *(Hybrid/Liveness)* | | H8 | conservativity: the crash-free hybrid is Odontoceti | `Faults5.toHybrid`, `Hybrid.toHybrid_toFaults` *(Hybrid/Conservativity)* | | H9 | one crash at four validators; the tight hybrid committee | `Uhyb4`, `Uhyb9` witnesses *(LeanDagTest/Hybrid/Model)* | | H10 | the bound is necessary, at every threshold | `hybrid_bound_necessary` *(LeanDagTest/Hybrid/Tight)* | | NN1 | the counting core: two majorities intersect, the shared intersection lemma with nothing excluded | `exists_mem_inter_notMem` *(Common/Counting)* | | NN2 | the hitting lemma: a majority of backers meets every valid block's parents, the record's lemma at Nemo's quorum | `exists_mem_refs_of_honest_support_of_card` *(Common/Support)* | | NN3 | link integrity: a direct commit is certified two rounds up, everywhere | `Nemo.certifiedIn_of_directCommit` *(Nemo/Rules)* | | NN4 | a slot has at most one candidate, every leader being honest | `isLeaderBlock_unique_of_honest` *(Common/Leader)* | | NN5 | agreement, with no side conditions | `Nemo.nemoLaws` *(Nemo/Decision)*, `NemoProperties.agree` *(NemoProperties)* | | NN6 | the ledger is agreed and never retracted | `Nemo.commitSeq_agree`, `Nemo.outputAt_agree` *(Nemo/Decision)* | | NN7 | a reliable-led slot commits directly | `Nemo.decided_of_leader_mem` *(Nemo/Liveness)* | | NN8 | every slot below a recurring adjacent pair is decided | `Nemo.all_decided_below_of_fairRun` *(NemoProperties, from `Support.decidedBelow_of_fairRun`)* | | NN9 | one crash at three validators; the crashed slot settled indirectly | `Unemo` witnesses *(LeanDagTest/Nemo/Model)* | **Mahi-Mahi** (§17): | Label | Statement | Lean | |:---|:---|:---| | MM1 | safety at wave `w`: skip excludes certificates, certificate uniqueness, agreement, conservativity at `w = 3` | `MahiMahi.Safety.holds` *(MahiMahi/Safety/Proof)* | | MM1a | a skipped slot has no certificate for any candidate | `MahiMahi.certificates_eq_empty_of_directSkip` *(MahiMahi/Helpers/Rules)* | | MM1b | two certified candidates of one author and round coincide | `MahiMahi.eq_of_certificates_nonempty` *(MahiMahi/Helpers/Rules)* | | MM1c | two views agree on every slot | `MahiMahi.mahiMahiLaws` *(MahiMahi/Helpers/Decision)*, `MahiMahi.Safety.holds` *(MahiMahi/Safety/Proof)* | | MM1d | at `w = 3` every derivation is the core's | `MahiMahi.core_decided_of_decided` *(MahiMahi/Helpers/Decision)* | | MM2 | the counting lemma: the common core; some correct block commits at `w ≥ 4`; `n − f − |byzantine|` of them at `w ≥ 5` | `MahiMahi.Counting.holds`, `MahiMahi.exists_commonCore`, `MahiMahi.goodNonempty`, `MahiMahi.goodCard` *(MahiMahi/Helpers/Counting)* | | MM2b | `2f + 1` distinct leaders at a round include a good one | `MahiMahi.multiLeader` *(MahiMahi/Helpers/Counting)* | | MM2′ | `good` depends only on the rounds up to the decision round | `MahiMahi.AgreeUpto.goodAt_eq` *(MahiMahi/Helpers/Liveness)* | | MM3 | liveness under the clause: a good leader commits; commits within every window; every slot below a run decided; local liveness | `MahiMahi.Liveness.holds`, `MahiMahi.decided_of_mem_good`, `MahiMahi.allDecidedBelow`, `MahiMahi.localCommit` *(MahiMahi/Helpers/Liveness)* | | MM4 | the clause is satisfiable, refuted by round-robin on the aiming pattern, and independent of fairness | `aim4`, `full4` witnesses *(LeanDagTest/MahiMahi)* | | MM5 | under coverage at one round a reliable leader is good; the clause is derived from fairness | `MahiMahi.Synchrony.holds`, `MahiMahi.good_of_synchronisedOn`, `MahiMahi.unpredictableWithin_of_synchronisedOn` *(MahiMahi/Helpers/Synchrony)* | **Black Marlin** (§18): | Label | Statement | Lean | |:---|:---|:---| | BM1 | two supported anchor blocks of one round are one block | `BlackMarlin.eq_of_isAnchor_of_supported` *(BlackMarlin/Helpers/Rules)* | | BM2 | a supported block is in the causal history of every block two rounds above it | `BlackMarlin.reaches_of_supported` *(BlackMarlin/Helpers/Rules)* | | BM3 | below the highest round, every round carries a quorum of distinct authors | `BlackMarlin.quorum_authorsAt_of_lt` *(BlackMarlin/Helpers/Rules)* | | BM5 | two committed anchors are one block, or one is in the causal history of the other | `BlackMarlin.reaches_of_committed_of_le` *(BlackMarlin/Helpers/Rules)* | | BM8 | Figure 1 on data: `B0`–`B2` committed, `B3` supported but unlinked | `Ubm` witnesses *(LeanDagTest/BlackMarlin)* | | BML6 | liveness on a covered four-round model, and why Figure 1 is not one | `Ufull` witnesses *(LeanDagTest/BlackMarlin)* | | BMA1 | a delivered block is delivered by every committed anchor from its round on | `BlackMarlin.history_subset_of_committed` *(BlackMarlin/Helpers/Rules)* | | BMD1 | the descent has one candidate where it steps by one round | `BlackMarlin.coneAnchors_subsingleton` *(BlackMarlin/Helpers/Ledger)* | | BMD1′ | and at a round whose anchor is reliable, however deep the cone | `BlackMarlin.coneAnchors_subsingleton_of_correct` *(BlackMarlin/Helpers/Ledger)* | | BMD2 | two records agreeing at a round agree at the round below | `BlackMarlin.block_eq_of_succ` *(BlackMarlin/Helpers/Ledger)* | | BMD3 | and throughout any stretch they both descend | `BlackMarlin.block_eq_of_add` *(BlackMarlin/Helpers/Ledger)* | | BMD4 | records flushing committed anchors at one round agree there | `BlackMarlin.block_eq_of_committed` *(BlackMarlin/Helpers/Ledger)* | | BMD5 | the link clause keeps the descent from skipping | `BlackMarlin.coneAnchors_succ_nonempty_of_committed` *(BlackMarlin/Helpers/Ledger)* | | BMD6 | no retraction, agreement, and one position per block | `BlackMarlin.ledgerSet_mono`, `BlackMarlin.ledgerSet_agree`, `BlackMarlin.outputAt_unique`, `BlackMarlin.outputAt_agree` *(BlackMarlin/Helpers/Ledger)* | | BME1 | the descent's choice is an anchor strictly below, at the highest anchor round | `BlackMarlin.descend_mem`, `BlackMarlin.descend_round_lt`, `BlackMarlin.round_descend` *(BlackMarlin/Helpers/Descent)* | | BME2 | and is made whenever an anchor lies below | `BlackMarlin.descend_isSome` *(BlackMarlin/Helpers/Descent)* | | BME3 | the computed record satisfies `Flush` | `BlackMarlin.flushRecord_step`, `BlackMarlin.flushRecord_dense`, `BlackMarlin.toFlush` *(BlackMarlin/Helpers/Descent)* | | BME4 | the record below a visited block is that block's own record | `BlackMarlin.flushRecord_suffix` *(BlackMarlin/Helpers/Descent)* | | BME5 | two records reaching one block agree at every round below it | `BlackMarlin.flushRecord_agree` *(BlackMarlin/Helpers/Descent)* | | BME6 | the computed record on data, and its agreement | `flushRecord` witnesses *(LeanDagTest/BlackMarlin)* | | BMO1 | the anchor is last in its own segment | `BlackMarlin.idxOf_le_idxOf_anchor` *(BlackMarlin/Helpers/Order)* | | BMO2 | records that agree output the same list | `BlackMarlin.deliverSeq_agree` *(BlackMarlin/Helpers/Order)* | | BMO3 | and the list only extends | `BlackMarlin.deliverSeq_prefix` *(BlackMarlin/Helpers/Order)* | | BMO4 | no author-and-round is output twice | `BlackMarlin.deliverSeq_pairwise` *(BlackMarlin/Helpers/Order)* | | BMO5 | every author-and-round flushed is output | `BlackMarlin.deliverSeq_key_mem` *(BlackMarlin/Helpers/Order)* | | BMO6 | and for a correct author, by the block itself | `BlackMarlin.deliverSeq_of_correct` *(BlackMarlin/Helpers/Order)* | | BMO9 | a reliable author's block is output | `BlackMarlin.mem_ledgerSeq_of_mem_history` *(BlackMarlin/Helpers/Order)* | | BMO10 | a sort exists, and the sequence on data | `TopoSort.ofFinOrder`, `divSort` witnesses *(LeanDagTest/BlackMarlin/Divergence)* | | BMO11 | two honest validators output different twins: Agreement refuted | `Udiv`, `vFlush`, `wFlush` witnesses *(LeanDagTest/BlackMarlin)* | | BMT4 | and one Byzantine anchor below suffices to order two reliable authors' blocks oppositely, refuting Total order | `deliverSeq` and `commitSeq` witnesses *(LeanDagTest/BlackMarlin/Divergence)* | **The properties** (§16.1): | Label | Statement | Lean | |:---|:---|:---| | TP1 | the carrier a mechanism reads, with its three laws | `Properties.DagRule` *(Properties/Carrier)* | | TP2 | the four properties a rule shows | `Banded`, `Agree`, `CommitsCandidate`, `Indirect` *(Properties/Band, Agree, Candidate, Commit)* | | TP3 | the support and its two laws; the precondition every liveness theorem reads | `Support`, `Support.Local`, `Support.Commits`, `Support.live` *(Properties/Support, Derived/LeaderCommits)* | | TP4 | what is derived: persistence, truncation invariance, leader commits, descent | `Persist.of_banded`, `LocalTruncate.of_banded`, `Support.leaderCommits`, `Descends.of_indirect` *(Properties/Derived)* | | TP5 | synchrony is the timed model's, not a property: the one bridge into `live` | `Timed.live_of_coverage` *(Timed/Coverage)* | | TP6 | verdicts across a cut, a fill, and any stack of mechanisms | `decided_of_truncate`, `decided_skipFill`, `decided_of_rebased`, `Stack.safe_and_live` *(Properties/Arcs)* | | TP7 | liveness across a cut, a fill, and any stack | `Support.live_of_truncates`, `Support.live_of_sustains`, `Support.live_of_rebased` *(Properties/Arcs/Liveness, Stack)* | | TP8 | every slot below a fair run is decided, at `live` | `Support.decidedBelow_of_fairRun` *(Properties/Arcs/Liveness)* | | TP9 | the prompt skip: a novel slot is skipped, and conflicts with no verdict | `decided_none_of_novel`, `decided_none_of_novel_agree` *(Properties/Arcs/SafeSkip)* | | TP10 | inclusion from self-reference: a reliable block reaches its author's next commit | `SelfParent.reaches_of_creator`, `committed_of_correct_block` *(Properties/Optional/SelfParent, Arcs/Quality)* | | TP11 | the safety headline: across any stack, and at every slot across an extension | `Properties.Safe`, `Properties.safety`, `Safe.prefix_agree` *(Properties/Arcs/Headline)* | | TP12 | the liveness headline: progress and inclusion at the rule's support | `Support.Lives`, `Support.liveness`, `Support.progress` *(Properties/Arcs/Headline)* | **Integration** (§16): | Label | Statement | Lean | |:---|:---|:---| | I1 | honest non-equivocation survives truncation and the fill | `honestNoEquiv_chop`, `honestNoEquiv_skipFill` *(Integration/Preservation)* | | I2 | coverage survives truncation, at a horizon offset | `synchronisedOn_chop` *(Integration/Coverage)* | | I4 | coverage under the fill: refuted for a set including the recovering validator, preserved otherwise, restored above the fill | `not_synchronisedOn_skipFill`, `synchronisedOn_skipFill_of_notMem`, `synchronisedOn_skipFill_above` *(Integration/Coverage)*, from `not_synchronisedOn_of_extends`, `synchronisedOn_of_extends` *(Timed/Extension)* | | I5 | the joiner: horizon-stability, and epoch alignment | `HorizonStable`, `joiner_run_decided_agree` *(Adaptive/Joiner)*, `epochOf_add_of_dvd` *(Adaptive/Basic)*, at the core in *Integration/Joiner* | | I6 | anchor retention, and the lag bounds the outage | `anchor_pruned`, `chopMsg`, `outage_bounded_by_lag` *(Integration/Retention)* | | I7 | the headlines at the core: safety across any stack, liveness at the support | `MysticetiProperties.safety`, `MysticetiProperties.liveness`, `stack_core` *(MysticetiProperties, Integration/StackRules)* | | I8 | a severed chain cannot restart | `no_blocks_of_no_genesis`, `severed_of_pruned_anchor` *(Integration/Retention)* | | I9 | the hypothesis the crash-prone lifecycle forced; the lifecycle theorem is retired (§16.5) | `hB1uniq`, `hB1uniq_of_correct` *(SafeSkip/Basic)* | | I10 | re-genesis at the cut | `addGenesis`, `populatedOn_addGenesis` *(Integration/ReGenesis)* | | I11 | local derivation converges; no agreement on the cut | `chop_addGenesis`, `regenesis_converges` *(Integration/ReGenesis)* | | I12 | bootstrap, re-genesis and Safe Skip compose into full recovery | `hB1uniq_of_addGenesis`, `recoveryMsg` *(Integration/ReGenesis)* | | I13 | the exposure condition survives re-genesis; the fill enlarges cones | `dosValid_addGenesis` *(Integration/ReGenesis)*; `history_B1_subset_fill` *(Integration/Exposure)* | | I14 | the fill disturbs exposure only at its own blocks | `exposedIn_skipFill_old`, `dosValid_skipFill` *(Integration/Exposure)* | | I15 | a covered donor line reduces the check to reachability | `fill_cone_subset`, `dosValid_skipFill_of_covered` *(Integration/Exposure)* | | I16 | the fill's delivery layer; the budget transfers, the reference discipline does not | `skipFillD`, `uniformBudget_skipFillD`, `not_refsAccepted_skipFillD` *(Integration/DeliveryFill)* | | I17 | the budget needs a donor, not the author | `card_novelty_le_of_donor` *(Integration/Margin)* | | I18 | severance costs liveness margin: at most `f` at once | `notMem_of_no_blocks`, `card_severed_le` *(Integration/Margin)* | | I19 | a common-core target makes the fill transmission-free | `CommonAt`, `exists_commonAt`, `fill_refs_available` *(Integration/CommonTarget)* | **FinWhale** (§20): | Label | Statement | Lean | |:---|:---|:---| | FW1 | a fast commit makes every block two rounds up FP-evidence for it | `lemma4` *(FinWhale/Evidence)* | | FW2 | the committee is the least at which that holds, and the counting behind the findings of §20.8 | `equivocating_margin`, `window_margin`, `c3_reachable`, `c3_margin` *(FinWhale/Counting)* | | FW3 | a fast commit excludes a second commit, a skip, and any other indirect commit: FinWhale's laws | `direct_commit_unique`, `no_nonFPEvidence_of_fastCommit`, `no_indirectCommit_of_fastCommit`, `finWhaleLaws` *(FinWhale/Decision, Skip, Anchor, View)* | | FW4 | two validators never decide a leader slot differently | `FinWhaleProperties.agree` *(FinWhale/Carrier)* | | FW5 | the delivery order extends rather than revises, and repeats nothing | `theorem14`, `theorem15` *(FinWhale/Order)* | | FW6 | safety with the views and the reverse pass supplied rather than assumed: the pass lands in the relation | `decided_of_wellFormed`, `agreement_of_commits` *(FinWhale/View)* | | FW7 | the two delivery lemmas, derived from C1, C2 and C3 | `lemma18`, `lemma19` *(FinWhale/Creation)* | | FW8 | both commit paths on the reactive schedule | `Creation.lemma20`, `Creation.theorem21`, `spCommit_of_reactive`, `fastCommit_of_reactive` *(FinWhale/Creation, Reactive)* | | FW9 | the fast commit within two message delays, the timeout never firing | `fastCommit_latency`, `no_timeout_of_fast` *(FinWhale/Reactive)* | | FW10 | three consecutive correct leaders in every window of `3f + 3` | `lemma22`, `three_correct_of_roundRobin`, `three_correct_window` *(FinWhale/Rotation)* | | FW11 | every slot below a committed triple is decided, and the sequences then coincide | `lemma23`, `all_decided`, `theorem24`, `theorem26_of_selfParent` *(FinWhale/Decided, Validity)* | | FW12 | what a validator guarantees: agreement, total order, integrity, validity | `Run.agreement`, `Run.totalOrder`, `Run.integrity`, `Run.validity` *(FinWhale/Protocol)* | | FW13 | a DoS-valid universe is a FinWhale DAG at any leader schedule | `leaderClause_of_dosValid`, `Dag.ofDoSValid`, `selfParented_ofDoSValid` *(FinWhale/DoSBridge)* | | FW14 | and on the reactive schedule it is a run, at every horizon; the condition never leaves a builder short of citable authors | `Run.ofDoSValidReactive`, `citable_of_correct`, `quorumCard_le_citable` *(FinWhale/DoSBridge)* | **Barnacle** (§21): | Label | Statement | Lean | |:---|:---|:---| | BN2 | the window is agreed: a view holding the anchor holds its history, and two views restrict it to one set | `Barnacle.Window.holds` *(Barnacle/Window/Proof)* | | BN3 | the configuration sequence is agreed, for any update rule: two runs to any heights agree on every configuration and verdict of their common ranges | `Barnacle.Agreement.holds` *(Barnacle/Agreement/Proof)* | | BN5 | the ledger is agreed as far as both runs reach, grows by prefixes, and holds each block once | `Barnacle.Ledger.holds` *(Barnacle/Ledger/Proof)* | | BN6 | under the constant rule the count never moves and every verdict is a one-leader verdict | `Barnacle.Conservativity.holds` *(Barnacle/Conservativity/Proof)* | | BN7 | the AIMD rule keeps its count in range, steps as the paper says, is the integer test, and is anchored — it does not read the view, so two validators take one step | `Barnacle.Aimd.holds` *(Barnacle/Aimd/Proof)* | | BN8 | progress: a run past the synchrony round extends by one configuration; runs of every height exist under the horizon | `Barnacle.Progress.holds` *(Barnacle/Progress/Proof)* | | BN9 | the heads descent: the liveness clause from the descent laws and a run of heads; round-robin has runs of heads by pigeonhole and is live at every count | `Barnacle.Heads.holds` *(Barnacle/Heads/Proof)* | | BN10 | the three rules satisfy the laws and the descent laws, and are live under round-robin at every count | `Barnacle.Mysticeti.holds`, `Barnacle.MysticetiLive.holds`, `Barnacle.Odontoceti.holds`, `Barnacle.Nemo.holds` *(LeanDagTest/Barnacle/Rules/Mysticeti/Proof, .../MysticetiLive/Proof, .../Odontoceti/Proof, .../Nemo/Proof)* | | BN11 | the mechanism over any rule with agreement and the descent laws, under any schedule whose good leaders run a wave, reaches every height with no clause left assumed, on any view caught up to the horizon; round-robin is one such schedule | `Barnacle.Live.holds`, `Barnacle.Live.runsExist_roundRobin`, `coversUpto_full` *(Barnacle/Live/Proof, Barnacle/Helpers/Cover)* | | BN12 | a healthy window is counted as healthy, and the rule then raises the count: the loop cannot back off where every scoring slot committed | `Barnacle.Healthy.holds` *(Barnacle/Healthy/Proof)* | | BN13 | BN11 on data: runs of every height on the grown family under the real rule, with nothing assumed | `real_runs` witnesses *(LeanDagTest/Barnacle/Real)* | | BN14 | validity: a good author's block lies in the history of the block a closed configuration's anchor commits; every rule over the block universe delivers | `Barnacle.Validity.holds`, `delivers_core` *(Barnacle/Validity/Proof, Barnacle/Helpers/Delivery)* | **Hydrozoan** (§22): | Label | Statement | Lean | |:---|:---|:---| | HZ1 | the six threshold inequalities of the consistency argument hold for every fault configuration, with no cap on the slack | `Hydrozoan.ThresholdArithmetic.holds` *(Hydrozoan/ThresholdArithmetic/Proof)* | | HZ2 | the direct rules never disagree: fast/fast, slow/slow and fast/slow agreement across views, certificate uniqueness, commit/skip exclusion | `Hydrozoan.DirectSafety.holds` *(Hydrozoan/DirectSafety/Proof)* | | HZ3 | any two verdicts on one slot agree, across views and across the six routes | `Hydrozoan.SlotAgreement.holds` *(Hydrozoan/SlotAgreement/Proof)* | | HZ4 | committed sequences agree at equal horizons and are prefixes at different ones, ledgers included | `Hydrozoan.PrefixAgreement.holds` *(Hydrozoan/PrefixAgreement/Proof)* | | HZ5 | a synchronised, populated wave with a correct leader slow-commits, and the verdict is derivable at the eventual view | `Hydrozoan.DirectLiveness.holds` *(Hydrozoan/DirectLiveness/Proof)* | | HZ6 | the graded rule is total below an anchor, and a committed run decides every slot beneath it | `Hydrozoan.IndirectLiveness.holds` *(Hydrozoan/IndirectLiveness/Proof)* | | HZ7 | a synchronised, populated, correct-led run decides everything below it, and fairness places runs past every slot and round | `Hydrozoan.EventualDecision.holds` *(Hydrozoan/EventualDecision/Proof)* | | HZ8 | the wave-aligned rotation is fair with no premise, the hypothesis package is realizable at every horizon, and progress is achievable | `Hydrozoan.Grounding.holds` *(Hydrozoan/Grounding/Proof)* | **Optimal-Hydrozoan** (§23): | Label | Statement | Lean | |:---|:---|:---| | OH1 | the threshold table at the Optimal allowance holds for every non-trivial fault configuration, with no cap on the slack; fast/fast agreement's `f ≥ 1` guard is exact | `OptimalHydrozoan.ThresholdArithmetic.holds` *(OptimalHydrozoan/ThresholdArithmetic/Proof)* | | OH2 | the Optimal direct rules never disagree: fast/fast, fast/slow agreement, commit/skip exclusion against the `qCert` blames, with certificates and the slow path Hydrozoan's | `OptimalHydrozoan.DirectSafety.holds` *(OptimalHydrozoan/DirectSafety/Proof)* | | OH3 | any two verdicts on one slot agree, across views and the six routes, with no order on ids: the evidence rung is unique | `OptimalHydrozoan.SlotAgreement.holds` *(OptimalHydrozoan/SlotAgreement/Proof)* | | OH4 | committed sequences agree at equal horizons and are prefixes at different ones, ledgers included | `OptimalHydrozoan.PrefixAgreement.holds` *(OptimalHydrozoan/PrefixAgreement/Proof)* | | OH5 | a synchronised, populated, correct-led wave slow-commits, and a candidate-less slot is skipped by the guaranteed quorum with no synchrony or fault-count hypothesis | `OptimalHydrozoan.DirectLiveness.holds` *(OptimalHydrozoan/DirectLiveness/Proof)* | | OH6 | the graded rule with the evidence rung is total below an anchor, and a committed run decides every slot beneath it | `OptimalHydrozoan.IndirectLiveness.holds` *(OptimalHydrozoan/IndirectLiveness/Proof)* | | OH7 | a synchronised, populated, correct-led run decides everything below it, and fairness places runs past every slot and round | `OptimalHydrozoan.EventualDecision.holds` *(OptimalHydrozoan/EventualDecision/Proof)* | | OH8 | the wave-aligned rotation is fair with no premise, the hypothesis package is realizable at every horizon under every schedule by an `OptUniverse`, and progress is achievable by a correct-authored universe | `OptimalHydrozoan.Grounding.holds` *(OptimalHydrozoan/Grounding/Proof)* | **Hydrozoan and Optimal-Hydrozoan through the properties** (§22.7, §23.7): | Label | Statement | Lean | |:---|:---|:---| | HI3 | Hydrozoan's universe satisfies the causal-structure interface, by its own fields | `BlockRecord.causal` *(Common/CausalHistory)* | | HI4 | Hydrozoan as a Barnacle base rule, with its laws | `Barnacle.Hydrozoan.holds` *(LeanDagTest/Barnacle/Rules/Hydrozoan/Proof)* | | HI5 | as a live rule: the descent laws at slack `f + c`, and round-robin liveness at `3(f + c) + 1 ≤ n` | `Barnacle.HydrozoanLive.holds` *(LeanDagTest/Barnacle/Rules/HydrozoanLive/Proof)* | | HI6 | the same two for Optimal-Hydrozoan, its exclusion rule a clause of validity | `Barnacle.OptimalHydrozoan.holds`, `Clause.leaderExcluded` *(LeanDagTest/Barnacle/Rules/OptimalHydrozoan/Proof, Common/BlockRecord)* | | HI7 | verdicts survive the cut, for both rules, on the base-slot premise alone | `DagRule.OnRecord.decided_chop_iff` at `Hydrozoan.onRecord` and `OptimalHydrozoanProperties.onRecord` *(Properties/Arcs/Record, Integration/OptimalMechanisms)* | | HI9 | verdicts survive the copy fill for both rules, with no quorum hypothesis; exclusion survives it as a clause of validity | `DagRule.OnRecord.decided_agree_copyFill` at the two instances; `ValidOpt.copyStable` *(Properties/Arcs/Record, OptimalHydrozoan/Model/Universe)* | | HI10 | what a deployment gets: the headlines at both rules | `Hydrozoan.Properties.safety`, `Hydrozoan.Properties.progress`, `OptimalHydrozoanProperties.safety`, `OptimalHydrozoanProperties.progress` *(Hydrozoan/Properties/Proof, OptimalHydrozoan/Carrier)* | --- ## Appendix B. The definition reference The 315 definitions and structures the report names, in the order a reader meets them. Each entry is the source text, unabridged, with the explanation the source carries. This appendix is generated from the compiled development by `scripts/gen-reference.py`, which selects what the body and Appendix A cite; the statements are therefore the declarations themselves rather than transcriptions of them. Some entries carry proofs, which can look like a misclassification. They are not. A structure in Lean may have fields that are propositions — a block record requires causal closure, validity and non-equivocation — so *constructing* one means discharging those obligations, and the proof is part of the definition rather than a theorem about it. A theorem, by contrast, asserts a proposition about objects already built, and those are Appendix C. ### The validator set and the fault model #### `Faults` *class, `Common.Validators.lean`* ```lean class Faults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where /-- The fault bound. -/ f : ℕ /-- The Byzantine validators. Everything else is correct. -/ byzantine : Finset Validator /-- There are at least `3f+1` validators. -/ card_validators : 3 * f + 1 ≤ Fintype.card Validator /-- At most `f` validators are Byzantine. -/ card_byzantine : byzantine.card ≤ f ``` The fault model: `n ≥ 3f+1` validators, at most `f` of them Byzantine. #### `Correct` *def, `Common.Validators.lean`* ```lean def Correct : Finset Validator := (F.byzantine)ᶜ ``` The correct (non-Byzantine) validators. ### Blocks, validity, and the universe #### `Block` *structure, `Common.Block.lean`* ```lean structure Block (Validator BlockId Payload : Type*) where /-- The round this block was produced in. -/ round : ℕ /-- The validator that authored the block. -/ creator : Validator /-- Ids of the blocks this one references, all from round `round - 1`. -/ refs : Finset BlockId /-- Opaque application data. Inert throughout Phase 1. -/ payload : Payload ``` A block: its round, its author, the ids it references from the preceding round, and an opaque payload. #### `creatorsOf` *def, `Common.Block.lean`* ```lean def creatorsOf (blk : BlockId → Block Validator BlockId Payload) (s : Finset BlockId) : Finset Validator := s.image (fun i => (blk i).creator) ``` The validators that authored a set of ids. Defined on an arbitrary `Finset BlockId`, not just on a block's refs: T3's hypothesis, T4's commit rule, and T0' all quantify over id-sets that are nobody's refs. #### `ValidWrt` *structure, `Common.Block.lean`* ```lean structure ValidWrt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every reference sits in the immediately preceding round. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- A block never cites the same author twice. -/ distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j /-- Non-genesis blocks reference a quorum of distinct validators. -/ quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card /-- Non-genesis blocks reference *some* block by their own creator, not a unique one — an equivocator's blocks form a forest of predecessor chains, one edge per block, which this does not collapse. -/ self_parent : 0 < b.round → ∃ i ∈ b.refs, (blk i).creator = b.creator ``` Block validity, relative to a lookup function. The predecessor condition is stated additively (`+ 1 = round`, not `round - 1`), which avoids `ℕ`-subtraction and makes the genesis case derivable rather than a branch: at round `0`, `refs = ∅` follows from unsatisfiability (`refs_empty_of_round_zero`). The quorum is on the *creator set*, not `refs.card` — the faithful reading of "2f+1 validators". #### `BlockUniverse` *abbrev, `Common.BlockDag.lean`* ```lean abbrev BlockUniverse (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] := BlockRecord Validator BlockId Payload ValidWrt (Correct : Finset Validator) ``` **The block universe**: every block that exists, authored by anyone, correct or Byzantine. The block record (`BlockRecord.lean`) at the core's validity predicate, with non-equivocation asked of the correct validators only. `block` is total, with junk outside `ids`; every clause quantifies over `i ∈ ids`, so the junk is never observed. #### `View` *abbrev, `Common.BlockDag.lean`* ```lean abbrev View (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] (U : BlockUniverse Validator BlockId Payload) := BlockRecord.View U ``` **A view**: one validator's local sub-DAG, a subset of the universe itself closed under references. Views share `U.block`, so they disagree about *which* blocks they hold, never about what an id denotes, and they inherit validity and non-equivocation from `U` unchanged. Different correct validators may hold different views — that asymmetry is the entire point of the cross-view results. #### `VotesAt` *def, `Common.BlockDag.lean`* ```lean def VotesAt (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (r : ℕ) (L : BlockId) : Prop := ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = r + 1 → L ∈ (U.block c).refs ``` **What the two-round rules count**: every `T`-authored block one round above `r` references `L`. Both pacing disciplines supply it — full coverage via `votesAt_of_synchronisedOn`, the reactive exit via `ReactivePace.votes` — so the commit arguments are stated against it and proved once. ### Causal structure #### `CausalStructure` *structure, `Common.Causality.lean`* ```lean structure CausalStructure (blk : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) : Prop where /-- Every referenced block is itself present. -/ complete : ∀ i ∈ ids, ∀ j ∈ (blk i).refs, j ∈ ids /-- A reference sits in the round immediately below its referrer. -/ refs_round : ∀ i ∈ ids, ∀ j ∈ (blk i).refs, (blk j).round + 1 = (blk i).round ``` **The structural core of a block DAG.** A population closed under references, whose references sit one round below. This is everything the causal-history layer consumes of a universe — no validity beyond the predecessor condition, no quorum, no fault model. #### `Reaches` *def, `Common.CausalHistory.lean`* ```lean def Reaches (U : BlockRecord Validator BlockId Payload P honest) : BlockId → BlockId → Prop := ReachesFrom U.block ``` `Reaches U c b` — `b` lies in the causal history of `c`. #### `history` *def, `Common.History.lean`* ```lean def history (U : BlockRecord Validator BlockId Payload P honest) (b : BlockId) : Finset BlockId := historyFrom U.block b ``` The causal history of `b`, as a `Finset`. #### `BlockRecord.historyView` *def, `Common.History.lean`* ```lean def BlockRecord.historyView (U : BlockRecord Validator BlockId Payload P honest) (A : BlockId) (hA : A ∈ U.ids) : U.View where ids := history U A subset_ids := history_subset_ids hA complete := fun _ hi _ hj => (mem_history_iff hA).mpr (((mem_history_iff hA).mp hi).trans (ReachesFrom.single hj)) ``` **The causal history of a block, as a view.** #### `LinkedVia` *abbrev, `Common.History.lean`* ```lean abbrev LinkedVia (U : BlockRecord Validator BlockId Payload P honest) (A : BlockId) (s : Finset BlockId) : Prop := ∃ C ∈ s, Reaches U A C ``` Some block of `s` lies in `A`'s causal history. #### `coneSupporters` *def, `Common.History.lean`* ```lean def coneSupporters (U : BlockRecord Validator BlockId Payload P honest) (A L : BlockId) (n : ℕ) : Finset Validator := creatorsOf U.block (coneVotesFor U A L n) ``` The authors of the round-`n` votes for `L` in `A`'s cone. #### `authorsAt` *def, `Common.Support.lean`* ```lean def authorsAt (U : BlockRecord Validator BlockId Payload P honest) (n : ℕ) : Finset Validator := creatorsOf U.block (blocksAt U n) ``` The validators holding a block at a given round — the pool `p`. #### `supporters` *def, `Common.Support.lean`* ```lean def supporters (U : BlockRecord Validator BlockId Payload P honest) (b : BlockId) (n : ℕ) : Finset Validator := creatorsOf U.block (votesFor U b n) ``` The validators whose round-`n` block references `b`. #### `blames` *def, `Common.Support.lean`* ```lean def blames (U : BlockRecord Validator BlockId Payload P honest) (L : BlockId) (n : ℕ) : Finset Validator := creatorsOf U.block (omissionsOf U L n) ``` The validators whose round-`n` block declines to reference `L`. An honest validator supports or blames, never both (`not_mem_of_supports_of_blames`); a Byzantine one may do both. #### `heldAuthors` *def, `Common.Support.lean`* ```lean def heldAuthors (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (s : Finset BlockId) : Finset Validator := creatorsOf U.block (s ∩ V.ids) ``` The authors of the blocks of `s` that a view holds. #### `HoldsAtLeast` *def, `Common.Support.lean`* ```lean def HoldsAtLeast (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (t : ℕ) (s : Finset BlockId) : Prop := t ≤ (heldAuthors U V s).card ``` **A view holds blocks of `s` from at least `t` distinct authors.** #### `supportersIn` *def, `Common.Support.lean`* ```lean def supportersIn (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (b : BlockId) (n : ℕ) : Finset Validator := heldAuthors U V (votesFor U b n) ``` The supporters of `b` at round `n` that a view holds. #### `blamesIn` *def, `Common.Support.lean`* ```lean def blamesIn (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (L : BlockId) (n : ℕ) : Finset Validator := heldAuthors U V (omissionsOf U L n) ``` The blamers of `L` at round `n` that a view holds. #### `carriedVotes` *def, `Common.Support.lean`* ```lean def carriedVotes (U : BlockRecord Validator BlockId Payload P honest) (Vote : BlockId → BlockId → Prop) [∀ b L, Decidable (Vote b L)] (C L : BlockId) : Finset BlockId := (U.block C).refs.filter (fun b => Vote b L) ``` The references of `C` that vote for `L`: the votes `C` carries for `L`. #### `CarriesVotes` *def, `Common.Support.lean`* ```lean def CarriesVotes (U : BlockRecord Validator BlockId Payload P honest) (Vote : BlockId → BlockId → Prop) [∀ b L, Decidable (Vote b L)] (t : ℕ) (C L : BlockId) : Prop := t ≤ (creatorsOf U.block (carriedVotes U Vote C L)).card ``` **`C` certifies `L` at threshold `t`**: it carries votes for `L` from `t` distinct authors. #### `certificatesAt` *def, `Common.Support.lean`* ```lean def certificatesAt (U : BlockRecord Validator BlockId Payload P honest) (Vote : BlockId → BlockId → Prop) [∀ b L, Decidable (Vote b L)] (t : ℕ) (L : BlockId) (n : ℕ) : Finset BlockId := (blocksAt U n).filter (fun C => CarriesVotes U Vote t C L) ``` The round-`n` blocks certifying `L` at threshold `t`. ### Slots and the schedule #### `IsLeaderBlock` *def, `Common.Leader.lean`* ```lean def IsLeaderBlock (U : BlockRecord Validator BlockId Payload P honest) (k : ℕ) (L : BlockId) : Prop := L ∈ U.ids ∧ (U.block L).round = S.slotRound k ∧ (U.block L).creator = S.leader k ``` `L` is a candidate block for slot `k`: the right round, the right author. #### `leaderBlocksAt` *def, `Common.Leader.lean`* ```lean def leaderBlocksAt (U : BlockRecord Validator BlockId Payload P honest) (k : ℕ) : Finset BlockId := (blocksAt U (S.slotRound k)).filter (fun b => (U.block b).creator = S.leader k) ``` The candidates of slot `k`, as a set. #### `slotBlamers` *def, `Common.Leader.lean`* ```lean def slotBlamers (U : BlockRecord Validator BlockId Payload P honest) (k : ℕ) : Finset BlockId := (blocksAt U (S.slotRound k + 1)).filter (fun q => ∀ j ∈ (U.block q).refs, ¬ IsLeaderBlock U k j) ``` The voting-round blocks that reference **no candidate** of slot `k`. #### `slotBlames` *def, `Common.Leader.lean`* ```lean def slotBlames (U : BlockRecord Validator BlockId Payload P honest) (k : ℕ) : Finset Validator := creatorsOf U.block (slotBlamers U k) ``` The validators whose voting-round block blames slot `k`. #### `slotBlamesIn` *def, `Common.Leader.lean`* ```lean def slotBlamesIn (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (k : ℕ) : Finset Validator := heldAuthors U V (slotBlamers U k) ``` The blamers of slot `k` that a view holds. ### Delivery, growth, and coverage #### `PopulatedFrom` *def, `Common.Participation.lean`* ```lean def PopulatedFrom (blk : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (T : Finset Validator) (r : ℕ) : Prop := ∀ v ∈ T, ∃ b ∈ ids, (blk b).creator = v ∧ (blk b).round = r ``` Every validator in `T` authors a block at round `r` among `ids`. #### `SynchronisedFrom` *def, `Common.Participation.lean`* ```lean def SynchronisedFrom (blk : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (T : Finset Validator) (R : ℕ) : Prop := ∀ n, R ≤ n → ∀ b ∈ ids, (blk b).round = n + 1 → (blk b).creator ∈ T → ∀ a ∈ ids, (blk a).round = n → (blk a).creator ∈ T → a ∈ (blk b).refs ``` From round `R` on, every `T`-authored block references every `T`-authored block of the round below. Both quantifiers restricted to `T`, deliberately. #### `PopulatedOn` *def, `Common.Participation.lean`* ```lean def PopulatedOn (U : BlockRecord Validator BlockId Payload P honest) (T : Finset Validator) (r : ℕ) : Prop := PopulatedFrom U.block U.ids T r ``` Every validator in `T` has a block at round `r`. #### `SynchronisedOn` *def, `Common.Participation.lean`* ```lean def SynchronisedOn (U : BlockRecord Validator BlockId Payload P honest) (T : Finset Validator) (R : ℕ) : Prop := SynchronisedFrom U.block U.ids T R ``` From round `R` on, every `T`-authored block references every `T`-authored block of the round below. An assumption about the network after stabilisation, not a theorem: a block's references are frozen when it is built. #### `CertifiesAt` *def, `Mysticeti.Liveness.lean`* ```lean def CertifiesAt (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (r : ℕ) (L : BlockId) : Prop := ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = r + 2 → Certifies U c L ``` **What the three-round rule counts**: every `T`-authored block at the decision round certifies `L`. Coverage implies it through the vote layer (`certifiesAt_of_synchronisedOn`); the reactive certificate wait supplies it directly (`ReactiveM.certifies`). ### Time: GST, and the rated bounds #### `Rated` *def, `Mysticeti.Quantitative.lean`* ```lean def Rated (timeout : ℕ → ℕ) : Prop := ∀ n, n ≤ timeout n ``` A backoff that grows at least as fast as the round index. Any schedule dominating the identity qualifies; it rules out growth slow enough that clearing a fixed threshold takes unboundedly many rounds. #### `FairWithin` *def, `Mysticeti.Quantitative.lean`* ```lean def FairWithin (T : Finset Validator) (w : ℕ) : Prop := ∀ k, ∃ k', k ≤ k' ∧ k' < k + w ∧ S.leader k' ∈ T ``` The schedule names a `T`-leader within every window of `w` slots, the rated form of `FairScheduleOn`. `w` is a property of the schedule alone, which keeps L6's quantifier order intact. ### The pacing structures, and the delivery they induce #### `DriftOn` *def, `Mysticeti.ViewPace.lean`* ```lean def DriftOn (built : Validator → ℕ → ℕ) (T : Finset Validator) (R D N : ℕ) : Prop := ∀ v ∈ T, ∀ w ∈ T, ∀ n, R ≤ n → n ≤ N → built w n ≤ built v n + D ``` Drift over a build schedule alone: `T`-validators are never more than `D` apart in real time at the same round, from `R` on. #### `ConvergesEventually` *def, `Mysticeti.ViewPace.lean`* ```lean def ConvergesEventually (holds : Validator → ℕ → Finset BlockId) (T : Finset Validator) : Prop := ∀ v ∈ T, ∀ w ∈ T, ∀ t, ∃ d, holds w t ⊆ holds v (t + d) ``` **The qualitative half.** Holdings converge: whatever `w` holds at `t`, `v` holds at some later time. No bound and no GST. #### `ConvergesWithin` *def, `Mysticeti.ViewPace.lean`* ```lean def ConvergesWithin (holds : Validator → ℕ → Finset BlockId) (T : Finset Validator) (gst bound : ℕ) : Prop := ∀ v ∈ T, ∀ w ∈ T, ∀ t, gst ≤ t → holds w t ⊆ holds v (t + bound) ``` **The quantitative half.** From `gst` on, that lag is at most `bound` — and uniformly so, in the validators and in the time. This is exactly the `converges` field of `ViewPace`. #### `PaceCore` *structure, `Mysticeti.ViewPace.lean`* ```lean structure PaceCore (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (N : ℕ) where /-- The highest round `v` reached. Rounds above it were never built. -/ top : Validator → ℕ /-- When `v` built its round-`n` block — read only at `n ≤ top v`. -/ built : Validator → ℕ → ℕ timeout : ℕ → ℕ gst : ℕ delay : ℕ rounds_le : ∀ b ∈ U.ids, (U.block b).round ≤ N /-- **Every round `v` reached, it built in.** At `n = 0` this is genesis — a validator produces its genesis block alone, so this much needs no network at all. Above `0` it is the reading of `top`: the validator got there, which is to say it built there. -/ built_of_le_top : ∀ v ∈ T, ∀ n ≤ top v, ∃ b ∈ U.ids, (U.block b).creator = v ∧ (U.block b).round = n /-- **And no round above it.** Together with the previous clause, `v`'s blocks are exactly rounds `0` through `top v`. -/ le_top_of_built : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round ≤ top v timeout_pos : ∀ n, 1 ≤ timeout n /-- An upper bound on when round `n` was built, over `T`. Only an upper bound is ever needed: production reads it for the common time at which `converges` assembles the quorum, and nothing else consults it. -/ latest : ℕ → ℕ built_le_latest : ∀ v ∈ T, ∀ n ≤ N, built v n ≤ latest n holds : Validator → ℕ → Finset BlockId /-- **S3.** A validator holds only blocks that exist. Nothing else ties `holds` to the universe: the liveness development never needs it, and the clause is stated here only so that a validator's holdings generate a `View` (`viewAt`), which is what connects the pacing line to the view-relative decision rules. -/ holds_sub : ∀ v, ∀ t, holds v t ⊆ U.ids /-- **S4.** Holdings are causally closed: a validator that holds a block holds everything it references — P4 as a store property, and what makes `viewAt` a validator's own view rather than the closure of its fragments. -/ holds_closed : ∀ v ∈ T, ∀ t, ∀ b ∈ holds v t, ∀ j ∈ (U.block b).refs, j ∈ holds v t /-- **S5.** A validator's block references only what it held when it built --- the converse of P7, and like it implementable and observable. It sits on the trunk rather than in a timeout discipline because it is discipline-independent: a reactive builder omits what it holds, but no builder can cite what it never held. -/ refs_held : ∀ v ∈ T, ∀ n, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round = n + 1 → (U.block b).refs ⊆ holds v (built v (n + 1)) /-- A validator holds every block it authored, from the time it built it. -/ holds_own : ∀ v ∈ T, ∀ n ≤ N, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round = n → b ∈ holds v (built v n) holds_mono : ∀ v, ∀ s t, s ≤ t → holds v s ⊆ holds v t /-- **N2, as view convergence** (network). -/ converges : ∀ v ∈ T, ∀ w ∈ T, ∀ t, gst ≤ t → holds w t ⊆ holds v (t + delay) /-- **P8, as the pacemaker's progress rule** (protocol). A validator that holds a quorum of distinct round-`n` authors at any time gets past round `n`. Conditional on the quorum, so it asserts no production until one is in hand. -/ advances : ∀ v ∈ T, ∀ n < N, ∀ t, quorumCard Validator ≤ (authorsIn U (holds v t) n).card → n < top v /-- The processing bound: how long round entry may lag evidence. -/ proc : ℕ /-- **Catch-up** (protocol). Seeing a round is entering it: any `T`-authored block of round `n` in hand at a post-GST time `t` means the holder reached round `n` and built its own block there by `t + proc`. Asserted only from `gst`, like `converges`, since before it the clamped floor may delay a validator past what this clause would claim. Makes drift a derived quantity (`drift_collapse`), with no start-spread hypothesis surviving into the headline statements. -/ catchup : ∀ v ∈ T, ∀ n ≤ N, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = n → ∀ t, gst ≤ t → b ∈ holds v t → n ≤ top v ∧ built v n ≤ t + proc ``` **The shared trunk of every pacing discipline**, over a partial build schedule. `top v` is the highest round `v` reached: `v`'s blocks are exactly the rounds `0` through `top v`. The trunk carries the schedule data, the views, `converges`, and the pacemaker's two rules — `advances` and `catchup` — everything production and drift consume; `ViewPace` extends it with the full-timeout floor (P9) and global referencing (P7), the reactive schedule with the deadline and vote clauses instead. #### `viewAt` *def, `Mysticeti.ViewPace.lean`* ```lean def viewAt (pc : PaceCore U T N) (v : Validator) (t : ℕ) : View Validator BlockId Payload U where ids := (pc.holds v t).biUnion (history U) subset_ids := by intro i hi obtain ⟨a, ha, hia⟩ := Finset.mem_biUnion.mp hi exact history_subset_ids (pc.holds_sub v t ha) hia complete := by intro i hi j hj obtain ⟨a, ha, hia⟩ := Finset.mem_biUnion.mp hi have ha_ids : a ∈ U.ids := pc.holds_sub v t ha refine Finset.mem_biUnion.mpr ⟨a, ha, ?_⟩ exact (mem_history_iff ha_ids).mpr (((mem_history_iff ha_ids).mp hia).trans (Reaches.single hj)) ``` **The view a validator's holdings generate.** The causal closure of what `v` holds at `t` — a legitimate `View`, closure discharged by transitivity of `Reaches`. The bridge between the pacing line's time-indexed `holds` and the commit rules' `View`, letting liveness be stated about a validator's own view rather than the full universe. #### `ViewPace` *structure, `Mysticeti.ViewPace.lean`* ```lean structure ViewPace (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (N : ℕ) extends PaceCore U T N where /-- **P9, the waiting rule** (protocol), over the rounds `v` reached. -/ waits : ∀ v ∈ T, ∀ n < top v, built v n + timeout n ≤ built v (n + 1) /-- **P7, referencing** (protocol), over any block the validator authors. -/ references : ∀ v ∈ T, ∀ n < N, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 1 → ∀ a ∈ holds v (built v (n + 1)), (U.block a).round = n → a ∈ (U.block c).refs ``` The full-timeout discipline: `PaceCore` with P9 (the waiting floor) and the global referencing clause P7. The structure the coverage derivation and quantitative results run on. No promptness ceiling or attainment clause appears, since drift is derived from the trunk's catch-up rule alone. ### Denial of service #### `ExposedIn` *def, `DoS.Exposure.lean`* ```lean def ExposedIn (U : BlockUniverse Validator BlockId Payload) (b : BlockId) (X : Validator) : Prop := ∃ i ∈ history U b, ∃ j ∈ history U b, EquivPair U X i j ``` **`X` is exposed in `b`'s history**: two distinct blocks by `X` at one round lie below `b`. #### `DoSValid` *def, `DoS.Exposure.lean`* ```lean def DoSValid (U : BlockUniverse Validator BlockId Payload) : Prop := ∀ b ∈ U.ids, ∀ i ∈ (U.block b).refs, ¬ ExposedIn U b (U.block i).creator ``` **The DoS-protection condition** (`dos-equivocation-and-growth.md` §3): a block may not reference an author exposed in its own history. A predicate on the universe, not a field of `ValidWrt`, so results that need it take it as an extra hypothesis. #### `View.ofAccepted` *def, `DoS.Acceptance.lean`* ```lean def View.ofAccepted (h : Accepted U A n) : View Validator BlockId Payload U where ids := A.biUnion (history U) subset_ids := by intro i hi obtain ⟨a, ha, hia⟩ := Finset.mem_biUnion.mp hi exact history_subset_ids (h.subset_ids ha) hia complete := by intro i hi j hj obtain ⟨a, ha, hia⟩ := Finset.mem_biUnion.mp hi have ha_ids : a ∈ U.ids := h.subset_ids ha refine Finset.mem_biUnion.mpr ⟨a, ha, ?_⟩ exact (mem_history_iff ha_ids).mpr (((mem_history_iff ha_ids).mp hia).trans (Reaches.single hj)) ``` **D1.** The view an accepted set generates: the union of the histories of what it accepted, downward closed by transitivity of `Reaches`. #### `StepNovelty` *def, `DoS.Novelty.lean`* ```lean def StepNovelty (U : BlockUniverse Validator BlockId Payload) (κ' : ℕ) : Prop := ∀ b ∈ U.ids, (U.block b).creator ∈ (Correct : Finset Validator) → ∀ p ∈ (U.block b).refs, (U.block p).creator = (U.block b).creator → (novelty U (history U p) b).card ≤ κ' ``` Stepwise novelty: every correct block adds at most `κ'` blocks over the history of its self-parent, unique for a correct author. #### `viewUpto` *def, `DoS.Novelty.lean`* ```lean def viewUpto (D : Delivery U) (v : Validator) : ℕ → Finset BlockId | 0 => (D.accepted v 0).biUnion (history U) | n + 1 => viewUpto D v n ∪ (D.accepted v (n + 1)).biUnion (history U) ``` Everything `v` has retained by round `n`: the whole histories of everything it accepted at any round up to `n` — the retained view of S1, accumulated. This is what novelty is measured against, and the reason C3 works: accepting a block means holding its entire cone. #### `ByzBudget` *def, `DoS.Novelty.lean`* ```lean def ByzBudget (D : Delivery U) (κ : ℕ) : Prop := ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ D.accepted v (n + 1), (U.block b).creator ∉ (Correct : Finset Validator) → (novelty U (viewUpto D v n) b).card ≤ κ ``` The **analysis-side budget**: only the Byzantine clause. The weakest thing the theorems need; the correct clause is derived from it (`card_novelty_le_of_byzBudget`). The enforced form is `UniformBudget` below. #### `UniformBudget` *def, `DoS.Novelty.lean`* ```lean def UniformBudget (D : Delivery U) (τ : ℕ) : Prop := ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ D.accepted v (n + 1), (novelty U (viewUpto D v n) b).card ≤ τ ``` **The mechanism-side budget** — the rule a validator actually runs: a guard-free cap on every acceptance, author-blind. The two formulations sandwich within one factor of `f` (`uniform_of_byzBudget`). #### `RefsAccepted` *def, `DoS.Novelty.lean`* ```lean def RefsAccepted (D : Delivery U) : Prop := ∀ w ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ U.ids, (U.block b).creator = w → (U.block b).round = n + 1 → (U.block b).refs ⊆ D.accepted w n ``` D3's ordinary case as a protocol property: a correct validator's block references **only** what it accepted — the converse of `includes`; together they say `refs = accepted`. ### Garbage collection #### `Slots.chop` *def, `GC.ChopDecided.lean`* ```lean def Slots.chop (S : Slots Validator) (G d : ℕ) (hd : G ≤ S.slotRound d) : Slots Validator where slotRound k := S.slotRound (d + k) - G leader k := S.leader (d + k) mono _ _ h := Nat.sub_le_sub_right (S.mono (Nat.add_le_add_left h d)) G unbounded := by intro n obtain ⟨k, hk⟩ := S.unbounded (G + n) rcases Nat.le_total k d with hkd | hdk · refine ⟨0, ?_⟩ have := S.mono hkd simp only [Nat.add_zero] omega · refine ⟨k - d, ?_⟩ have hcancel : d + (k - d) = k := by omega simp only [hcancel] omega keyed := by intro k₁ k₂ h simp only [Prod.mk.injEq] at h obtain ⟨hr, hl⟩ := h have h₁ := hd.trans (S.mono (Nat.le_add_right d k₁)) have h₂ := hd.trans (S.mono (Nat.le_add_right d k₂)) have hpair : (S.slotRound (d + k₁), S.leader (d + k₁)) = (S.slotRound (d + k₂), S.leader (d + k₂)) := by have : S.slotRound (d + k₁) = S.slotRound (d + k₂) := by omega rw [this, hl] have := S.keyed hpair omega ``` The truncation's slot schedule: slots re-indexed from a base slot `d` whose round clears the horizon, rounds rebased by `−G`. The base-slot condition keeps subtraction faithful, which is what keying needs. #### `chopD` *def, `GC.Window.lean`* ```lean def chopD (D : Delivery U) (G : ℕ) : Delivery (chop U G) where held v m := D.held v (G + m) held_spec := by intro v m i hi obtain ⟨h1, h2⟩ := D.held_spec v (G + m) i hi refine ⟨mem_chop_ids.mpr ⟨h1, by omega⟩, ?_⟩ rw [chop_block, chopBlk_round] omega accepted v m := D.accepted v (G + m) accepted_sub v m := D.accepted_sub v (G + m) accepted_inj := by intro v m i hi j hj hij rw [chop_block, chopBlk_creator, chopBlk_creator] at hij exact D.accepted_inj v (G + m) i hi j hj hij accepts_correct := by intro v hv m a ha hac rw [chop_block, chopBlk_creator] at hac exact D.accepts_correct v hv (G + m) a ha hac includes := by intro v hv m b hb hbc hbr rw [mem_chop_ids] at hb rw [chop_block, chopBlk_creator] at hbc rw [chop_block, chopBlk_round] at hbr have hsub := D.includes v hv (G + m) b hb.1 hbc (by omega) intro i hi rw [chop_block, chopBlk_refs_of_lt (by omega)] exact hsub hi ``` A delivery for the truncation: round `m` of the window is round `G + m` of the original. Nothing below the cut is consulted. #### `joinView` *def, `GC.Bootstrap.lean`* ```lean def joinView {R m t : ℕ} (hs : Synchronised U R) (hw : w ∈ (Correct : Finset Validator)) (hcar : Populated U (m + 1)) (hpop : Populated U t) (hR : R ≤ m + 1) (hmt : m + 2 ≤ t) : View Validator BlockId Payload (chop U G) where ids := joinIds D w m t G subset_ids := by intro i hi rcases Finset.mem_union.mp hi with h | h · obtain ⟨⟨hids, hround⟩, -⟩ := mem_base.mp h exact mem_chop_ids.mpr ⟨hids, by omega⟩ · obtain ⟨hiv, hround⟩ := Finset.mem_filter.mp h exact mem_chop_ids.mpr ⟨viewUpto_subset_ids hiv, by omega⟩ complete := by intro i hi j hj rw [chop_block] at hj rcases Finset.mem_union.mp hi with h | h · obtain ⟨⟨hids, hround⟩, -⟩ := mem_base.mp h rw [chopBlk_refs_of_le (by omega)] at hj simp at hj · obtain ⟨hiv, hround⟩ := Finset.mem_filter.mp h have hiids : i ∈ U.ids := viewUpto_subset_ids hiv rw [chopBlk_refs_of_lt hround] at hj have hjv : j ∈ viewUpto D w m := mem_viewUpto_of_mem_refs hiv hj have hjr : (U.block j).round + 1 = (U.block i).round := U.round_of_mem_refs hiids hj rcases Nat.lt_or_ge G (U.block j).round with hlt | hge · exact Finset.mem_union_right _ (Finset.mem_filter.mpr ⟨hjv, hlt⟩) · exact Finset.mem_union_left _ (accepted_mem_base hs hw hjv (by omega) hcar hpop hR hmt) ``` **G12, the assembly.** Base plus window is a bona-fide view of the truncation. Closure is the whole content: a window reference above the cut is in the window (stores are reference-closed), and a window reference *at* the cut is a round-`G` block the peer accepted — which is exactly what G11 puts in the base. The base layer itself has no references to chase: `chop` made it the genesis layer. ### Odontoceti #### `Faults5` *class, `Odontoceti.Rules.lean`* ```lean class Faults5 (Validator : Type*) [Fintype Validator] [DecidableEq Validator] extends Faults Validator where /-- There are at least `5f+1` validators. -/ card_validators5 : 5 * f + 1 ≤ Fintype.card Validator ``` The Odontoceti committee: `n ≥ 5f+1`. An extension of `Faults`, so every existing theorem applies to the same types unchanged; the new bound is consumed only where the two-round arithmetic needs it. #### `ThickLink` *def, `Odontoceti.Rules.lean`* ```lean def ThickLink (U : BlockUniverse Validator BlockId Payload) (A L : BlockId) (r : ℕ) : Prop := coneLink (Fintype.card Validator - 3 * F.f) U A L r ``` **The indirect test** (the thesis's ThickLink): at least `n − 3f` distinct authors of support blocks for `L` in the anchor's cone. At `n = 5f+1` this is the thesis's `2f+1`. #### `DirectCommitIn` *abbrev, `Odontoceti.Decision.lean`* ```lean abbrev DirectCommitIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := supportCommit (quorumCard Validator) U V L r ``` Direct commit, as judged from a single view: the view holds votes for `L` at the round above it from a quorum of validators. #### `DirectSkipIn` *abbrev, `Odontoceti.Decision.lean`* ```lean abbrev DirectSkipIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V (quorumCard Validator) (omissionsOf U L (r + 1)) ``` Direct skip, as judged from a single view: the view holds blocks at the round above `L` that omit it, from a quorum of validators. #### `Decided` *abbrev, `Odontoceti.Decision.lean`* ```lean abbrev Decided (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) : ℕ → Option BlockId → Prop := (odontocetiAnchored Validator BlockId Payload).Decided (S := S) U V ``` **The decision relation**: the anchored relation at Odontoceti's data. #### `DecidedWithin` *abbrev, `Odontoceti.Decision.lean`* ```lean abbrev DecidedWithin (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (B : ℕ) : ℕ → Option BlockId → Prop := (odontocetiAnchored Validator BlockId Payload).DecidedWithin (S := S) U V B ``` **The bounded relation**, at Odontoceti's data. ### The reactive schedule #### `ReactivePace` *structure, `Reactive.Basic.lean`* ```lean structure ReactivePace (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (N : ℕ) extends PaceCore U T N where /-- Time advances with rounds — the only lower bound a reactive schedule keeps, over the rounds `v` reached. -/ built_lt : ∀ v ∈ T, ∀ n < top v, built v n < built v (n + 1) /-- **The reactive ceiling.** A validator never waits past the timeout; it may build any time before it. -/ deadline : ∀ v ∈ T, ∀ n < top v, built v (n + 1) ≤ built v n + timeout n /-- **The leader wait.** At the round above a reliable leader, any `T`-authored block either votes (the reactive exit), or its builder waited the full timeout and votes for any leader block it holds (the fallback). -/ vote_or_wait : ∀ v ∈ T, ∀ k : ℕ, S.slotRound k + 1 ≤ N → S.leader k ∈ T → ∀ L, IsLeaderBlock U k L → ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = S.slotRound k + 1 → L ∈ (U.block c).refs ∨ (built v (S.slotRound k) + timeout (S.slotRound k) ≤ built v (S.slotRound k + 1) ∧ (L ∈ holds v (built v (S.slotRound k + 1)) → L ∈ (U.block c).refs)) /-- **The reactive exit is prompt.** Once a validator past its round entry holds the leader and every reliable round-`r` block, it builds within `proc`. Consumed only by the fast-path results. -/ prompt_vote : ∀ v ∈ T, ∀ k : ℕ, S.slotRound k + 1 ≤ N → S.leader k ∈ T → ∀ L, IsLeaderBlock U k L → ∀ t, built v (S.slotRound k) ≤ t → L ∈ holds v t → (∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ holds v t) → built v (S.slotRound k + 1) ≤ t + proc ``` The reactive schedule and network layer, shared by both protocols: `PaceCore` with `deadline`, `built_lt`, `vote_or_wait` and `prompt_vote` in place of `ViewPace`'s full-timeout floor. #### `ReactiveM` *structure, `Reactive.Mysticeti.lean`* ```lean structure ReactiveM (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (N : ℕ) extends ReactivePace U T N where /-- **The certificate wait.** At two rounds above a reliable leader, any `T`-authored block either already certifies (the reactive exit — its references carry a quorum of votes), or its builder waited the full timeout and references every reliable vote it holds (the fallback). -/ cert_or_wait : ∀ v ∈ T, ∀ k : ℕ, S.slotRound k + 2 ≤ N → S.leader k ∈ T → ∀ L, IsLeaderBlock U k L → ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = S.slotRound k + 2 → Certifies U c L ∨ (built v (S.slotRound k + 1) + timeout (S.slotRound k + 1) ≤ built v (S.slotRound k + 2) ∧ ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k + 1 → b ∈ holds v (built v (S.slotRound k + 2)) → L ∈ (U.block b).refs → b ∈ (U.block c).refs) ``` The reactive three-round schedule: `ReactivePace`'s vote stage, plus the certificate wait, stated over any `T`-authored block. ### Safe Skip: crash recovery in one message #### `SkipMsg` *abbrev, `SafeSkip.Basic.lean`* ```lean abbrev SkipMsg (U : BlockUniverse Validator BlockId Payload) := SkipData U.ids U.block ``` **A Safe Skip message at a core universe**: the same data, read off `U`, stated over `ids`/`blk` since the data of a fill is shared across rules — only the invariants a universe carries differ. Nemo and FinWhale build their own fills from it. #### `skipFill` *def, `SafeSkip.Basic.lean`* ```lean def skipFill : BlockUniverse Validator BlockId Payload := BlockRecord.fill U sk (sk.selfBlocks U.complete) (fun _ hk1 hk2 => sk.fillBlock_valid hk1 hk2) ``` **The denotation.** `U`, extended with one filled block per gap round; every old block looked up unchanged. The block record's fill under the self-referencing reading, with `fillBlock_valid` as the one obligation. #### `selfParent` *def, `SafeSkip.Jump.lean`* ```lean noncomputable def selfParent (U : BlockUniverse Validator BlockId Payload) (b : BlockId) : BlockId := if h : ∃ i ∈ (U.block b).refs, (U.block i).creator = (U.block b).creator then h.choose else b ``` The self-parent of a block: its unique reference by its own creator. Total, with the block itself as junk value when no such reference exists (a genesis block, or an identifier outside the universe); every lemma below assumes the good case. #### `lineOf` *def, `SafeSkip.Jump.lean`* ```lean noncomputable def lineOf (U : BlockUniverse Validator BlockId Payload) (B2 : BlockId) (k : ℕ) : BlockId := (selfParent U)^[(U.block B2).round - k] B2 ``` The self-parent chain below `B2`, indexed by round: `lineOf U B2 k` is the ancestor of `B2` at round `k` on its self-parent line. Junk above `B2`'s round or off a valid chain, as usual. #### `JumpMsg` *structure, `SafeSkip.Jump.lean`* ```lean structure JumpMsg (U : BlockUniverse Validator BlockId Payload) where /-- The recovering validator. -/ v1 : Validator /-- Its last block before the crash — the anchor. -/ B1 : BlockId /-- The donor of the reference structure. -/ v2 : Validator /-- The pinned target block on the donor's line. -/ B2 : BlockId /-- Fresh ids for the filled blocks, and their decoder. -/ fresh : ℕ → BlockId idx : BlockId → ℕ /-- The anchor is `v1`'s only block at its round (see `SkipData.hB1uniq` for why this is a field rather than derived from correctness). -/ hB1uniq : ∀ j ∈ U.ids, (U.block j).creator = v1 → (U.block j).round = (U.block B1).round → j = B1 hv12 : v1 ≠ v2 hB1 : B1 ∈ U.ids hB1c : (U.block B1).creator = v1 hB2 : B2 ∈ U.ids hB2c : (U.block B2).creator = v2 hB2r : (U.block B1).round ≤ (U.block B2).round hfresh_new : ∀ k, fresh k ∉ U.ids hidx : ∀ k, idx (fresh k) = k /-- The crash: `v1` authored nothing in the gap. -/ hgap : ∀ b ∈ U.ids, (U.block b).creator = v1 → (U.block B1).round < (U.block b).round → (U.block b).round ≤ (U.block B2).round → False ``` **The jump message**: the compact core a recovering validator actually sends — its own name and anchor, the target block and its author — with the fresh-identifier supply and a `SkipMsg`'s semantic clauses about them, but no line: the line is derived. ### Integration: composing the arcs #### `chopMsg` *def, `Integration.Retention.lean`* ```lean def chopMsg (sk : SkipMsg U) (hG : G ≤ (U.block sk.B1).round) (hGr : G ≤ sk.r) : SkipMsg (chop U G) where v1 := sk.v1 B1 := sk.B1 v2 := sk.v2 r := sk.r - G line k := sk.line (G + k) fresh k := sk.fresh (G + k) idx b := sk.idx b - G hB1uniq := by intro j hj hjc hjr rw [mem_chop_ids] at hj simp only [chop_block, chopBlk_creator] at hjc simp only [chop_block, chopBlk_round] at hjr exact sk.hB1uniq j hj.1 hjc (by omega) hv12 := sk.hv12 hB1 := mem_chop_ids.mpr ⟨sk.hB1, hG⟩ hB1c := by simp only [chop_block, chopBlk_creator]; exact sk.hB1c hline_mem := by intro k hk1 hk2 simp only [chop_block, chopBlk_round] at hk1 have hlm := sk.hline_mem (G + k) (by omega) (by omega) have hlr := sk.hline_round (G + k) (by omega) (by omega) exact mem_chop_ids.mpr ⟨hlm, by omega⟩ hline_creator := by intro k hk1 hk2 simp only [chop_block, chopBlk_round] at hk1 simp only [chop_block, chopBlk_creator] exact sk.hline_creator (G + k) (by omega) (by omega) hline_round := by intro k hk1 hk2 simp only [chop_block, chopBlk_round] at hk1 ⊢ rw [sk.hline_round (G + k) (by omega) (by omega)] omega hline_chain := by intro k hk1 hk2 simp only [chop_block, chopBlk_round] at hk1 -- the line block sits strictly above the cut, so its references survive have hlm := sk.hline_mem (G + k) (by omega) (by omega) have hlr := sk.hline_round (G + k) (by omega) (by omega) have hgt : G < (U.block (sk.line (G + k))).round := by omega rw [chop_block, chopBlk_refs_of_lt hgt] have := sk.hline_chain (G + k) (by omega) (by omega) have hidx : G + k - 1 = G + (k - 1) := by omega rwa [hidx] at this hfresh_new := by intro k rw [mem_chop_ids] intro h exact sk.hfresh_new (G + k) h.1 hidx := by intro k rw [sk.hidx (G + k)] omega hgap := by intro b hb hbc hb1 hb2 rw [mem_chop_ids] at hb simp only [chop_block, chopBlk_creator] at hbc simp only [chop_block, chopBlk_round] at hb1 hb2 exact sk.hgap b hb.1 hbc (by omega) (by omega) ``` **I7b.** With the anchor retained, a Safe Skip message over the original universe induces one over the truncation: same validators, same anchor, every round rebased by `−G`. #### `recoveryMsg` *def, `Integration.ReGenesis.lean`* ```lean def recoveryMsg (r : ℕ) (line fresh : ℕ → BlockId) (idx : BlockId → ℕ) (v2 : Validator) (hv12 : v ≠ v2) (hline_mem : ∀ k, k ≤ r → line k ∈ V.ids) (hline_creator : ∀ k, k ≤ r → (V.block (line k)).creator = v2) (hline_round : ∀ k, k ≤ r → (V.block (line k)).round = ((addGenesis V v g p hg hsev).block g).round + k) (hline_chain : ∀ k, 0 < k → k ≤ r → line (k - 1) ∈ (V.block (line k)).refs) (hfresh_new : ∀ k, fresh k ∉ (addGenesis V v g p hg hsev).ids) (hidx : ∀ k, idx (fresh k) = k) : SkipMsg (addGenesis V v g p hg hsev) where v1 := v B1 := g v2 := v2 r := ((addGenesis V v g p hg hsev).block g).round + r line k := line (k - ((addGenesis V v g p hg hsev).block g).round) fresh := fresh idx := idx hB1uniq := hB1uniq_of_addGenesis hv12 := hv12 hB1 := mem_addGenesis hB1c := by rw [addGenesis_block_new] hline_mem := by intro k hk1 hk2 exact Finset.mem_insert_of_mem (hline_mem _ (by omega)) hline_creator := by intro k hk1 hk2 rw [addGenesis_block_old (hline_mem _ (by omega))] exact hline_creator _ (by omega) hline_round := by intro k hk1 hk2 rw [addGenesis_block_old (hline_mem _ (by omega))] rw [hline_round _ (by omega)] omega hline_chain := by intro k hk1 hk2 rw [addGenesis_block_old (hline_mem _ (by omega))] have := hline_chain (k - ((addGenesis V v g p hg hsev).block g).round) (by omega) (by omega) have hidx' : k - ((addGenesis V v g p hg hsev).block g).round - 1 = k - 1 - ((addGenesis V v g p hg hsev).block g).round := by omega rwa [hidx'] at this hfresh_new := hfresh_new hidx := hidx hgap := by intro b hb hbc _ _ rcases Finset.mem_insert.mp hb with rfl | ho · rw [addGenesis_block_new] at * omega · rw [addGenesis_block_old ho] at hbc exact absurd hbc (hsev b ho) ``` **The catch-up fill.** After re-genesis the returning validator rejoins production with one message: a `SkipMsg` anchored on its new genesis block, filling every round from the cut to the target. #### `skipFillD` *def, `Integration.DeliveryFill.lean`* ```lean def skipFillD (sk : SkipMsg U) (D : Delivery U) (hdown : ∀ m, sk.r0 ≤ m → m < sk.r → D.accepted sk.v1 m = ∅) : Delivery sk.skipFill where held := D.held held_spec := by intro v n i hi obtain ⟨h1, h2⟩ := D.held_spec v n i hi exact ⟨sk.ids_subset_skipFill h1, by rw [sk.skipFill_block_old h1]; exact h2⟩ accepted := D.accepted accepted_sub := D.accepted_sub accepted_inj := by intro v n i hi j hj hij have hio := (D.held_spec v n i (D.accepted_sub v n hi)).1 have hjo := (D.held_spec v n j (D.accepted_sub v n hj)).1 rw [sk.skipFill_block_old hio, sk.skipFill_block_old hjo] at hij exact D.accepted_inj v n i hi j hj hij accepts_correct := by intro v hv n a ha hac have hao := (D.held_spec v n a ha).1 rw [sk.skipFill_block_old hao] at hac exact D.accepts_correct v hv n a ha hac includes := by intro v hv n b hb hbc hbr rcases Finset.mem_union.mp hb with ho | hf · -- an old block references what it accepted, as before rw [sk.skipFill_block_old ho] at hbc hbr ⊢ exact D.includes v hv n b ho hbc hbr · -- a filled block: the recovering validator accepted nothing obtain ⟨k, hk1, hk2, rfl⟩ := sk.mem_freshIds.mp hf have hR0 : sk.r0 = (U.block sk.B1).round := rfl rw [sk.skipFill_block_fresh] at hbc hbr simp only [SkipData.fillBlock] at hbc hbr subst hbc rw [hdown n (by omega) (by omega)] exact Finset.empty_subset _ ``` **I15a — the delivery transformer.** The fill's delivery structure is the original's: the recovering validator's blocks were never delivered to anyone, being reconstructed after the fact. `hdown` is the hypothesis `Delivery.includes` forces. #### `CommonAt` *def, `Integration.CommonTarget.lean`* ```lean def CommonAt (U : BlockUniverse Validator BlockId Payload) (b : BlockId) (r : ℕ) : Prop := b ∈ U.ids ∧ (U.block b).round = r ∧ ∀ c ∈ U.ids, (U.block c).round = r + 2 → Reaches U c b ``` A block is **common at round `r`** when every block two rounds above it reaches it. ### Hybrid fault tolerance: Byzantine and crash faults apart #### `HybridFaults` *class, `Hybrid.Faults.lean`* ```lean class HybridFaults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where /-- The Byzantine bound. -/ fb : ℕ /-- The crash bound. -/ fc : ℕ /-- The Byzantine validators: may equivocate. -/ byzantine : Finset Validator /-- The crash-prone validators: honest, may halt. -/ crash : Finset Validator disjoint : Disjoint byzantine crash card_byzantine : byzantine.card ≤ fb card_crash : crash.card ≤ fc /-- The base bound the derived instance needs — deliberately not the hybrid committee bound `n ≥ 5fb + 3fc + 1`, which every safety theorem consumes through the admissible interval instead, so the one-short committee `n = 5fb + 3fc` stays expressible for the tightness counterexample (H10). -/ card_validators : 3 * (fb + fc) + 1 ≤ Fintype.card Validator ``` The hybrid fault model: at most `fb` Byzantine, at most `fc` crash-prone. The committee bound `n ≥ 5·fb + 3·fc + 1` enters through the admissible threshold interval, not here — see `card_validators`. #### `Honest` *def, `Hybrid.Faults.lean`* ```lean def Honest : Finset Validator := (H.byzantine)ᶜ ``` The honest validators: everyone outside the Byzantine set. A crash-prone validator is honest — its blocks are consistent; only its availability is in doubt. #### `HonestNoEquiv` *def, `Hybrid.Faults.lean`* ```lean def HonestNoEquiv (U : BlockUniverse Validator BlockId Payload) : Prop := U.NoEquivOn (Honest Validator) ``` **The strengthened equivocation clause.** Non-equivocation over `Honest` rather than the derived instance's `Correct` — P5's shape at the larger class, the base clause following from it — is the hybrid model's one genuinely new assumption. #### `q` *def, `Hybrid.Rules.lean`* ```lean def q : ℕ := Fintype.card Validator - (H.fb + H.fc) ``` The hybrid quorum `q = n − fb − fc` — the derived instance's `n − F.f`, spelled out. #### `kTight` *def, `Hybrid.Rules.lean`* ```lean def kTight : ℕ := 2 * H.fb + H.fc + 1 ``` The tight indirect threshold. #### `kRel` *def, `Hybrid.Rules.lean`* ```lean def kRel : ℕ := Fintype.card Validator - (3 * H.fb + 2 * H.fc) ``` The `n`-relative indirect threshold, mirroring the house generalization of `2f + 1` to `n − 3f`; equal to `kTight` at the tight committee. #### `ThickLink` *def, `Hybrid.Rules.lean`* ```lean def ThickLink (k : ℕ) (U : BlockUniverse Validator BlockId Payload) (A L : BlockId) (r : ℕ) : Prop := coneLink k U A L r ``` **The indirect test** at threshold `k`: at least `k` distinct authors of support blocks in the anchor's cone. #### `DirectCommitIn` *abbrev, `Hybrid.Decision.lean`* ```lean abbrev DirectCommitIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := supportCommit (q Validator) U V L r ``` Direct commit, as judged from a single view: the view holds votes for `L` at the round above it from a hybrid quorum of validators. #### `DirectSkipIn` *abbrev, `Hybrid.Decision.lean`* ```lean abbrev DirectSkipIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V (q Validator) (omissionsOf U L (r + 1)) ``` Direct skip, as judged from a single view: the view holds blocks at the round above `L` that omit it, from a hybrid quorum of validators. #### `DirectSkipSlotIn` *abbrev, `Hybrid.Decision.lean`* ```lean abbrev DirectSkipSlotIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (s : ℕ) : Prop := blameSkip (q Validator) U V s ``` **The slot is directly skipped, as judged from a view**: a hybrid quorum of voting-round blocks, in view, reference no candidate of the slot. Strictly stronger than the per-candidate `DirectSkipIn`, needed for the same reason the core and Odontoceti were repaired (`docs/target-properties.md` §3.2): a skip over the candidates that happen to exist is not `Banded`. #### `Decided` *abbrev, `Hybrid.Decision.lean`* ```lean abbrev Decided (k : ℕ) (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) : ℕ → Option BlockId → Prop := (hybridAnchored Validator BlockId Payload k).Decided (S := S) U V ``` **The decision relation** at threshold `k`: the anchored relation at Hybrid's data. #### `_root_.LeanDag.Faults5.toHybrid` *def, `Hybrid.Conservativity.lean`* ```lean def _root_.LeanDag.Faults5.toHybrid [F : Faults5 Validator] : HybridFaults Validator where fb := F.f fc := 0 byzantine := F.byzantine crash := ∅ disjoint := Finset.disjoint_empty_right _ card_byzantine := F.card_byzantine card_crash := le_refl 0 card_validators := by have := F.card_validators5; omega ``` **Every pure-Byzantine committee is a crash-free hybrid committee.** The generalization direction of H8. ### Adaptive leaders: the schedule as a fixpoint #### `slotsOfKeyed` *def, `Adaptive.Basic.lean`* ```lean @[reducible] def slotsOfKeyed (a : ℕ → Validator) (hk : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂) : Slots Validator where slotRound := S.slotRound leader := a mono := S.mono unbounded := S.unbounded keyed := fun _ _ h => hk _ _ (congrArg Prod.fst h) (congrArg Prod.snd h) ``` **The induced instance, at any base schedule.** `Slots.keyed` asks that distinct slots differ in round or in leader; under one leader per round the rounds already separate them, which is `slotsOf` below. What the law actually needs is weaker and survives multiple leaders: distinct slots of *one* round get distinct validators. #### `slotsOf` *def, `Adaptive.Basic.lean`* ```lean @[reducible] def slotsOf (hinj : Function.Injective S.slotRound) (a : ℕ → Validator) : Slots Validator where slotRound := S.slotRound leader := a mono := S.mono unbounded := S.unbounded keyed := fun _ _ h => hinj (congrArg Prod.fst h) ``` The `Slots` instance a leader assignment induces: the base round structure, the given leaders. `keyed` is where one-leader-per-round enters: with `slotRound` injective, distinct slots differ in round whatever the assignment names. #### `Policy` *structure, `Adaptive.Policy.lean`* ```lean structure Policy (R : DagRule Validator BlockId Payload) [S : Slots Validator] where /-- The epoch length, in slots. -/ W : ℕ W_pos : 0 < W /-- The reassignment rule: from the universe, the validator's view of it and a verdict function, the leader of each slot. -/ pick : (U : R.Universe) → R.View U → (ℕ → Option BlockId) → ℕ → Validator /-- **The schedule law the reassignment owes.** `Slots.keyed` asks that distinct slots differ in round or in leader. Under one leader per round the rounds separate them whatever the policy does; under multiple leaders a reassignment could collide two slots of one round onto one validator, so the clause is genuinely owed — the obligation `adaptive-leaders.md` §2 recorded and deferred. -/ keyed : ∀ (U : R.Universe) (V : R.View U) (v : ℕ → Option BlockId) (k₁ k₂ : ℕ), S.slotRound k₁ = S.slotRound k₂ → pick U V v k₁ = pick U V v k₂ → k₁ = k₂ /-- **Adaptedness.** The leader of slot `k` reads the verdicts of epochs `≤ epochOf k − 2` and nothing else — not the view either. -/ adapted : ∀ (U : R.Universe) (V₁ V₂ : R.View U) v w k, (∀ j, epochOf W j + 2 ≤ epochOf W k → v j = w j) → pick U V₁ v k = pick U V₂ w k /-- Epochs `0` and `1` run the base schedule. -/ base_prefix : ∀ (U : R.Universe) (V : R.View U) v k, epochOf W k < 2 → pick U V v k = S.leader k ``` A Hammerhead-style reassignment policy over a carrier: epoch length, the rule, and the clauses it owes. Fairness is deliberately not here: safety must hold for arbitrary, even adversarial, adapted policies. #### `PartialRun` *structure, `Adaptive.Run.lean`* ```lean structure PartialRun (P : Policy R) (U : R.Universe) (V : R.View U) (E : ℕ) where /-- The leader assignment. -/ assign : ℕ → Validator /-- The verdicts. -/ vdct : ℕ → Option BlockId /-- **The assignment is a lawful schedule.** Under one leader per round the rounds separate slots whatever the assignment names, and this is free; under multiple leaders a run must exhibit an assignment that does not collide two slots of one round onto one validator. -/ keyed : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → assign k₁ = assign k₂ → k₁ = k₂ /-- Every slot of a closed epoch is decided inside its window: anchors strictly below the start of epoch `e + 2`. -/ closed : ∀ k, epochOf P.W k < E → DecidedBelow R (slotsOfKeyed assign keyed) (P.W * (epochOf P.W k + 2)) V k (vdct k) /-- The assignment is the policy's, computed on this view, as far as the derivations read it. -/ coherent : ∀ m, epochOf P.W m < E + 1 → assign m = P.pick U V vdct m ``` A run closed up to epoch height `E`: verdicts derived for every slot of epochs `< E`, the schedule coherent as far as those derivations read it (epochs `< E + 1`). What a validator holds mid-execution. #### `Run` *structure, `Adaptive.Run.lean`* ```lean structure Run (P : Policy R) (U : R.Universe) (V : R.View U) where /-- The leader assignment. -/ assign : ℕ → Validator /-- The verdicts. -/ vdct : ℕ → Option BlockId /-- **The assignment is a lawful schedule.** Under one leader per round the rounds separate slots whatever the assignment names, and this is free; under multiple leaders a run must exhibit an assignment that does not collide two slots of one round onto one validator. -/ keyed : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → assign k₁ = assign k₂ → k₁ = k₂ /-- Every slot is decided inside its epoch window. -/ closed : ∀ k, DecidedBelow R (slotsOfKeyed assign keyed) (P.W * (epochOf P.W k + 2)) V k (vdct k) /-- The assignment is the policy's, computed on this view, everywhere. -/ coherent : ∀ m, assign m = P.pick U V vdct m ``` A total run: the adaptive fixpoint itself. #### `PlacesRuns` *def, `Adaptive.Liveness.lean`* ```lean def PlacesRuns {R : DagRule Validator BlockId Payload} (P : Policy R) (T : Finset Validator) (c : ℕ) : Prop := ∀ (U : R.Universe) (V : R.View U) (v : ℕ → Option BlockId) (e : ℕ), ∃ b, P.W * (e + 1) ≤ b ∧ b + c ≤ P.W * (e + 2) ∧ ∀ i, i < c → P.pick U V v (b + i) ∈ T ``` **The adaptive fairness clause.** Every assignment the policy can emit places, in each epoch past the base prefix, a run of `c` consecutive `T`-led slots. The clause liveness prices and safety never sees: `run_agree` holds for policies that violate it. ### Nemo-Nemo: crash-fault consensus in two rounds #### `majority` *def, `Nemo.Basic.lean`* ```lean def majority (Validator : Type*) [Fintype Validator] : ℕ := Fintype.card Validator / 2 + 1 ``` The majority quorum: strictly more than half the validators, `n/2 + 1`. #### `ValidWrt` *structure, `Nemo.Basic.lean`* ```lean structure ValidWrt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every reference sits in the immediately preceding round. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- Non-genesis blocks reference a **majority** of distinct validators. -/ quorum : 0 < b.round → majority Validator ≤ (creators blk b).card ``` Crash block validity: like the core `ValidWrt`, but the parents quorum is the majority `n/2+1` rather than `n − f`, and `self_parent` and `distinct_creators` are gone — the implementation's verifier imposes neither, and under crash the second is derivable from universal non-equivocation anyway. #### `View` *abbrev, `Nemo.Basic.lean`* ```lean abbrev View (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] (U : Universe Validator BlockId Payload) := BlockRecord.View U ``` A view: one validator's local, reference-closed sub-DAG. #### `CertifiedIn` *def, `Nemo.Rules.lean`* ```lean def CertifiedIn (U : Universe Validator BlockId Payload) (A L : BlockId) (r : ℕ) : Prop := ∃ p ∈ history U A, (U.block p).round = r + 1 ∧ L ∈ (U.block p).refs ``` **The indirect test** (link size one): a round-`(r+1)` vote for `L` lies in the anchor's cone. The name keeps the core's `CertifiedIn` — under crash the vote block *is* the certificate. Stated over the `history` `Finset` rather than `Reaches`, so it is decidable. #### `DirectCommitIn` *abbrev, `Nemo.Decision.lean`* ```lean abbrev DirectCommitIn (U : Universe Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := supportCommit (majority Validator) U V L r ``` Direct commit, as judged from a single view: the view holds votes for `L` at the round above it from a majority of validators. #### `Decided` *abbrev, `Nemo.Decision.lean`* ```lean abbrev Decided (U : Universe Validator BlockId Payload) (V : View Validator BlockId Payload U) : ℕ → Option BlockId → Prop := (nemoAnchored Validator BlockId Payload).Decided (S := S) U V ``` **The decision relation**: the anchored relation at Nemo's data. #### `CrashFaults` *class, `Nemo.Liveness.lean`* ```lean class CrashFaults (Validator : Type*) [Fintype Validator] [DecidableEq Validator] where /-- The fault bound. -/ f : ℕ /-- The crashed validators. Everything else is live. -/ crashed : Finset Validator /-- At most `f` validators crash. -/ card_crashed : crashed.card ≤ f /-- There are at least `2f+1` validators. -/ card_validators : 2 * f + 1 ≤ Fintype.card Validator ``` The crash fault model: `n ≥ 2f+1` validators, at most `f` of them crashed. A crashed validator halts — its blocks, while they lasted, are consistent (`no_equivocation` is universal); only its availability is in doubt. Safety never consults this class; it exists for liveness alone. #### `Live` *def, `Nemo.Liveness.lean`* ```lean def Live : Finset Validator := (C.crashed)ᶜ ``` The live validators: everyone outside the crashed set. #### `Populated` *abbrev, `Nemo.Liveness.lean`* ```lean abbrev Populated (U : Universe Validator BlockId Payload) (r : ℕ) : Prop := PopulatedOn U (Live Validator) r ``` The all-of-`Live` participation case. #### `Synchronised` *abbrev, `Nemo.Liveness.lean`* ```lean abbrev Synchronised (U : Universe Validator BlockId Payload) (R : ℕ) : Prop := SynchronisedOn U (Live Validator) R ``` The all-of-`Live` coverage case. ### Mahi-Mahi: the asynchronous rule at wave w #### `DirectCommitIn` *abbrev, `MahiMahi.Model.Decision.lean`* ```lean abbrev DirectCommitIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (w : ℕ) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V (quorumCard Validator) (certificates U w L r) ``` Direct commit, as judged from a single view: the view holds certificates for `L` from a quorum of distinct validators. #### `DirectSkipIn` *abbrev, `MahiMahi.Model.Decision.lean`* ```lean abbrev DirectSkipIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (w : ℕ) (a : Validator) (r : ℕ) : Prop := HoldsAtLeast U V (quorumCard Validator) ((blocksAt U (votingRound w r)).filter (fun q => Blames U q a r)) ``` Direct skip, as judged from a single view: the view holds voting-round blocks blaming the slot `(a, r)` from a quorum of distinct validators. #### `CertifiedIn` *abbrev, `MahiMahi.Model.Decision.lean`* ```lean abbrev CertifiedIn (U : BlockUniverse Validator BlockId Payload) (w : ℕ) (A L : BlockId) (r : ℕ) : Prop := LinkedVia U A (certificates U w L r) ``` **The indirect test**: a certificate for `L` lies in the causal history of the anchor `A`. The core's `CertifiedIn` at wave `w`. Not decidable as stated — `Reaches` is a `Prop` — and not made so: the witnesses exhibit the certificate. #### `Decided` *abbrev, `MahiMahi.Model.Decision.lean`* ```lean abbrev Decided (w : ℕ) (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) : ℕ → Option BlockId → Prop := (mahiMahiAnchored Validator BlockId Payload w).Decided (S := S) U V ``` **The decision relation at wave `w`**: the anchored relation at Mahi-Mahi's data. `Decided w U V k (some L)`: a validator holding `V` may commit `L` at `k`; `Decided w U V k none`: it may skip the slot; *undecided* is the absence of any derivation. #### `goodAt` *def, `MahiMahi.Model.Good.lean`* ```lean def goodAt (U : BlockUniverse Validator BlockId Payload) (w r : ℕ) : Finset Validator := Finset.univ.filter (fun v => ∃ L ∈ U.ids, (U.block L).round = r ∧ (U.block L).creator = v ∧ DirectCommit U w L r) ``` The validators whose round-`r` block is directly committed at wave `w`. Round-indexed and slot-free, so that the counting theorems mention no schedule; decidable on a concrete universe, as a bounded search over `U.ids` of decidable conjuncts. #### `good` *def, `MahiMahi.Model.Good.lean`* ```lean def good (U : BlockUniverse Validator BlockId Payload) [S : Slots Validator] (w k : ℕ) : Finset Validator := goodAt U w (S.slotRound k) ``` The slot-`k` candidates the DAG directly commits: `goodAt` at the slot's round. The schedule enters only through `slotRound`. #### `UnpredictableRunWithin` *def, `MahiMahi.Model.Unpredictable.lean`* ```lean def UnpredictableRunWithin (U : BlockUniverse Validator BlockId Payload) (w c d N : ℕ) : Prop := ∀ k, -- the latest run's last decision round lies below the horizon (mahiMahiAnchored Validator BlockId Payload w).decisionRound (k + c + d - 1) ≤ N → -- some run of d slots starting in the window is led by committed candidates ∃ k', k ≤ k' ∧ k' < k + c ∧ ∀ i < d, S.leader (k' + i) ∈ good U w (k' + i) ``` **The run form.** In every window of `c` slots below the horizon, a run of `d` consecutive slots whose leaders are all committed candidates. The bound reads the last slot of the latest possible run, `k + c + d − 1`, so that small universes are not vacuously covered. ### Black Marlin: the three-round commit rule #### `SupportedIn` *def, `BlackMarlin.Model.Decision.lean`* ```lean def SupportedIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := quorumCard Validator ≤ (supportersIn U V L (r + 1)).card ``` `supp(L) ≥ n − f`, counted in a view: the record's `supportersIn`. #### `LinkedIn` *def, `BlackMarlin.Model.Decision.lean`* ```lean def LinkedIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := (linkersIn U V L r).Nonempty ``` `L` is linked, as judged from a view. #### `CommittedIn` *def, `BlackMarlin.Model.Decision.lean`* ```lean def CommittedIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := IsAnchor U r L ∧ SupportedIn U V L r ∧ LinkedIn U V L r ``` **The commit rule, as a validator applies it.** `IsAnchor` is not relativised: which validator anchors a round is a schedule fact rather than an observation, and that the block exists at all is implied by the view holding a block that references it. #### `Flush` *structure, `BlackMarlin.Model.Ledger.lean`* ```lean structure Flush (U : BlockUniverse Validator BlockId Payload) where /-- The anchor flushed at each round, where the descent flushed one. -/ block : ℕ → Option BlockId /-- What is flushed at a round is that round's anchor. -/ isAnchor : ∀ ρ L, block ρ = some L → IsAnchor U ρ L /-- **The descent steps by one round.** -/ step : ∀ ρ L M, block ρ = some L → block (ρ + 1) = some M → L ∈ (U.block M).refs /-- **And does not pass over an anchor it references.** -/ dense : ∀ ρ M, block (ρ + 1) = some M → (coneAnchors U M ρ).Nonempty → (block ρ).isSome ``` **A flush record**: the anchor block a validator flushed at each round. `step` is the descent's own shape — the anchor at `ρ` is a reference of the anchor at `ρ + 1` — and `dense` says it does not pass over a round its reference set contains; neither says anything about a round the descent skips. #### `maxAnchor` *def, `BlackMarlin.Model.Descent.lean`* ```lean def maxAnchor (U : BlockUniverse Validator BlockId Payload) (s : Finset BlockId) : Finset BlockId := (anchorsOf U s).filter (fun X => (U.block X).round = maxAnchorRound U s) ``` **`maxAnchor(s)`**: the anchors of `s` at the highest round they reach. A set, as L21 reads it. #### `pick` *def, `BlackMarlin.Model.Descent.lean`* ```lean def pick (s : Finset BlockId) : Option BlockId := if h : s.Nonempty then some (s.min' h) else none ``` The `≤`-least member of a set, as an `Option`. #### `flushRecord` *def, `BlackMarlin.Model.Descent.lean`* ```lean def flushRecord (U : BlockUniverse Validator BlockId Payload) (B : BlockId) (ρ : ℕ) : Option BlockId := descentUpto U ((U.block B).round) B ρ ``` **The flush record of `commit(B)`**: the anchor its descent visits at each round. A step drops the round strictly, so `round B` steps are enough and the fuel is not a parameter of the result. #### `TopoSort` *structure, `BlackMarlin.Model.Order.lean`* ```lean structure TopoSort (U : BlockUniverse Validator BlockId Payload) where /-- The sorted list. -/ sort : Finset BlockId → List BlockId /-- It holds exactly the set. -/ mem : ∀ s b, b ∈ sort s ↔ b ∈ s /-- Without repetition. -/ nodup : ∀ s, (sort s).Nodup /-- And in causal order: what a block reaches comes no later than it. -/ topo : ∀ s a b, a ∈ s → b ∈ s → Reaches U b a → (sort s).idxOf a ≤ (sort s).idxOf b ``` **`τ`**: a deterministic topological sort of a set of blocks. A function of the set alone, so two validators sorting the same set produce the same list; and causally ordered, so a block never precedes what it reaches. #### `ledgerSeq` *def, `BlackMarlin.Model.Order.lean`* ```lean def ledgerSeq (U : BlockUniverse Validator BlockId Payload) (f : Flush U) (τ : TopoSort U) (n : ℕ) : List BlockId := (List.range n).flatMap (segment U f τ) ``` The blocks a record flushes through round `n`, in order. #### `deliverSeq` *def, `BlackMarlin.Model.Order.lean`* ```lean def deliverSeq (U : BlockUniverse Validator BlockId Payload) (f : Flush U) (τ : TopoSort U) (n : ℕ) : List BlockId := (filterFirstFrom U (ledgerSeq U f τ n) ∅).1 ``` **What a validator outputs**: the flushed sequence with L27 applied. Definition 1 speaks about this list. #### `TopoSort.ofFinOrder` *def, `BlackMarlin.Helpers.Order.lean`* ```lean def TopoSort.ofFinOrder {n : ℕ} (W : BlockUniverse Validator (Fin n) Payload) (h : ∀ b : Fin n, ∀ j ∈ (W.block b).refs, j < b) : TopoSort W where sort s := (List.finRange n).filter (fun x => decide (x ∈ s)) mem s b := by simp nodup s := (List.nodup_finRange n).filter _ topo s a b ha hb hr := by refine idxOf_le_of_pairwise ((List.pairwise_le_finRange n).filter _) ((List.nodup_finRange n).filter _) (by simp [ha]) (by simp [hb]) ?_ clear ha hb induction hr with | refl => exact le_refl _ | tail _ hstep ihr => exact le_trans (le_of_lt (h _ _ hstep)) ihr ``` **The same sort, in a form the kernel evaluates.** `Finset.sort` rests on a merge sort the kernel does not reduce, so a witness filters `List.finRange` instead: the same order, structurally computed, which is what lets a concrete model settle the delivered sequence by `decide`. ### FinWhale: the two-round commit rule #### `Params` *class, `FinWhale.Model.Params.lean`* ```lean class Params (Validator : Type*) [Fintype Validator] [DecidableEq Validator] [F : Faults Validator] where /-- How many round-`(r+1)` votes the fast path can do without. -/ p : ℕ /-- The fast path tolerates at least one missing vote: a threshold of `n` votes, requiring every validator, is not admitted. -/ p_pos : 1 ≤ p /-- The fast path does not tolerate more missing votes than there may be faults. -/ p_le_f : p ≤ F.f /-- `n = 3f + 2p − 1`, written additively. -/ card_add_one : Fintype.card Validator + 1 = 3 * F.f + 2 * p ``` **FinWhale's committee**: `n = 3f + 2p − 1`, with `1 ≤ p ≤ f`. #### `spQuorum` *def, `FinWhale.Model.Params.lean`* ```lean def spQuorum (Validator : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] [Params Validator] : ℕ := 2 * Faults.f Validator + Params.p Validator ``` The slow-path quorum, the paper's `⌈(n+f+1)/2⌉`. #### `fastCard` *def, `FinWhale.Model.Params.lean`* ```lean def fastCard (Validator : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] [Params Validator] : ℕ := Fintype.card Validator - Params.p Validator ``` The fast-path threshold: `n − p` distinct voters one round above. #### `ValidHere` *structure, `FinWhale.Model.Rule.lean`* ```lean structure ValidHere (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every edge points to the round immediately below. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- No two edges share a validator. -/ distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j /-- A non-genesis block carries `n − f` edges by distinct validators. -/ quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card /-- **FinWhale's clause**: `Clause.leaderExcluded` of the common layer, a genuine but harmless strengthening of the paper's rule. -/ leader_clause : Clause.leaderExcluded blk b ``` **Validity, as FinWhale extends Mysticeti's**: the core's three clauses, plus leader exclusion, which the fast path's counting rests on. #### `Dag` *abbrev, `FinWhale.Model.Rule.lean`* ```lean abbrev Dag (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] := BlockRecord Validator BlockId Payload ValidHere (Correct : Finset Validator) ``` **A DAG the communication component can build**: the block record at FinWhale's validity, with non-equivocation asked of the correct validators. Equivocating blocks are admitted, of faulty validators only. #### `parentSet` *def, `FinWhale.Model.Rule.lean`* ```lean def parentSet (D : Dag Validator BlockId Payload) (b : BlockId) : Finset Validator := creatorsOf D.block ((D.block b).refs) ``` The parents of `b`, as validators. #### `parentsVoting` *def, `FinWhale.Model.Rule.lean`* ```lean def parentsVoting (D : Dag Validator BlockId Payload) (b l : BlockId) : Finset Validator := creatorsOf D.block (((D.block b).refs).filter (fun q => l ∈ (D.block q).refs)) ``` The parents of `b` that vote for the leader block `l`. #### `SPCertificate` *def, `FinWhale.Model.Rule.lean`* ```lean def SPCertificate (D : Dag Validator BlockId Payload) (b l : BlockId) : Prop := spQuorum Validator ≤ (parentsVoting D b l).card ``` **An SP-certificate**, Mysticeti's, at this committee's quorum. #### `NonFPEvidence` *def, `FinWhale.Model.Skip.lean`* ```lean def NonFPEvidence (D : Dag Validator BlockId Payload) (b : BlockId) (slot : Finset BlockId) : Prop := ∀ l ∈ slot, ¬ FPEvidence D b l ``` **Non-FP-evidence**: a round-`(r+2)` block that is FP-evidence for no block of the slot. #### `SPSkip` *def, `FinWhale.Model.Skip.lean`* ```lean def SPSkip (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := spQuorum Validator ≤ (nonVoters D l).card ``` **The SP-skip half of the direct skip rule**, at one block of the slot: a quorum of round-`(r+1)` validators decline to vote for it. #### `slotBlocks` *abbrev, `FinWhale.Model.Decision.lean`* ```lean abbrev slotBlocks (S : Slots Validator) (D : Dag Validator BlockId Payload) (k : ℕ) : Finset BlockId := leaderBlocksAt (S := S) D k ``` The blocks of the leader slot of round `r`. There may be several, if the leader equivocates. #### `IndirectCommit` *def, `FinWhale.Model.Anchor.lean`* ```lean def IndirectCommit (S : Slots Validator) (D : Dag Validator BlockId Payload) (A : BlockId) (k : ℕ) (b : BlockId) : Prop := b ∈ slotBlocks S D k ∧ ((∃ c ∈ blocksAt D (S.slotRound k + 2), ReachesFrom D.block A c ∧ SPCertificate D c b) ∨ (∃ ev : Finset Validator, spQuorum Validator ≤ ev.card ∧ ∀ v ∈ ev, ∃ c ∈ blocksAt D (S.slotRound k + 2), ReachesFrom D.block A c ∧ (D.block c).creator = v ∧ FPEvidence D c b)) ``` **The indirect commit condition**, as a predicate of the anchor `A`, the slot's round `r`, and the candidate block `b`. No view occurs in it: the anchor's causal history is a function of the anchor. #### `Creation` *structure, `FinWhale.Model.Creation.lean`* ```lean structure Creation (U : BlockUniverse Validator BlockId Payload) (T : Finset Validator) (N : ℕ) (lead : ℕ → Validator) extends PaceCore U T N where /-- Time advances with rounds. -/ built_lt : ∀ v ∈ T, ∀ n < top v, built v n < built v (n + 1) /-- Which condition created each block. -/ trigger : Validator → ℕ → Trigger /-- **The network does not deliver before it sends.** -/ holds_built : ∀ v ∈ T, ∀ t, ∀ b ∈ holds v t, (U.block b).creator ∈ T → built ((U.block b).creator) ((U.block b).round) ≤ t /-- **No two reliable validators build one round at one instant.** -/ builds_distinct : ∀ u ∈ T, ∀ v ∈ T, ∀ n, u ≠ v → built u n ≠ built v n /-- **C1's L1**: the round-`r` leader's block is in hand. -/ c1_leader : ∀ v ∈ T, ∀ n, n + 1 ≤ N → trigger v (n + 1) = Trigger.leaderAndQuorum → ∀ L ∈ U.ids, (U.block L).round = n → (U.block L).creator = lead n → L ∈ holds v (built v (n + 1)) /-- **C1's L2**: either a quorum of held voters for the round-`r` leader, or a quorum of held blocks declining to vote for it. -/ c1_votes : ∀ v ∈ T, ∀ n, n + 2 ≤ N → trigger v (n + 2) = Trigger.leaderAndQuorum → ∀ L ∈ U.ids, (U.block L).round = n → (U.block L).creator = lead n → (∃ S : Finset Validator, spQuorum Validator ≤ S.card ∧ ∀ u ∈ S, ∃ b ∈ U.ids, b ∈ holds v (built v (n + 2)) ∧ (U.block b).creator = u ∧ (U.block b).round = n + 1 ∧ L ∈ (U.block b).refs) ∨ (∃ S : Finset Validator, spQuorum Validator ≤ S.card ∧ ∀ u ∈ S, ∃ b ∈ U.ids, b ∈ holds v (built v (n + 2)) ∧ (U.block b).creator = u ∧ (U.block b).round = n + 1 ∧ L ∉ (U.block b).refs) /-- **C2**: the full timeout was waited out. -/ c2_wait : ∀ v ∈ T, ∀ n, trigger v (n + 1) = Trigger.timeout → built v n + timeout n ≤ built v (n + 1) /-- **C3**: `n − f` blocks of the round being built are in hand. -/ c3_quorum : ∀ v ∈ T, ∀ n, n + 1 ≤ N → trigger v (n + 1) = Trigger.roundQuorum → ∃ S : Finset Validator, quorumCard Validator ≤ S.card ∧ ∀ u ∈ S, ∃ b ∈ U.ids, b ∈ holds v (built v (n + 1)) ∧ (U.block b).creator = u ∧ (U.block b).round = n + 1 /-- **Parent selection takes the leader's block** when it is held and the leader is reliable. The reliability guard is not decoration: an equivocating leader would force a validator holding two of its blocks to reference both, which `distinct_creators` forbids. -/ selects_leader : ∀ v ∈ T, ∀ n, lead n ∈ T → ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 1 → ∀ L ∈ U.ids, (U.block L).round = n → (U.block L).creator = lead n → L ∈ holds v (built v (n + 1)) → L ∈ (U.block c).refs /-- **And it takes the votes** it holds: where the builder holds a round-`(n+1)` block voting for the reliable leader's block, its own block has a parent by that same validator, voting for it too. Stated as "has a parent by that author," not "the held block is a parent," since a Byzantine validator's two voting blocks would force two references otherwise — what the certificate counts is authors. -/ selects_votes : ∀ v ∈ T, ∀ n, lead n ∈ T → ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 2 → ∀ L ∈ U.ids, (U.block L).round = n → (U.block L).creator = lead n → ∀ b ∈ U.ids, (U.block b).round = n + 1 → b ∈ holds v (built v (n + 2)) → L ∈ (U.block b).refs → ∃ q ∈ (U.block c).refs, (U.block q).creator = (U.block b).creator ∧ L ∈ (U.block q).refs ``` **The block-creation rule, over the pacing trunk.** `lead` is the leader schedule, one leader per round. #### `CommitsCorrectLeaders` *def, `FinWhale.Model.Liveness.lean`* ```lean def CommitsCorrectLeaders (S : Slots Validator) (D : Dag Validator BlockId Payload) (R N : ℕ) : Prop := ∀ s, R ≤ S.slotRound s → S.slotRound s + 2 ≤ N → S.leader s ∈ (Correct : Finset Validator) → ∃ l ∈ slotBlocks S D s, SPCommitBy D l (Correct : Finset Validator) ``` **The liveness input, as an interface.** Every correct-led slot past the coverage round and below the horizon carries a direct commit. Two routes supply it — `commits_of_reactive`, from the reactive schedule's wait clauses, and `commits_of_creation`, from the block-creation conditions themselves — and nothing below cares which. #### `settled` *def, `FinWhale.Model.Liveness.lean`* ```lean def settled (pc : PaceCore U T M) : ℕ := (Finset.range (M + 1)).sup (fun n => pc.latest n + pc.delay) ``` The instant by which every reliable block of every round up to `M` has arrived: the latest build of any of those rounds, plus one delay. #### `holdsView` *def, `FinWhale.Holdings.lean`* ```lean def holdsView (pc : PaceCore U T M) (hids : D.ids = U.ids) (hblk : D.block = U.block) {v : Validator} (hv : v ∈ T) (t : ℕ) : D.View := ⟨pc.holds v t, by rw [hids]; exact pc.holds_sub v t, by rw [hblk, hids] at *; exact pc.holds_closed v hv t⟩ ``` **A validator's holdings are a view.** `holds_sub` is the subset clause and `holds_closed` the closure clause. #### `Dag.ofDoSValid` *def, `FinWhale.DoSBridge.lean`* ```lean def Dag.ofDoSValid (U : BlockUniverse Validator BlockId Payload) (leader : ℕ → Validator) (hdos : DoSValid U) : Dag Validator BlockId Payload where ids := U.ids block := U.block complete := U.complete valid := fun i hi => { predecessor := (U.valid i hi).predecessor distinct_creators := (U.valid i hi).distinct_creators quorum := (U.valid i hi).quorum leader_clause := leaderClause_of_dosValid hdos hi } no_equivocation := U.no_equivocation ``` **The construction.** A DoS-valid universe, read as a FinWhale DAG at a given leader schedule. Three validity clauses are the core's, the fourth is the theorem above, and non-equivocation is the universe's. ### Minnow: the minimal commit rule #### `ValidHere` *structure, `Minnow.Model.Rule.lean`* ```lean structure ValidHere (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every edge points to the round immediately below. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- No two edges share a process. -/ distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j /-- A non-genesis vertex carries `2f + 1` edges by distinct processes. -/ quorum : 0 < b.round → quorumCard Validator ≤ (creators blk b).card ``` **Validity, as Minnow's communication component defines it**: every edge sits in the round below, no two edges share a process, and a non-genesis vertex carries `2f + 1` of them. No self-parent. #### `Dag` *structure, `Minnow.Model.Rule.lean`* ```lean structure Dag (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] where /-- Which vertices exist. -/ ids : Finset BlockId /-- What each id denotes. -/ block : BlockId → Block Validator BlockId Payload /-- The DAG is closed under edges. -/ complete : ∀ i ∈ ids, ∀ j ∈ (block i).refs, j ∈ ids /-- Every vertex is valid. -/ valid : ∀ i ∈ ids, ValidHere block (block i) /-- **Only a faulty process issues two vertices in one round.** Section 2 of the paper admits equivocating vertices into the DAG, but a correct process follows its algorithm, which issues one vertex a round. -/ correct_single : ∀ i ∈ ids, ∀ j ∈ ids, (block i).creator ∈ (Correct : Finset Validator) → (block i).creator = (block j).creator → (block i).round = (block j).round → i = j ``` A DAG the communication component can build. Equivocating vertices are admitted: a faulty process may issue two, and both are held. #### `Reaches` *def, `Minnow.Model.Rule.lean`* ```lean def Reaches (D : Dag Validator BlockId Payload) (v u : BlockId) : Prop := u ∈ cone D v ``` `u ⇝ v`: `u` lies in the causal past of `v`. #### `Concurrent` *def, `Minnow.Model.Rule.lean`* ```lean def Concurrent (D : Dag Validator BlockId Payload) (a b : BlockId) : Prop := ¬ Reaches D a b ∧ ¬ Reaches D b a ``` Neither vertex is in the other's causal past. #### `slotBlocks` *def, `Minnow.Model.Rule.lean`* ```lean def slotBlocks (D : Dag Validator BlockId Payload) (s : Slot Validator) : Finset BlockId := D.ids.filter (fun b => (D.block b).creator = s.1 ∧ (D.block b).round = s.2) ``` The vertices occupying a slot. #### `Skipped` *def, `Minnow.Model.Rule.lean`* ```lean def Skipped (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := quorumCard Validator ≤ ((creatorsOf D.block (verticesAt D ((D.block l).round + 1))) \ pointers D l ((D.block l).round + 1)).card ``` **Skip**, the escape in the second clause: `2f + 1` of the round above carry no edge to `l`, counted by **distinct process**, none of whose round-above vertices points at `l`. Definition 9 writes "there are `2f + 1` vertices", but the quorum clause two lines above counts "vertices issued by distinct processes", and every other quorum in the paper is over processes. `report.md` §19.2 is why the vertex reading cannot be meant: under it a vertex may be committed and skipped at once. #### `SkippedByVertex` *def, `Minnow.Model.Rule.lean`* ```lean def SkippedByVertex (D : Dag Validator BlockId Payload) (l : BlockId) : Prop := quorumCard Validator ≤ ((verticesAt D ((D.block l).round + 1)).filter (fun q => l ∉ (D.block q).refs)).card ``` **Skip, counted by vertex** — Definition 9 at the letter. Kept only to state what that reading costs; `Skipped` is what the rule is taken to mean. #### `CommittedAt` *def, `Minnow.Model.Rule.lean`* ```lean def CommittedAt (D : Dag Validator BlockId Payload) (L : ℕ → Slot Validator) : ℕ → BlockId → Prop | 0, l => Quorum D l | (k + 1), l => Quorum D l ∧ ∀ j ≤ k, ∃ v ∈ slotBlocks D (L j), Reaches D l v ∨ (Concurrent D l v ∧ (CommittedAt D L j v ∨ Skipped D v)) ``` **`crs*`** (Definition 9), by recursion on the position in `leaders`. `CommittedAt D L k l` is the pattern `Ps*` enabled for the vertex `l` occupying the `k`-th leader slot: a quorum points to it, and every earlier slot is *resolved* — some vertex of that slot lies in `l`'s causal past, or is concurrent with `l` and either committed or skipped. The existential over `v` is the paper's: "there is a vertex `v′` in slot `s′` in `D` such that …". One vertex of a slot resolving it is what makes `minnow.md` §3 possible. ### Barnacle: the adaptive leader count #### `BaseRule` *structure, `Barnacle.Model.Rule.lean`* ```lean structure BaseRule (Validator : Type) [Fintype Validator] [DecidableEq Validator] (BlockId : Type) [DecidableEq BlockId] (Payload : Type) extends Properties.DagRule Validator BlockId Payload where /-- The full view: every block of the universe. -/ full : ∀ U : Universe, View U /-- The causal history of a block of the universe, as a view. -/ historyView : ∀ (U : Universe) (A : BlockId), A ∈ ids U → View U /-- **A3.** The length of a wave: the rounds the direct rule reads from a slot's proposal. Three for the three-round rules, two for the two-round ones. -/ waveLength : ℕ /-- **A3.** The direct commit predicate, as judged from a view: block `L` proposed at round `r` is directly committed. -/ DirectCommitIn : ∀ {U : Universe}, View U → BlockId → ℕ → Prop /-- The direct predicate is decidable, so a validator — and a witness — can compute the window count. -/ decDirect : ∀ {U : Universe} (V : View U) (L : BlockId) (r : ℕ), Decidable (DirectCommitIn V L r) ``` **The base protocol, as the paper assumes it — the data.** A universe of blocks with its views, the direct decision predicate, and a decision relation parametric in the schedule. The laws these must satisfy are `BaseRule.Laws` below, a proposition each instantiation is proved to meet in its own `Statement`/`Proof` pair. `historyView` is the view a validator measures on — the anchor's causal history, which the paper's `GetSubDag` computes. An instantiation may build it however it likes: the law `historyView_ids` pins its ids to `historyFrom`, the shared history function of `Causality.lean`, so that the window is a function of the universe and the anchor alone. #### `IsLeaderBlock` *def, `Barnacle.Model.Rule.lean`* ```lean def IsLeaderBlock (R : BaseRule Validator BlockId Payload) (S : Slots Validator) (U : R.Universe) (k : ℕ) (L : BlockId) : Prop := L ∈ R.ids U ∧ (R.block U L).round = S.slotRound k ∧ (R.block U L).creator = S.leader k ``` `L` is a candidate block for slot `k` of schedule `S`: the right round, the right author. The same conjunction every rule of this development states, here over the interface's `block` and `ids` so that the arc has one candidate predicate for all four. #### `Laws` *structure, `Barnacle.Model.Rule.lean`* ```lean structure Laws (R : BaseRule Validator BlockId Payload) : Prop where /-- The full view holds exactly the universe. -/ full_ids : ∀ U, R.viewIds (R.full U) = R.ids U /-- The history view holds exactly the history. -/ historyView_ids : ∀ U A (hA : A ∈ R.ids U), R.viewIds (R.historyView U A hA) = historyFrom (R.block U) A /-- **A4, safety.** For a fixed schedule, verdicts agree across views. -/ agree : Properties.Agree R.toDagRule /-- A directly committed candidate of a slot is a commit verdict. -/ commitsDirect : Properties.CommitsDirect R.toDagRule (fun {U} V L r => R.DirectCommitIn V L r) /-- A committed block is a candidate of its slot. -/ candidates : Properties.CommitsCandidate R.toDagRule ``` **The laws of a base rule.** The two view laws pin the view fields; the other three are the properties of `docs/target-properties.md` at the rule's carrier: `agree` is the safety half of A4, and the two candidate properties tie the direct predicate to the relation, which makes the window count a count of verdicts. A2 is carried by `BaseRule` itself, as `viewSound` and `viewComplete`; the liveness half of A4 is `LiveRule.LiveOn`. #### `Anchored` *def, `Barnacle.Model.Rule.lean`* ```lean def Anchored (R : BaseRule Validator BlockId Payload) (upd : UpdateRule R) : Prop := ∀ (U : R.Universe) (V₁ V₂ : R.View U) (C : Config Validator) (b : ℕ) (A : BlockId), upd C b U V₁ A = upd C b U V₂ A ``` **A rule a validator can run without disagreeing.** The step depends on the configuration, the back-off and the anchor, and on the *view* only through what every view holding the anchor shares. The type above lets a rule read the view, which is what a validator actually has. Nothing then makes two validators agree, and nothing should: a rule reading its own view freely could return different configurations to two correct validators and break the configuration sequence outright. `Anchored` is the condition that rules that out, and it is not a restriction in practice — the window a rule measures on is the anchor's causal history, which BN2 shows every view holding the anchor holds whole and restricts identically. A rule computing from its own copy of that history satisfies this; the AIMD rule of `Model/Window.lean` does, by not reading the view at all. #### `Params` *structure, `Barnacle.Model.Window.lean`* ```lean structure Params where /-- The upper bound on the leader count. -/ maxLeaders : ℕ /-- The upper bound on a configuration's interval. -/ maxInterval : ℕ /-- The threshold's numerator. -/ num : ℕ /-- The threshold's denominator. -/ den : ℕ max_pos : 0 < maxLeaders ``` The mechanism's parameters: the caps on a configuration's widths and interval, and the health threshold `num / den`. The interval itself is a configuration's own, so a reconfiguration may change it; `maxInterval` is the bound a horizon is computed against. #### `Config.InBounds` *def, `Barnacle.Model.Window.lean`* ```lean def Config.InBounds (P : Params) (C : Config Validator) : Prop := (∀ r, C.slotsAt r ≤ P.maxLeaders) ∧ 0 < C.interval ∧ C.interval ≤ P.maxInterval ``` **A configuration within the parameters**: no round wider than `maxLeaders`, and an interval of at least one round and at most `maxInterval`. This is what a run asks of every configuration it reaches and what an update rule must preserve; the widths' positivity is a `Config` field and needs no clause. #### `expected` *def, `Barnacle.Model.Window.lean`* ```lean def expected (R : BaseRule Validator BlockId Payload) (C : Config Validator) (r : ℕ) : ℕ := C.cum (r + 1 - R.waveLength) - C.cum (r - C.interval) ``` **The expected count** (`ExpectedCommits`): the slots offered by the rounds of the window old enough to have been decided — those from `r − C.interval` through `r − waveLength`, for the anchor's round `r`. At one width `m` this is the paper's `(interval − waveLength + 1) · m`. The upper bound is written `r + 1 − waveLength` rather than `r − waveLength + 1`: below a wave from genesis there is no decided round, and the second form truncates to `1` and counts round `0`. #### `PartialRun` *structure, `Barnacle.Model.Run.lean`* ```lean structure PartialRun (R : BaseRule Validator BlockId Payload) (P : Params) (upd : UpdateRule R) (C₀ : Config Validator) (U : R.Universe) (V : R.View U) (K : ℕ) where /-- The round after which configuration `k` is in force. -/ start : ℕ → ℕ /-- Configuration `k`: its leaders, its slots per round, its interval. -/ cfg : ℕ → Config Validator /-- The back-off of configuration `k`. -/ backoff : ℕ → ℕ /-- The slot, in `(cfg k).sched`, of the anchor that closes configuration `k`. -/ anchor : ℕ → ℕ /-- `vdct k κ`: the verdict of slot `κ` of `(cfg k).sched`. -/ vdct : ℕ → ℕ → Option BlockId /-- The run starts after round `0`, in the genesis configuration. -/ init : start 0 = 0 ∧ cfg 0 = C₀ ∧ backoff 0 = 0 /-- Every configuration is within the parameters. -/ bounds : ∀ k, (cfg k).InBounds P /-- Every slot of the range — after `start k`, through the anchor's round — decided against the configuration's schedule (`TryDecide`). -/ closed : ∀ k, k < K → ∀ κ, start k < (cfg k).roundOf κ → (cfg k).roundOf κ ≤ start (k + 1) → R.Decided (cfg k).sched V κ (vdct k κ) /-- The anchor is committed, past the threshold … -/ anchor_commits : ∀ k, k < K → (∃ A, vdct k (anchor k) = some A) ∧ start k + (cfg k).interval < (cfg k).roundOf (anchor k) /-- … and is the least such slot. -/ anchor_least : ∀ k, k < K → ∀ κ, κ < anchor k → start k + (cfg k).interval < (cfg k).roundOf κ → vdct k κ = none /-- The next configuration is in force after the anchor's round. -/ start_succ : ∀ k, k < K → start (k + 1) = (cfg k).roundOf (anchor k) /-- The next configuration is the rule's. -/ update : ∀ k, k < K → ∀ A, vdct k (anchor k) = some A → (cfg (k + 1), backoff (k + 1)) = upd (cfg k) (backoff k) U V A ``` **A run closed up to height `K`.** Configurations `0, …, K` are determined, and the ranges of configurations below `K` are decided in full. `closed` is the paper's `TryDecide`: every slot of the range — the rounds after `start k`, through the anchor's round `start (k + 1)` — decided against the configuration's schedule. `anchor_commits` and `anchor_least` are `TryCommit`'s trigger: the anchor is the least committed slot whose round exceeds `start k + (cfg k).interval`. `update` is `UpdateLeaders`, for an arbitrary rule. `bounds` is a clause of the run because the rule is arbitrary; for the AIMD rule it is a theorem. #### `LiveRule.LiveOn` *def, `Barnacle.Model.Live.lean`* ```lean def LiveRule.LiveOn (R : LiveRule Validator BlockId Payload) (S : Slots Validator) (c : ℕ) : Prop := -- On every DAG good from `Rnd` to `N` … ∀ (U : R.Universe) (V : R.View U) (Rnd N : ℕ), R.Good U Rnd N → R.toBaseRule.CoversUpto U V N → -- … every slot at a round from `Rnd`, with `c` rounds and a wave still -- under the horizon, is decided on any view caught up to `N` … (∀ κ, Rnd ≤ S.slotRound κ → S.slotRound κ + c + R.waveLength ≤ N → ∃ v, R.Decided S V κ v) ∧ -- … and from every round `r` at or after `Rnd`, with `c` rounds and a -- wave still under the horizon, some slot at a round in `[r, r + c]` -- is committed on that view. (∀ r, Rnd ≤ r → r + c + R.waveLength ≤ N → ∃ κ, r ≤ S.slotRound κ ∧ S.slotRound κ ≤ r + c ∧ ∃ L, R.Decided S V κ (some L)) ``` **A4, liveness, on one schedule with commit gap `c`.** #### `LiveRule.Delivers` *structure, `Barnacle.Model.Live.lean`* ```lean structure LiveRule.Delivers (R : LiveRule Validator BlockId Payload) (slack : ℕ) : Prop where /-- A good author's block is in the history of every block two rounds above it. -/ reaches : ∀ (U : R.Universe) (Rnd N : ℕ), R.Good U Rnd N → ∃ T : Finset Validator, Fintype.card Validator ≤ T.card + slack ∧ ∀ b ∈ R.ids U, (R.block U b).creator ∈ T → Rnd ≤ (R.block U b).round → (R.block U b).round + 1 ≤ N → ∀ c ∈ R.ids U, (R.block U b).round + 2 ≤ (R.block U c).round → b ∈ historyFrom (R.block U) c ``` **What a good DAG delivers**: a `slack`-missing set of validators whose blocks, from `Rnd`, are reached by everything two rounds above them — the base protocol's coverage read as delivery, the second law a live rule carries beside `Descent`. #### `ofAnchored` *def, `Barnacle.Model.Anchored.lean`* ```lean def ofAnchored (R : AnchoredRule Validator BlockId Payload P honest) : BaseRule Validator BlockId Payload where toDagRule := R.toDagRule full := fun U => View.full U historyView := fun U A hA => U.historyView A hA waveLength := R.wave + 1 DirectCommitIn := fun {U} V L r => R.Commit U V L r decDirect := fun {U} V L r => R.decCommit U V L r ``` **An anchored rule as a base rule.** #### `ofAnchoredVia` *def, `Barnacle.Model.Anchored.lean`* ```lean def ofAnchoredVia (R : AnchoredRule Validator BlockId Payload P honest) {X : Type} (f : X → BlockRecord Validator BlockId Payload P honest) : BaseRule Validator BlockId Payload where toDagRule := R.toDagRuleVia f full := fun U => View.full (f U) historyView := fun U A hA => (f U).historyView A hA waveLength := R.wave + 1 DirectCommitIn := fun {U} V L r => R.Commit (f U) V L r decDirect := fun {U} V L r => R.decCommit (f U) V L r ``` **An anchored rule read through a projection, as a base rule**: the universes are any type projecting to records. ### Hydrozoan: the dual-path rule under hybrid faults #### `Faults` *class, `Hydrozoan.Model.Faults.lean`* ```lean class Faults (Replica : Type*) [Fintype Replica] [DecidableEq Replica] where /-- The Byzantine fault bound. -/ f : ℕ /-- The crash fault bound. -/ c : ℕ /-- The slack parameter. -/ k : ℕ /-- The Byzantine replicas: may deviate arbitrarily, in particular equivocate. -/ byzantine : Finset Replica /-- The crashed replicas: follow the protocol but may halt; they never equivocate. -/ crashed : Finset Replica /-- No replica is both Byzantine and crashed. -/ byzantine_disjoint_crashed : Disjoint byzantine crashed /-- There are at least `3f + 2c + k + 1` replicas. -/ card_replicas : 3 * f + 2 * c + k + 1 ≤ Fintype.card Replica /-- At most `f` replicas are Byzantine. -/ card_byzantine : byzantine.card ≤ f /-- At most `c` replicas are crashed. -/ card_crashed : crashed.card ≤ c ``` The hybrid fault model: `n ≥ 3f + 2c + k + 1` replicas, at most `f` Byzantine, at most `c` crashed, `k` a tunable slack widening the fast path. The `byzantine` and `crashed` sets are the *actual* fault assignment of a run; the bounds `f` and `c` are what the protocol is configured to tolerate. #### `p` *def, `Hydrozoan.Model.Faults.lean`* ```lean def p : ℕ := (F.c + F.k) / 2 ``` `p = ⌊(c + k)/2⌋` — the fast path's fault allowance. Derived from `c` and `k` (ℕ division is floor division), never an input. #### `q` *def, `Hydrozoan.Model.Faults.lean`* ```lean def q : ℕ := Fintype.card Replica - F.f - F.c ``` `q = n − f − c`: the DAG quorum governing round advancement and the number of refs each block references (`q` in the paper). #### `qFast` *def, `Hydrozoan.Model.Faults.lean`* ```lean def qFast : ℕ := Fintype.card Replica - p Replica ``` `q_fast = n − p`: the quorum of votes at the voting round to fast-commit a leader; also the quorum of slotBlames to directly skip it. #### `qCert` *def, `Hydrozoan.Model.Faults.lean`* ```lean def qCert : ℕ := (Fintype.card Replica + F.f) / 2 + 1 ``` `q_cert = ⌈(n + f + 1)/2⌉`: the quorum of votes a decision-round block must reference to count as a certificate, written as the smallest strict majority of `n + f` so that `2 · q_cert > n + f` is immediate. #### `qSlow` *def, `Hydrozoan.Model.Faults.lean`* ```lean def qSlow : ℕ := 2 * F.f + F.c + 1 ``` `q_slow = 2f + c + 1`: the quorum of certificates at the decision round to (slow) direct-commit a leader. #### `qWeak` *def, `Hydrozoan.Model.Faults.lean`* ```lean def qWeak : ℕ := F.f + p Replica + 1 ``` `q_weak = f + p + 1`: the quorum of anchor-linked votes to indirectly commit a leader — the second rung of the graded indirect rule. #### `Correct` *def, `Hydrozoan.Model.Faults.lean`* ```lean def Correct : Finset Replica := (F.byzantine ∪ F.crashed)ᶜ ``` The correct replicas: neither Byzantine nor crashed. The pool that availability and liveness arguments count. #### `NonByzantine` *def, `Hydrozoan.Model.Faults.lean`* ```lean def NonByzantine : Finset Replica := F.byzantineᶜ ``` The non-Byzantine replicas: correct or crashed — every replica that never equivocates. The pool that uniqueness arguments count. #### `Block` *abbrev, `Hydrozoan.Model.Block.lean`* ```lean abbrev Block (Replica BlockId : Type*) := LeanDag.Block Replica BlockId Unit ``` A Hydrozoan block: the shared block with no payload. #### `ValidWrt` *structure, `Hydrozoan.Model.Block.lean`* ```lean structure ValidWrt (blk : BlockId → Block Replica BlockId) (b : Block Replica BlockId) : Prop where /-- Every reference sits in the immediately preceding round. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- A block never references the same creator twice. -/ distinct_creators : ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j /-- Non-genesis blocks reference a DAG quorum of distinct creators. -/ quorum : 0 < b.round → q Replica ≤ (creators blk b).card ``` Block validity, relative to a lookup function: `q` distinct-creator references from the preceding round. The predecessor condition is additive (`+1 =`, never `−1`), so the genesis case (`refs = ∅`) is derivable rather than assumed. #### `BlockUniverse` *abbrev, `Hydrozoan.Model.BlockUniverse.lean`* ```lean abbrev BlockUniverse (Replica BlockId : Type*) [Fintype Replica] [DecidableEq Replica] [F : LeanDag.Hydrozoan.Faults Replica] := BlockRecord Replica BlockId Unit ValidWrt (NonByzantine : Finset Replica) ``` **The block universe**: the block record at Hydrozoan's validity, with non-equivocation asked of the non-Byzantine replicas. `block` is total, with junk outside `ids`; every clause quantifies over `i ∈ ids`, so the junk is never observed. #### `View` *abbrev, `Hydrozoan.Model.View.lean`* ```lean abbrev View {Replica BlockId : Type*} [Fintype Replica] [DecidableEq Replica] [F : LeanDag.Hydrozoan.Faults Replica] (U : BlockUniverse Replica BlockId) := BlockRecord.View U ``` A view: one replica's local DAG — a subset of the universe that is closed under references. The block record's view. #### `Populated` *abbrev, `Hydrozoan.Model.Liveness.lean`* ```lean abbrev Populated (U : BlockUniverse Replica BlockId) (r : ℕ) : Prop := PopulatedOn U (Correct : Finset Replica) r ``` The all-of-`Correct` case. #### `Synchronised` *abbrev, `Hydrozoan.Model.Liveness.lean`* ```lean abbrev Synchronised (U : BlockUniverse Replica BlockId) (R : ℕ) : Prop := SynchronisedOn U (Correct : Finset Replica) R ``` The all-of-`Correct` case. #### `CertifiedIn` *abbrev, `Hydrozoan.Model.IndirectRules.lean`* ```lean abbrev CertifiedIn (U : BlockUniverse Replica BlockId) (A L : BlockId) (r : ℕ) : Prop := certifiedLink IsVote (qCert Replica) 2 U A L r ``` Rung 1's test: a certificate for `L` lies in the anchor's causal history — the paper's `∃ b : Link(b, b_anchor) ∧ IsCertificate(b, b_leader)`, with `r` the candidate's propose round. #### `WeakLinked` *def, `Hydrozoan.Model.IndirectRules.lean`* ```lean def WeakLinked (U : BlockUniverse Replica BlockId) (A L : BlockId) (r : ℕ) : Prop := ∃ s : Finset BlockId, (∀ b ∈ s, b ∈ blocksAt U (r + 1) ∧ IsVote U b L ∧ Reaches U A b) ∧ qWeak Replica ≤ (creatorsOf U.block s).card ``` Rung 2's test: `q_weak` distinct creators of anchor-reachable votes for `L` at the voting round, stated over a witness set of such blocks rather than a filter. #### `hydrozoanAnchored` *def, `Hydrozoan.Model.Decided.lean`* ```lean def hydrozoanAnchored : AnchoredRule Replica BlockId Unit ValidWrt (NonByzantine : Finset Replica) where wave := 2 Commit := fun U V L r => FastCommitInView U V L r ∨ SlowCommitInView U V L r decCommit := fun _ _ _ _ => inferInstance Skip := fun U V S k => SkippedLeaderInView (S := S) U V k rungs := 2 Link := fun i U A L S k => match i with | 0 => CertifiedIn U A L (S.slotRound k) | _ => WeakLinked U A L (S.slotRound k) tie := fun i L' L => match i with | 0 => False | _ => L' < L ``` **Hydrozoan as an anchored rule**: wave two; the direct commit is the fast path or the slow path in view; the direct skip is `q_fast` slotBlames in view; two rungs, the anchor-linked certificate and then the weak quorum, the second tie-broken by the order. #### `Decided` *abbrev, `Hydrozoan.Model.Decided.lean`* ```lean abbrev Decided (U : BlockUniverse Replica BlockId) (V : View U) : ℕ → Option BlockId → Prop := (hydrozoanAnchored Replica BlockId).Decided (S := S) U V ``` The verdicts a replica holding view `V` may reach on slot `k`. #### `FastUniqueness` *def, `Hydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def FastUniqueness : Prop := Fintype.card Replica + F.f < 2 * qFast Replica ``` **No two conflicting fast commits**, `2·q_fast > n + f`: two fast quorums must overlap in a non-Byzantine replica, so no two conflicting leaders are both fast-committed. #### `ledger` *def, `Hydrozoan.PrefixAgreement.Statement.lean`* ```lean def ledger (lin : BlockId → List BlockId) (g : ℕ → Option BlockId) (n : ℕ) : List BlockId := (commitSeq g n).flatMap lin ``` A ledger: every committed leader flattened by a linearizer — the paper's `LinearizeSubDags`, abstracted to an arbitrary function. #### `DecidesBelow` *def, `Hydrozoan.PrefixAgreement.Statement.lean`* ```lean def DecidesBelow (U : BlockUniverse Replica BlockId) (V : View U) (g : ℕ → Option BlockId) (n : ℕ) : Prop := ∀ k < n, Decided U V k (g k) ``` `g` records a decided verdict for every slot below `n`, as judged from `V`. #### `CommitLiveness` *def, `Hydrozoan.DirectLiveness.Statement.lean`* ```lean def CommitLiveness (U : BlockUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R k : ℕ), -- for any set T, round R, slot k: T ⊆ (Correct : Finset Replica) → -- T holds only correct replicas ... q Replica ≤ T.card → -- ... and is at least a DAG quorum, SynchronisedOn U T R → -- T is internally synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, PopulatedOn U T (S.slotRound k) → -- T fills the propose round ... PopulatedOn U T (S.slotRound k + 1) → -- ... the voting round ... PopulatedOn U T (S.slotRound k + 2) → -- ... and the decision round, S.leader k ∈ T → -- and the slot's leader is in T: ∀ V : View U, -- then, on any view caught up V.CoversUpto (S.slotRound k + 2) → -- ... to the decision round: ∃ L, IsLeaderBlock U k L ∧ -- a candidate exists, SlowCommit U L (S.slotRound k) ∧ -- the slow threshold is met, Decided U V k (some L) -- and its verdict is committed ``` **Commit liveness**: a quorum-sized correct set, populated through the wave and synchronised from `R`, slow-commits its correct leader on any view caught up to the decision round. The fast path may also fire in the same universe; this is the route the guaranteed quorum always reaches. #### `FastLatency` *def, `Hydrozoan.DirectLiveness.Statement.lean`* ```lean def FastLatency (U : BlockUniverse Replica BlockId) : Prop := ∀ (R k : ℕ), -- for any round R and slot k: (F.byzantine ∪ F.crashed).card ≤ p Replica → -- ACTUAL faults fit p, Synchronised U R → -- all correct synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, Populated U (S.slotRound k) → -- correct fill the propose round ... Populated U (S.slotRound k + 1) → -- ... and the voting round, S.leader k ∈ (Correct : Finset Replica) → -- and the leader is correct: ∃ L, IsLeaderBlock U k L ∧ -- then a candidate exists ... FastCommit U L (S.slotRound k) -- ... and it fast-commits (2 rounds) ``` **Performance, not liveness — deliberately outside `Statement`.** When the actual faults fit `p`, a synchronised, populated wave with a correct leader fires the fast path in two rounds — needing all of `Correct`, not just a quorum-sized `T`. #### `RunsRecur` *def, `Hydrozoan.EventualDecision.Statement.lean`* ```lean def RunsRecur : Prop := ∀ (T : Finset Replica) (c k R : ℕ), FairRunOn T c → -- given a fair schedule: ∃ b, k ≤ b ∧ -- a run location past k ... R ≤ S.slotRound b ∧ -- ... at or after round R ... ∀ i, i < c → S.leader (b + i) ∈ T -- ... with every slot T-led. ``` **Fairness places a run wherever needed**: past any slot `k` and any round `R`, some run of `c` consecutive `T`-led slots begins. Pure schedule arithmetic. #### `RunDecidesBelow` *def, `Hydrozoan.EventualDecision.Statement.lean`* ```lean def RunDecidesBelow (U : BlockUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R b c : ℕ), T ⊆ (Correct : Finset Replica) → -- a set of correct replicas ... q Replica ≤ T.card → -- ... of at least a DAG quorum, SynchronisedOn U T R → -- internally synchronised from R, 0 < c → -- a nonempty run of slots ... (hydrozoanAnchored Replica BlockId).SpansEligible c → -- ... every run's end anchoring all below, R ≤ S.slotRound b → -- lying at or after R, (∀ i, i < c → S.leader (b + i) ∈ T) → -- every run slot T-led, (∀ r, S.slotRound b ≤ r → -- and T fills every round from r ≤ S.slotRound (b + c - 1) + 2 → -- the run's propose round to its PopulatedOn U T r) → -- last decision round: ∀ V : View U, -- then, on any view caught up V.CoversUpto (S.slotRound (b + c - 1) + 2) → -- ... to that round: ∀ i, i < b → ∃ v, Decided U V i v -- all below decided. ``` **A committed-to-be run decides everything below it.** The workhorse with the run location `b` explicit: direct liveness commits each of the `c` run slots, and the indirect descent settles every slot below. #### `WaveRobinFair` *def, `Hydrozoan.Grounding.Statement.lean`* ```lean def WaveRobinFair : Prop := ∀ (n : ℕ) (hn : 0 < n) [Faults (Fin n)], FairRunOn (S := waveRobin n hn) (Correct : Finset (Fin n)) 3 -- correct 3-runs recur. ``` **A fair schedule exists, unconditionally.** One correct leader's wave is a full correct 3-run by itself and recurs every cycle, so wave-aligned round-robin is fair with no premise beyond the fault model — unlike per-slot rotation, which needs `n` to exceed three times the actual fault count. #### `GroundedProgress` *def, `Hydrozoan.Grounding.Statement.lean`* ```lean def GroundedProgress : Prop := ∀ (n : ℕ) (hn : 0 < n) [Faults (Fin n)], ∀ k : ℕ, ∃ b, k ≤ b ∧ -- past any slot k, ∃ U : BlockUniverse (Fin n) ℕ, -- some universe commits b: ∀ V : View U, -- on any view caught up to V.CoversUpto (b + 4) → -- ... the decision round, (∃ L, Decided (S := waveRobin n hn) U V b (some L)) ∧ ∀ i, i < b → ∃ v, -- with every slot below Decided (S := waveRobin n hn) U V i v -- decided. ``` **Grounded progress.** Under wave-aligned round-robin, past every slot some bound is committed with every slot below it decided, on any view caught up to the decision round — satisfiability, not a claim about which rule reaches the verdict. The bound itself must commit; an all-skip universe does not qualify. ### Optimal-Hydrozoan: the fast path at Hydrangea's bound #### `OptimalFaults` *class, `OptimalHydrozoan.Model.Faults.lean`* ```lean class OptimalFaults (Replica : Type*) [Fintype Replica] [DecidableEq Replica] extends LeanDag.Hydrozoan.Faults Replica where /-- The fault model is non-trivial: at least one fault of some kind is tolerated (`f + c ≥ 1`). -/ nontrivial : 1 ≤ f + c ``` Hydrozoan's `Faults`, plus the standing assumption `f + c ≥ 1`: at `f = c = 0` the threshold arithmetic is not guaranteed (it fails at `n = 1`), so the case is excluded here rather than assumed away in every statement. #### `pOpt` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def pOpt : ℕ := p Replica + 1 ``` `pOpt = ⌊(c+k)/2⌋ + 1`: the fast path's fault allowance, one more than Hydrozoan's `p`, defined through it. #### `qFastOpt` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def qFastOpt : ℕ := Fintype.card Replica - pOpt Replica ``` `q_fast = n − pOpt`: the quorum of votes at the voting round to fast-commit a leader, one fewer than Hydrozoan's at the same committee size. Unlike Hydrozoan, this is not the direct skip's blame quorum, which becomes `q_cert`. #### `tPlain` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def tPlain : ℕ := Fintype.card Replica - (2 * O.f + O.c + pOpt Replica) ``` `t_plain = n − 2f − c − pOpt`: the votes for a leader block a decision-round block must reference to be fast evidence for it, absent an equivocation witness. A truncated ℕ subtraction on purpose: the arithmetic phase's identity `q_fast + q = n + f + t_plain` fails under truncation, so proving it also certifies none occurred. #### `tEquiv` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def tEquiv : ℕ := O.f + pOpt Replica ``` `t_equiv = f + pOpt`: the same threshold when the decision-round block witnesses an equivocation of the leader — it must then reference at least `t_equiv` votes for the candidate and fewer than `t_equiv` for every conflicting block (`IsFastEvidence`, second case; O4). #### `CertFastExclusion` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def CertFastExclusion : Prop := Fintype.card Replica + O.f < qCert Replica + qFastOpt Replica ``` **A fast commit starves every conflicting certificate**, `q_cert + q_fast > n + f` (row 2): the two quorums overlap in a non-Byzantine replica, which is also what makes `q_cert` slotBlames exclude a fast commit. #### `EvidenceEquiv` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def EvidenceEquiv : Prop := Fintype.card Replica + O.f + tEquiv Replica ≤ qFastOpt Replica + q Replica + 1 ``` **Fast evidence with an exposed equivocation**, row 6: subtraction-free as `n + f + t_equiv ≤ q_fast + q + 1`. The `+ 1` is the leader-exclusion dividend — a witnessing block excludes the leader, so at most `f − 1` of its refs are undetected Byzantine. #### `FastUniqueness` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def FastUniqueness : Prop := 1 ≤ O.f → Fintype.card Replica + O.f < 2 * qFastOpt Replica ``` **No two conflicting fast commits**, `2·q_fast > n + f`, guarded by `f ≥ 1`: two fast quorums overlap in a non-Byzantine replica. The guard is necessary — at `f = 0` the row can fail (`fourCrashOnlySlack`) — and `1 ≤ f` is the paper's exact form; a silently stronger guard would still pass every witness, since a weakening of a true row is invisible to `decide`. #### `ValidOpt` *abbrev, `OptimalHydrozoan.Model.Universe.lean`* ```lean abbrev ValidOpt : Validity Replica BlockId Unit := fun blk b => LeanDag.Hydrozoan.ValidWrt blk b ∧ Clause.leaderExcluded blk b ``` **Optimal-Hydrozoan's validity**: Hydrozoan's, and leader exclusion. #### `OptUniverse` *abbrev, `OptimalHydrozoan.Model.Universe.lean`* ```lean abbrev OptUniverse (Replica BlockId : Type*) [Fintype Replica] [DecidableEq Replica] [DecidableEq BlockId] [F : LeanDag.Hydrozoan.Faults Replica] := BlockRecord Replica BlockId Unit ValidOpt (NonByzantine : Finset Replica) ``` **The block universe**: the block record at Optimal's validity, with non-equivocation asked of the non-Byzantine replicas. #### `OptUniverse.toBlockRecord` *def, `OptimalHydrozoan.Model.Universe.lean`* ```lean def OptUniverse.toBlockRecord (U : OptUniverse Replica BlockId) : LeanDag.Hydrozoan.BlockUniverse Replica BlockId := { U with valid := fun i hi => (U.valid i hi).1 } ``` **The Hydrozoan universe beneath**: the same blocks, the exclusion forgotten. Hydrozoan's rules and lemmas read an Optimal universe through it. #### `LeaderExcluded` *def, `OptimalHydrozoan.Model.Universe.lean`* ```lean def LeaderExcluded (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) : Prop := ∀ b ∈ U.ids, ∀ k, (U.block b).round = LeanDag.Hydrozoan.decisionRound Replica k → WitnessesEquivocation U k b → ∀ j ∈ (U.block b).refs, (U.block j).creator ≠ S.leader k ``` **Leader exclusion at a schedule** — the validity rule as `sections/optimal-protocol.tex` states it: a block at the decision round of slot `k` that witnesses an equivocation in `k` references no block by `k`'s leader. The form the decision relation's laws hold under; every Optimal universe satisfies it at every schedule (`OptUniverse.leader_excluded`). #### `NoEvidenceQuorum` *def, `OptimalHydrozoan.Model.DirectRules.lean`* ```lean def NoEvidenceQuorum (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) (k : ℕ) : Prop := ∃ s : Finset BlockId, -- some set of blocks such that (∀ b ∈ s, -- every block in it b ∈ blocksAt U (LeanDag.Hydrozoan.decisionRound Replica k) ∧ -- sits at slot k's decision round IsNoFastEvidence U k b) ∧ -- and is evidence for no candidate; qCert Replica ≤ (creatorsOf U.block s).card -- and they come from q_cert creators ``` `qCert` distinct creators of decision-round blocks of slot `k` that are fast evidence for no candidate — the second half of the paper's `SkippedLeader`, `noEvidence`. Existential over a witness set of blocks (see the module docstring). #### `optimalAnchored` *def, `OptimalHydrozoan.Model.Decided.lean`* ```lean def optimalAnchored : AnchoredRule Replica BlockId Unit LeanDag.Hydrozoan.ValidWrt (LeanDag.Hydrozoan.NonByzantine : Finset Replica) where wave := 2 Commit := fun U V L r => FastCommitOptInView U V L r ∨ SlowCommitInView U V L r decCommit := fun _ _ _ _ => inferInstance Skip := fun U V S k => SkippedLeaderOptInView (S := S) U V k rungs := 2 Link := fun i U A L S k => match i with | 0 => LeanDag.Hydrozoan.CertifiedIn U A L (S.slotRound k) | _ => EvidenceLinked (S := S) U A L k tie := fun _ _ _ => False ``` **Optimal-Hydrozoan as an anchored rule**: wave two, the direct commit the Optimal fast path or Hydrozoan's slow path, two rungs (certificate, then evidence quorum), neither tie-broken. #### `DecidedOpt` *abbrev, `OptimalHydrozoan.Model.Decided.lean`* ```lean abbrev DecidedOpt (U : OptUniverse Replica BlockId) (V : LeanDag.Hydrozoan.View U.toBlockRecord) : ℕ → Option BlockId → Prop := (optimalAnchored Replica BlockId).Decided (S := S) U.toBlockRecord V ``` The verdicts a replica holding view `V` may reach on slot `k`. #### `DecidesBelow` *def, `OptimalHydrozoan.PrefixAgreement.Statement.lean`* ```lean def DecidesBelow (U : OptUniverse Replica BlockId) (V : LeanDag.Hydrozoan.View U.toBlockRecord) (g : ℕ → Option BlockId) (n : ℕ) : Prop := ∀ k < n, DecidedOpt U V k (g k) ``` `g` records a decided verdict for every slot below `n`, as judged from `V`. #### `CommitLiveness` *def, `OptimalHydrozoan.DirectLiveness.Statement.lean`* ```lean def CommitLiveness (U : OptUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R k : ℕ), -- for any set T, round R, slot k: T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- T holds only correct replicas ... q Replica ≤ T.card → -- ... and is at least a DAG quorum, SynchronisedOn U.toBlockRecord T R → -- T is internally synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, PopulatedOn U.toBlockRecord T (S.slotRound k) → -- T fills the propose round ... PopulatedOn U.toBlockRecord T (S.slotRound k + 1) → -- ... the voting round ... PopulatedOn U.toBlockRecord T (S.slotRound k + 2) → -- ... and the decision round, S.leader k ∈ T → -- and the slot's leader is in T: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- then, on any view caught up V.CoversUpto (S.slotRound k + 2) → -- ... to the decision round: ∃ L, IsLeaderBlock U.toBlockRecord k L ∧ -- a candidate exists, SlowCommit U.toBlockRecord L (S.slotRound k) ∧ -- the slow threshold is met, DecidedOpt U V k (some L) -- and its verdict is committed ``` **Commit liveness** (Hydrozoan's, harvested as `DecidedOpt`): a quorum-sized correct set, populated through the wave and synchronised from `R`, slow-commits its correct leader, on any view caught up to the decision round. The fast path may also fire in the same universe; this is the route the guaranteed quorum always reaches. #### `FastLatency` *def, `OptimalHydrozoan.DirectLiveness.Statement.lean`* ```lean def FastLatency (U : OptUniverse Replica BlockId) : Prop := ∀ (R k : ℕ), -- for any round R and slot k: (O.byzantine ∪ O.crashed).card ≤ pOpt Replica → -- ACTUAL faults fit pOpt, Synchronised U.toBlockRecord R → -- all correct synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, Populated U.toBlockRecord (S.slotRound k) → -- correct fill the propose round ... Populated U.toBlockRecord (S.slotRound k + 1) → -- ... and the voting round, S.leader k ∈ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- and the leader is correct: ∃ L, IsLeaderBlock U.toBlockRecord k L ∧ -- then a candidate exists ... FastCommitOpt U.toBlockRecord L (S.slotRound k) -- ... and it fast-commits ``` **Performance, not liveness — deliberately outside `Statement`.** When the actual faults fit `pOpt`, a synchronised, populated wave with a correct leader fires the fast path in two rounds — needing all of `Correct`, not just a quorum-sized `T`. #### `RunDecidesBelow` *def, `OptimalHydrozoan.EventualDecision.Statement.lean`* ```lean def RunDecidesBelow (U : OptUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R b c : ℕ), T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- a set of correct replicas ... q Replica ≤ T.card → -- ... of at least a DAG quorum, SynchronisedOn U.toBlockRecord T R → -- internally synchronised from R, 0 < c → -- a nonempty run of slots ... (optimalAnchored Replica BlockId).SpansEligible c → -- ... every run's end anchoring all below, R ≤ S.slotRound b → -- lying at or after R, (∀ i, i < c → S.leader (b + i) ∈ T) → -- every run slot T-led, (∀ r, S.slotRound b ≤ r → -- and T fills every round from r ≤ S.slotRound (b + c - 1) + 2 → -- the run's propose round to its PopulatedOn U.toBlockRecord T r) → -- last decision round: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- then, on any view caught up V.CoversUpto (S.slotRound (b + c - 1) + 2) → -- ... to that round: ∀ i, i < b → ∃ v, DecidedOpt U V i v -- all below decided. ``` **A committed-to-be run decides everything below it.** Direct liveness commits each of the `c` run slots through the unchanged slow path, and the indirect descent settles every slot below. #### `GroundedProgress` *def, `OptimalHydrozoan.Grounding.Statement.lean`* ```lean def GroundedProgress : Prop := ∀ (n : ℕ) (hn : 0 < n) [OptimalFaults (Fin n)], letI : Slots (Fin n) := waveRobin n hn -- under wave-aligned rotation, ∀ k : ℕ, ∃ b, k ≤ b ∧ -- past any slot k, ∃ U : OptUniverse (Fin n) ℕ, -- some Optimal universe (∀ i ∈ U.ids, (U.block i).creator ∈ LeanDag.Hydrozoan.Correct) ∧ -- of correct creators only: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- on any view caught up to V.CoversUpto (b + 4) → -- ... the decision round, it (∃ L, DecidedOpt U V b (some L)) ∧ -- commits b ∀ i, i < b → ∃ v, -- with every slot below DecidedOpt U V i v -- decided. ``` **Grounded progress.** Under wave-aligned round-robin, past every point some Optimal universe authored by correct replicas alone commits a bound with every slot below it decided, on any view caught up to the decision round — satisfiability, as in Hydrozoan. Correct-only authorship is what makes the claim consult the fault sets at all: without it, a universe where every replica participates would satisfy the conclusion trivially. #### `OptimalFaults` *class, `OptimalHydrozoan.Model.Faults.lean`* ```lean class OptimalFaults (Replica : Type*) [Fintype Replica] [DecidableEq Replica] extends LeanDag.Hydrozoan.Faults Replica where /-- The fault model is non-trivial: at least one fault of some kind is tolerated (`f + c ≥ 1`). -/ nontrivial : 1 ≤ f + c ``` Hydrozoan's `Faults`, plus the standing assumption `f + c ≥ 1`: at `f = c = 0` the threshold arithmetic is not guaranteed (it fails at `n = 1`), so the case is excluded here rather than assumed away in every statement. #### `pOpt` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def pOpt : ℕ := p Replica + 1 ``` `pOpt = ⌊(c+k)/2⌋ + 1`: the fast path's fault allowance, one more than Hydrozoan's `p`, defined through it. #### `qFastOpt` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def qFastOpt : ℕ := Fintype.card Replica - pOpt Replica ``` `q_fast = n − pOpt`: the quorum of votes at the voting round to fast-commit a leader, one fewer than Hydrozoan's at the same committee size. Unlike Hydrozoan, this is not the direct skip's blame quorum, which becomes `q_cert`. #### `tPlain` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def tPlain : ℕ := Fintype.card Replica - (2 * O.f + O.c + pOpt Replica) ``` `t_plain = n − 2f − c − pOpt`: the votes for a leader block a decision-round block must reference to be fast evidence for it, absent an equivocation witness. A truncated ℕ subtraction on purpose: the arithmetic phase's identity `q_fast + q = n + f + t_plain` fails under truncation, so proving it also certifies none occurred. #### `tEquiv` *def, `OptimalHydrozoan.Model.Faults.lean`* ```lean def tEquiv : ℕ := O.f + pOpt Replica ``` `t_equiv = f + pOpt`: the same threshold when the decision-round block witnesses an equivocation of the leader — it must then reference at least `t_equiv` votes for the candidate and fewer than `t_equiv` for every conflicting block (`IsFastEvidence`, second case; O4). #### `CertFastExclusion` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def CertFastExclusion : Prop := Fintype.card Replica + O.f < qCert Replica + qFastOpt Replica ``` **A fast commit starves every conflicting certificate**, `q_cert + q_fast > n + f` (row 2): the two quorums overlap in a non-Byzantine replica, which is also what makes `q_cert` slotBlames exclude a fast commit. #### `EvidenceEquiv` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def EvidenceEquiv : Prop := Fintype.card Replica + O.f + tEquiv Replica ≤ qFastOpt Replica + q Replica + 1 ``` **Fast evidence with an exposed equivocation**, row 6: subtraction-free as `n + f + t_equiv ≤ q_fast + q + 1`. The `+ 1` is the leader-exclusion dividend — a witnessing block excludes the leader, so at most `f − 1` of its refs are undetected Byzantine. #### `FastUniqueness` *def, `OptimalHydrozoan.ThresholdArithmetic.Statement.lean`* ```lean def FastUniqueness : Prop := 1 ≤ O.f → Fintype.card Replica + O.f < 2 * qFastOpt Replica ``` **No two conflicting fast commits**, `2·q_fast > n + f`, guarded by `f ≥ 1`: two fast quorums overlap in a non-Byzantine replica. The guard is necessary — at `f = 0` the row can fail (`fourCrashOnlySlack`) — and `1 ≤ f` is the paper's exact form; a silently stronger guard would still pass every witness, since a weakening of a true row is invisible to `decide`. #### `ValidOpt` *abbrev, `OptimalHydrozoan.Model.Universe.lean`* ```lean abbrev ValidOpt : Validity Replica BlockId Unit := fun blk b => LeanDag.Hydrozoan.ValidWrt blk b ∧ Clause.leaderExcluded blk b ``` **Optimal-Hydrozoan's validity**: Hydrozoan's, and leader exclusion. #### `OptUniverse` *abbrev, `OptimalHydrozoan.Model.Universe.lean`* ```lean abbrev OptUniverse (Replica BlockId : Type*) [Fintype Replica] [DecidableEq Replica] [DecidableEq BlockId] [F : LeanDag.Hydrozoan.Faults Replica] := BlockRecord Replica BlockId Unit ValidOpt (NonByzantine : Finset Replica) ``` **The block universe**: the block record at Optimal's validity, with non-equivocation asked of the non-Byzantine replicas. #### `OptUniverse.toBlockRecord` *def, `OptimalHydrozoan.Model.Universe.lean`* ```lean def OptUniverse.toBlockRecord (U : OptUniverse Replica BlockId) : LeanDag.Hydrozoan.BlockUniverse Replica BlockId := { U with valid := fun i hi => (U.valid i hi).1 } ``` **The Hydrozoan universe beneath**: the same blocks, the exclusion forgotten. Hydrozoan's rules and lemmas read an Optimal universe through it. #### `LeaderExcluded` *def, `OptimalHydrozoan.Model.Universe.lean`* ```lean def LeaderExcluded (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) : Prop := ∀ b ∈ U.ids, ∀ k, (U.block b).round = LeanDag.Hydrozoan.decisionRound Replica k → WitnessesEquivocation U k b → ∀ j ∈ (U.block b).refs, (U.block j).creator ≠ S.leader k ``` **Leader exclusion at a schedule** — the validity rule as `sections/optimal-protocol.tex` states it: a block at the decision round of slot `k` that witnesses an equivocation in `k` references no block by `k`'s leader. The form the decision relation's laws hold under; every Optimal universe satisfies it at every schedule (`OptUniverse.leader_excluded`). #### `NoEvidenceQuorum` *def, `OptimalHydrozoan.Model.DirectRules.lean`* ```lean def NoEvidenceQuorum (U : LeanDag.Hydrozoan.BlockUniverse Replica BlockId) (k : ℕ) : Prop := ∃ s : Finset BlockId, -- some set of blocks such that (∀ b ∈ s, -- every block in it b ∈ blocksAt U (LeanDag.Hydrozoan.decisionRound Replica k) ∧ -- sits at slot k's decision round IsNoFastEvidence U k b) ∧ -- and is evidence for no candidate; qCert Replica ≤ (creatorsOf U.block s).card -- and they come from q_cert creators ``` `qCert` distinct creators of decision-round blocks of slot `k` that are fast evidence for no candidate — the second half of the paper's `SkippedLeader`, `noEvidence`. Existential over a witness set of blocks (see the module docstring). #### `optimalAnchored` *def, `OptimalHydrozoan.Model.Decided.lean`* ```lean def optimalAnchored : AnchoredRule Replica BlockId Unit LeanDag.Hydrozoan.ValidWrt (LeanDag.Hydrozoan.NonByzantine : Finset Replica) where wave := 2 Commit := fun U V L r => FastCommitOptInView U V L r ∨ SlowCommitInView U V L r decCommit := fun _ _ _ _ => inferInstance Skip := fun U V S k => SkippedLeaderOptInView (S := S) U V k rungs := 2 Link := fun i U A L S k => match i with | 0 => LeanDag.Hydrozoan.CertifiedIn U A L (S.slotRound k) | _ => EvidenceLinked (S := S) U A L k tie := fun _ _ _ => False ``` **Optimal-Hydrozoan as an anchored rule**: wave two, the direct commit the Optimal fast path or Hydrozoan's slow path, two rungs (certificate, then evidence quorum), neither tie-broken. #### `DecidedOpt` *abbrev, `OptimalHydrozoan.Model.Decided.lean`* ```lean abbrev DecidedOpt (U : OptUniverse Replica BlockId) (V : LeanDag.Hydrozoan.View U.toBlockRecord) : ℕ → Option BlockId → Prop := (optimalAnchored Replica BlockId).Decided (S := S) U.toBlockRecord V ``` The verdicts a replica holding view `V` may reach on slot `k`. #### `DecidesBelow` *def, `OptimalHydrozoan.PrefixAgreement.Statement.lean`* ```lean def DecidesBelow (U : OptUniverse Replica BlockId) (V : LeanDag.Hydrozoan.View U.toBlockRecord) (g : ℕ → Option BlockId) (n : ℕ) : Prop := ∀ k < n, DecidedOpt U V k (g k) ``` `g` records a decided verdict for every slot below `n`, as judged from `V`. #### `CommitLiveness` *def, `OptimalHydrozoan.DirectLiveness.Statement.lean`* ```lean def CommitLiveness (U : OptUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R k : ℕ), -- for any set T, round R, slot k: T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- T holds only correct replicas ... q Replica ≤ T.card → -- ... and is at least a DAG quorum, SynchronisedOn U.toBlockRecord T R → -- T is internally synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, PopulatedOn U.toBlockRecord T (S.slotRound k) → -- T fills the propose round ... PopulatedOn U.toBlockRecord T (S.slotRound k + 1) → -- ... the voting round ... PopulatedOn U.toBlockRecord T (S.slotRound k + 2) → -- ... and the decision round, S.leader k ∈ T → -- and the slot's leader is in T: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- then, on any view caught up V.CoversUpto (S.slotRound k + 2) → -- ... to the decision round: ∃ L, IsLeaderBlock U.toBlockRecord k L ∧ -- a candidate exists, SlowCommit U.toBlockRecord L (S.slotRound k) ∧ -- the slow threshold is met, DecidedOpt U V k (some L) -- and its verdict is committed ``` **Commit liveness** (Hydrozoan's, harvested as `DecidedOpt`): a quorum-sized correct set, populated through the wave and synchronised from `R`, slow-commits its correct leader, on any view caught up to the decision round. The fast path may also fire in the same universe; this is the route the guaranteed quorum always reaches. #### `FastLatency` *def, `OptimalHydrozoan.DirectLiveness.Statement.lean`* ```lean def FastLatency (U : OptUniverse Replica BlockId) : Prop := ∀ (R k : ℕ), -- for any round R and slot k: (O.byzantine ∪ O.crashed).card ≤ pOpt Replica → -- ACTUAL faults fit pOpt, Synchronised U.toBlockRecord R → -- all correct synchronised from R, R ≤ S.slotRound k → -- the wave lies at or after R, Populated U.toBlockRecord (S.slotRound k) → -- correct fill the propose round ... Populated U.toBlockRecord (S.slotRound k + 1) → -- ... and the voting round, S.leader k ∈ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- and the leader is correct: ∃ L, IsLeaderBlock U.toBlockRecord k L ∧ -- then a candidate exists ... FastCommitOpt U.toBlockRecord L (S.slotRound k) -- ... and it fast-commits ``` **Performance, not liveness — deliberately outside `Statement`.** When the actual faults fit `pOpt`, a synchronised, populated wave with a correct leader fires the fast path in two rounds — needing all of `Correct`, not just a quorum-sized `T`. #### `RunDecidesBelow` *def, `OptimalHydrozoan.EventualDecision.Statement.lean`* ```lean def RunDecidesBelow (U : OptUniverse Replica BlockId) : Prop := ∀ (T : Finset Replica) (R b c : ℕ), T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → -- a set of correct replicas ... q Replica ≤ T.card → -- ... of at least a DAG quorum, SynchronisedOn U.toBlockRecord T R → -- internally synchronised from R, 0 < c → -- a nonempty run of slots ... (optimalAnchored Replica BlockId).SpansEligible c → -- ... every run's end anchoring all below, R ≤ S.slotRound b → -- lying at or after R, (∀ i, i < c → S.leader (b + i) ∈ T) → -- every run slot T-led, (∀ r, S.slotRound b ≤ r → -- and T fills every round from r ≤ S.slotRound (b + c - 1) + 2 → -- the run's propose round to its PopulatedOn U.toBlockRecord T r) → -- last decision round: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- then, on any view caught up V.CoversUpto (S.slotRound (b + c - 1) + 2) → -- ... to that round: ∀ i, i < b → ∃ v, DecidedOpt U V i v -- all below decided. ``` **A committed-to-be run decides everything below it.** Direct liveness commits each of the `c` run slots through the unchanged slow path, and the indirect descent settles every slot below. #### `GroundedProgress` *def, `OptimalHydrozoan.Grounding.Statement.lean`* ```lean def GroundedProgress : Prop := ∀ (n : ℕ) (hn : 0 < n) [OptimalFaults (Fin n)], letI : Slots (Fin n) := waveRobin n hn -- under wave-aligned rotation, ∀ k : ℕ, ∃ b, k ≤ b ∧ -- past any slot k, ∃ U : OptUniverse (Fin n) ℕ, -- some Optimal universe (∀ i ∈ U.ids, (U.block i).creator ∈ LeanDag.Hydrozoan.Correct) ∧ -- of correct creators only: ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, -- on any view caught up to V.CoversUpto (b + 4) → -- ... the decision round, it (∃ L, DecidedOpt U V b (some L)) ∧ -- commits b ∀ i, i < b → ∃ v, -- with every slot below DecidedOpt U V i v -- decided. ``` **Grounded progress.** Under wave-aligned round-robin, past every point some Optimal universe authored by correct replicas alone commits a bound with every slot below it decided, on any view caught up to the decision round — satisfiability, as in Hydrozoan. Correct-only authorship is what makes the claim consult the fault sets at all: without it, a universe where every replica participates would satisfy the conclusion trivially. ### Not otherwise grouped #### `HorizonStable` *def, `Adaptive.Joiner.lean`* ```lean def HorizonStable (P : Policy R) (G d : ℕ) (pick' : (U' : R.Universe) → R.View U' → (ℕ → Option BlockId) → ℕ → Validator) : Prop := ∀ (U U' : R.Universe), RebasedAbove R U U' G G → ∀ (V : R.View U) (V' : R.View U') (v : ℕ → Option BlockId) (k : ℕ), pick' U' V' (fun m => v (d + m)) k = P.pick U V v (d + k) ``` **Horizon-stability**: a validator that pruned below `G` and re-indexed from `d` still computes the leaders everyone else is using. #### `Config` *structure, `Barnacle.Config.lean`* ```lean structure Config (Validator : Type) where /-- How many slots round `r` holds. A function of the round, so a configuration's rounds need not agree. -/ slotsAt : ℕ → ℕ slotsAt_pos : ∀ r, 0 < slotsAt r /-- Who leads position `i` of round `r`. -/ lead : ℕ → ℕ → Validator /-- Distinct positions of a round have distinct leaders, which is what `Slots.keyed` asks of the schedule this gives. -/ keyed : ∀ r i j, i < slotsAt r → j < slotsAt r → lead r i = lead r j → i = j /-- Rounds from the configuration's start before the next reconfiguration is due. -/ interval : ℕ ``` **A configuration.** The schedule its rounds run on, given by the slots each round holds and the leader of each place, and the rounds it runs before the next reconfiguration is due. #### `Config.uniform` *def, `Barnacle.Config.lean`* ```lean def Config.uniform (lead : ℕ → ℕ → Validator) {w : ℕ} (hl : LeadKeyed lead w) (m : ℕ) (hm : 0 < m) (hmax : m ≤ w) (I : ℕ) : Config Validator where slotsAt := fun _ => m slotsAt_pos := fun _ => hm lead := lead keyed := fun r i j hi hj h => hl r i j (lt_of_lt_of_le hi hmax) (lt_of_lt_of_le hj hmax) h interval := I ``` **A configuration whose every round is `m` slots wide.** The shape the paper's mechanism emits, where only the count varies. #### `commitSeq` *def, `BlackMarlin.Model.Recursion.lean`* ```lean def commitSeq (U : BlockUniverse Validator BlockId Payload) (τ : TopoSort U) : ℕ → BlockId → Finset BlockId → List BlockId × Finset BlockId | 0, B, D => ([], insert B D) | (n + 1), B, D => let pd := match descendD U B D with | none => (([] : List BlockId), D) | some B' => commitSeq U τ n B' D let md := emitFrom U (τ.sort ((history U B).erase B \ pd.2)) pd.2 (pd.1 ++ md.1 ++ (if keyHeld U B md.2 then [] else [B]), insert B md.2) ``` **L18–L32**, with fuel for the recursion: descend first, then emit `τ(past(B) \ D)`, then `B` itself. Returns what the invocation `ab-deliver`s and the delivered set it leaves behind, so successive invocations compose. #### `toDagRule` *def, `Common.Anchored.Band.lean`* ```lean def toDagRule [P.Mechanised] : DagRule Validator BlockId Payload where Universe := BlockRecord Validator BlockId Payload P honest View := fun U => U.View block := fun U i => U.block i ids := fun U => U.ids viewIds := fun V => V.ids viewSound := fun V => V.subset_ids viewComplete := fun V => V.complete causal := fun U => U.causal Decided := fun S _ V k v => R.Decided (S := S) _ V k v ``` **An anchored rule as a carrier**: the record as universe, the record's views, the relation as the verdict. #### `toDagRuleOn` *abbrev, `Common.Anchored.Band.lean`* ```lean abbrev toDagRuleOn (I : BlockRecord Validator BlockId Payload P honest → Prop) : DagRule Validator BlockId Payload := R.toDagRuleVia (fun U : {U : BlockRecord Validator BlockId Payload P honest // I U} => U.val) ``` **An anchored rule under an invariant, as a carrier**: the records satisfying `I` as universes. #### `DecidedWithin` *inductive, `Common.Anchored.Bounded.lean`* ```lean inductive DecidedWithin (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (B : ℕ) : ℕ → Option BlockId → Prop /-- The direct rule commits a candidate outright. -/ | directCommit {k : ℕ} {L : BlockId} : k < B → IsLeaderBlock U k L → R.Commit U V L (S.slotRound k) → DecidedWithin U V B k (some L) /-- The direct rule skips the slot. -/ | directSkip {k : ℕ} : k < B → R.Skip U V S k → DecidedWithin U V B k none /-- Anchored below the bound, the tie-break's choice at the first nonempty rung is committed. -/ | indirectCommit {k j : ℕ} {A L : BlockId} {i : ℕ} : k < j → j < B → R.Eligible k j → DecidedWithin U V B j (some A) → (∀ m, k < m → m < j → R.Eligible k m → DecidedWithin U V B m none) → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → IsLeaderBlock U k L → R.Link i U A L S k → R.Least U A i k L → DecidedWithin U V B k (some L) /-- Anchored below the bound, every rung is empty. -/ | indirectSkip {k j : ℕ} {A : BlockId} : k < j → j < B → R.Eligible k j → DecidedWithin U V B j (some A) → (∀ m, k < m → m < j → R.Eligible k m → DecidedWithin U V B m none) → (∀ i, i < R.rungs → R.RungEmpty U A i k) → DecidedWithin U V B k none ``` **The bounded relation**: `Decided`, with every slot the derivation mentions strictly below `B`. #### `EligibleAt` *def, `Common.Anchored.lean`* ```lean def EligibleAt (wave k j : ℕ) : Prop := S.slotRound k + wave < S.slotRound j ``` `j` may anchor `k` at wave `wave`. #### `AnchoredRule` *structure, `Common.Anchored.lean`* ```lean structure AnchoredRule (Validator : Type*) (BlockId : Type*) (Payload : Type*) (P : Validity Validator BlockId Payload) (honest : Finset Validator) where /-- The rounds a slot's direct rules read above its proposal, less one: an anchor must sit strictly above `slotRound k + wave`. -/ wave : ℕ /-- The direct commit, judged from a view: `Commit U V L r` says the candidate `L` proposed at round `r` is committed by what `V` holds. -/ Commit : (U : BlockRecord Validator BlockId Payload P honest) → U.View → BlockId → ℕ → Prop /-- The direct commit is decidable: a validator computes it from its view, and so does a witness. -/ decCommit : ∀ (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (L : BlockId) (r : ℕ), Decidable (Commit U V L r) /-- The direct skip of a slot, judged from a view. -/ Skip : (U : BlockRecord Validator BlockId Payload P honest) → U.View → Slots Validator → ℕ → Prop /-- The number of rungs of the indirect test. -/ rungs : ℕ /-- Rung `i`: `Link i U A L r` says the anchor `A` links the candidate `L` proposed at round `r`. -/ Link : ℕ → (U : BlockRecord Validator BlockId Payload P honest) → BlockId → BlockId → Slots Validator → ℕ → Prop /-- The tie-break at rung `i`: `tie i L' L` says `L'` is preferred to `L`. Empty where the rung's link is unique per slot. -/ tie : ℕ → BlockId → BlockId → Prop ``` **An anchored rule**: what a leader-based decision rule supplies. #### `Eligible` *abbrev, `Common.Anchored.lean`* ```lean abbrev Eligible (k j : ℕ) : Prop := EligibleAt (S := S) R.wave k j ``` **`j` may anchor `k`**: eligibility at the rule's wave. #### `SpansEligible` *abbrev, `Common.Anchored.lean`* ```lean abbrev SpansEligible (c : ℕ) : Prop := SpansEligibleAt (S := S) R.wave c ``` **A run of `c` slots reaches past everything below it**, at the rule's wave. #### `Decided` *inductive, `Common.Anchored.lean`* ```lean inductive Decided (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) : ℕ → Option BlockId → Prop /-- The direct rule commits a candidate outright. -/ | directCommit {k : ℕ} {L : BlockId} : IsLeaderBlock U k L → R.Commit U V L (S.slotRound k) → Decided U V k (some L) /-- The direct rule skips the slot. -/ | directSkip {k : ℕ} : R.Skip U V S k → Decided U V k none /-- Anchored on the nearest eligible committed slot, every rung below `i` is empty, and `L` is the tie-break's choice among the candidates linked at rung `i`. -/ | indirectCommit {k j : ℕ} {A L : BlockId} {i : ℕ} : k < j → R.Eligible k j → Decided U V j (some A) → (∀ m, k < m → m < j → R.Eligible k m → Decided U V m none) → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → IsLeaderBlock U k L → R.Link i U A L S k → R.Least U A i k L → Decided U V k (some L) /-- Anchored on the nearest eligible committed slot, every rung is empty. -/ | indirectSkip {k j : ℕ} {A : BlockId} : k < j → R.Eligible k j → Decided U V j (some A) → (∀ m, k < m → m < j → R.Eligible k m → Decided U V m none) → (∀ i, i < R.rungs → R.RungEmpty U A i k) → Decided U V k none ``` **`Decided U V k v`** — a validator holding the view `V` has settled slot `k`, committing `v = some L` or skipping it, `v = none`. Undecided is the absence of a derivation. #### `Laws` *structure, `Common.Anchored.lean`* ```lean structure Laws (I : Slots Validator → BlockRecord Validator BlockId Payload P honest → Prop := fun _ _ => True) : Prop where /-- Two direct commits at one slot, from any two views, name one block. -/ commit_unique : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V₁ V₂ : U.View} {k : ℕ} {L₁ L₂ : BlockId}, I S U → IsLeaderBlock U k L₁ → IsLeaderBlock U k L₂ → R.Commit U V₁ L₁ (S.slotRound k) → R.Commit U V₂ L₂ (S.slotRound k) → L₁ = L₂ /-- A direct commit and a direct skip of one slot cannot both hold. -/ commit_skip : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V₁ V₂ : U.View} {k : ℕ} {L : BlockId}, I S U → IsLeaderBlock U k L → R.Commit U V₁ L (S.slotRound k) → R.Skip U V₂ S k → False /-- **Visibility.** A direct commit is linked, at some rung, from any candidate anchor of any eligible slot. -/ commit_link : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {k j : ℕ} {L A : BlockId}, I S U → IsLeaderBlock U k L → R.Commit U V L (S.slotRound k) → IsLeaderBlock U j A → R.Eligible k j → ∃ i, i < R.rungs ∧ R.Link i U A L S k /-- A direct commit and the tie-break's choice at any rung, from any candidate anchor of any eligible slot, are one block. -/ commit_link_unique : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {k j i : ℕ} {L₁ L₂ A : BlockId}, I S U → IsLeaderBlock U k L₁ → IsLeaderBlock U k L₂ → R.Commit U V L₁ (S.slotRound k) → IsLeaderBlock U j A → R.Eligible k j → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → R.Link i U A L₂ S k → R.Least U A i k L₂ → L₁ = L₂ /-- A direct skip excludes every link for the slot's candidates. -/ skip_link : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {k i : ℕ} {L A : BlockId}, I S U → R.Skip U V S k → IsLeaderBlock U k L → i < R.rungs → ¬ R.Link i U A L S k /-- Two tie-break choices at one rung, from one anchor, are one block. -/ link_unique : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {k j i : ℕ} {L₁ L₂ A : BlockId}, I S U → IsLeaderBlock U k L₁ → IsLeaderBlock U k L₂ → IsLeaderBlock U j A → R.Eligible k j → i < R.rungs → (∀ i', i' < i → R.RungEmpty U A i' k) → R.Link i U A L₁ S k → R.Link i U A L₂ S k → R.Least U A i k L₁ → R.Least U A i k L₂ → L₁ = L₂ /-- A larger view can only see more of a direct commit. -/ commit_mono : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V V' : U.View} {L : BlockId} {r : ℕ}, I S U → V.ids ⊆ V'.ids → R.Commit U V L r → R.Commit U V' L r /-- And of a direct skip. -/ skip_mono : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V V' : U.View} {k : ℕ}, I S U → V.ids ⊆ V'.ids → R.Skip U V S k → R.Skip U V' S k /-- The direct skip reads the schedule only at its own slot. -/ skip_congr : ∀ {S₁ S₂ : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {k : ℕ}, I S₁ U → S₁.slotRound k = S₂.slotRound k → S₁.leader k = S₂.leader k → R.Skip U V S₁ k → R.Skip U V S₂ k /-- And so does every rung's link. -/ link_congr : R.LinkCongr ``` **The laws of an anchored rule** — what the direct predicates and the rungs must satisfy for agreement, on the records satisfying an invariant `I` (every record, by default). Every rule proves each of them under its own name. #### `View` *structure, `Common.BlockRecord.lean`* ```lean structure View (U : BlockRecord Validator BlockId Payload P honest) where /-- The ids this validator holds. -/ ids : Finset BlockId /-- A view holds only blocks that exist. -/ subset_ids : ids ⊆ U.ids /-- A view is closed under references. -/ complete : ∀ i ∈ ids, ∀ j ∈ (U.block i).refs, j ∈ ids ``` **A view**: a reference-closed part of the universe. Views share `U.block`, so they disagree about *which* blocks they hold, never about what an id denotes. #### `View.toRecord` *def, `Common.BlockRecord.lean`* ```lean def View.toRecord (V : U.View) : BlockRecord Validator BlockId Payload P honest where ids := V.ids block := U.block complete := V.complete valid := fun i hi => U.valid i (V.subset_ids hi) no_equivocation := fun i hi j hj => U.no_equivocation i (V.subset_ids hi) j (V.subset_ids hj) ``` **A view is a record.** Its ids under the universe's block map: closure is the view's, validity and non-equivocation are inherited, since the block map is unchanged. This is what a rule evaluates its rules on when it reads a view as a DAG in its own right. #### `View.full` *def, `Common.BlockRecord.lean`* ```lean def View.full (U : BlockRecord Validator BlockId Payload P honest) : U.View := ⟨U.ids, Finset.Subset.rfl, U.complete⟩ ``` **The full view**: every block of the record. Every honest validator's eventual view, downward-closed by `U.complete`. #### `View.CoversUpto` *def, `Common.BlockRecord.lean`* ```lean def View.CoversUpto (V : U.View) (N : ℕ) : Prop := ∀ b ∈ U.ids, (U.block b).round ≤ N → b ∈ V.ids ``` **A view caught up to round `N`**: it holds every block of the record at a round at or below `N`. What a validator that has received everything up to `N` holds, and the hypothesis under which a liveness result holds of a validator's own view rather than of the full view. #### `chopBlk` *def, `Common.BlockRecord.lean`* ```lean def chopBlk (blk : BlockId → Block Validator BlockId Payload) (G : ℕ) (i : BlockId) : Block Validator BlockId Payload := if (blk i).round ≤ G then { blk i with round := (blk i).round - G, refs := ∅ } else { blk i with round := (blk i).round - G } ``` One block of the truncation, over the raw block assignment. #### `Mechanised` *class, `Common.BlockRecord.lean`* ```lean class Mechanised : Prop where /-- References sit one round below. -/ pred : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), P blk b → ∀ j ∈ b.refs, (blk j).round + 1 = b.round /-- Validity reads only referenced blocks: two maps agreeing on a reference-closed set holding `b`'s references judge `b` alike. -/ reads : ∀ (blk blk' : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (b : Block Validator BlockId Payload), (∀ i ∈ ids, ∀ j ∈ (blk i).refs, j ∈ ids) → (∀ j ∈ b.refs, j ∈ ids) → (∀ j ∈ ids, blk' j = blk j) → P blk b → P blk' b /-- A reference-free block at round zero is valid. -/ base : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), b.round = 0 → b.refs = ∅ → P blk b /-- Validity survives the cut strictly above the horizon. -/ chops : ∀ (blk : BlockId → Block Validator BlockId Payload) (G : ℕ) (b : Block Validator BlockId Payload), P blk b → G < b.round → P (chopBlk blk G) { b with round := b.round - G } ``` **The four facts the mechanisms consume of a validity predicate.** Every predicate in the development has them; a rule proves them once and its cut, fill and re-genesis are the generic constructions. #### `CopyStable` *class, `Common.BlockRecord.lean`* ```lean class CopyStable : Prop where copy : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) (v : Validator), P blk b → P blk { b with creator := v } ``` **The author is not read.** What the copy fill needs: a rule with no self-parent clause judges a re-authored block as it judged the original. #### `ValidAt` *structure, `Common.BlockRecord.lean`* ```lean structure ValidAt [DecidableEq Validator] (q : ℕ) (C : Clause Validator BlockId Payload) (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : Prop where /-- Every reference sits in the immediately preceding round. -/ predecessor : ∀ i ∈ b.refs, (blk i).round + 1 = b.round /-- Non-genesis blocks reference `q` distinct creators. -/ quorum : 0 < b.round → q ≤ (creators blk b).card /-- The rule's own clause. -/ clause : C blk b ``` **The common shape of every validity predicate**, at threshold `q` with clause `C`. #### `Clause.Mechanised` *class, `Common.BlockRecord.lean`* ```lean class Clause.Mechanised (C : Clause Validator BlockId Payload) : Prop where reads : ∀ (blk blk' : BlockId → Block Validator BlockId Payload) (ids : Finset BlockId) (b : Block Validator BlockId Payload), (∀ i ∈ ids, ∀ j ∈ (blk i).refs, j ∈ ids) → (∀ j ∈ b.refs, j ∈ ids) → (∀ j ∈ ids, blk' j = blk j) → C blk b → C blk' b base : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), b.round = 0 → b.refs = ∅ → C blk b chops : ∀ (blk : BlockId → Block Validator BlockId Payload) (G : ℕ) (b : Block Validator BlockId Payload), C blk b → (∀ i ∈ b.refs, (blk i).round + 1 = b.round) → G < b.round → C (chopBlk blk G) { b with round := b.round - G } ``` **What a clause owes**: the three facts of `Validity.Mechanised` that concern it, the predecessor fact being the family's. #### `Clause.CopyStable` *class, `Common.BlockRecord.lean`* ```lean class Clause.CopyStable (C : Clause Validator BlockId Payload) : Prop where copy : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) (v : Validator), C blk b → C blk { b with creator := v } ``` The clause does not read the creator. #### `none` *def, `Common.BlockRecord.lean`* ```lean def none : Clause Validator BlockId Payload := fun _ _ => True ``` No clause. #### `distinct` *def, `Common.BlockRecord.lean`* ```lean def distinct : Clause Validator BlockId Payload := fun blk b => ∀ i ∈ b.refs, ∀ j ∈ b.refs, (blk i).creator = (blk j).creator → i = j ``` No two references share a creator. #### `selfParent` *def, `Common.BlockRecord.lean`* ```lean def selfParent : Clause Validator BlockId Payload := fun blk b => 0 < b.round → ∃ i ∈ b.refs, (blk i).creator = b.creator ``` A non-genesis block references a block by its own creator. Read by the core; not `CopyStable`, which is why the core's fill adds a self reference. #### `and` *def, `Common.BlockRecord.lean`* ```lean def and (C D : Clause Validator BlockId Payload) : Clause Validator BlockId Payload := fun blk b => C blk b ∧ D blk b ``` Two clauses together. #### `Quorate` *class, `Common.BlockRecord.lean`* ```lean class Quorate [DecidableEq Validator] (P : Validity Validator BlockId Payload) (q : outParam ℕ) : Prop where quorum : ∀ (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload), P blk b → 0 < b.round → q ≤ (creators blk b).card pos : 0 < q ``` Non-genesis blocks reference `q` distinct creators, and `q` is positive, so a non-genesis block references something. #### `commitSeq` *def, `Common.Ledger.lean`* ```lean def commitSeq (g : ℕ → Option BlockId) (n : ℕ) : List BlockId := (List.range n).filterMap g ``` The blocks committed at slots `0, …, n-1`, in slot order, with skipped slots dropped. `g` is a validator's verdict assignment. #### `ledgerSet` *def, `Common.Ledger.lean`* ```lean def ledgerSet (U : BlockRecord Validator BlockId Payload P honest) (g : ℕ → Option BlockId) (n : ℕ) : Set BlockId := {b | ∃ k, k < n ∧ ∃ L, g k = some L ∧ Reaches U L b} ``` The blocks output after settling slots `0, …, n-1`: everything in the causal history of a committed leader. #### `OutputAt` *def, `Common.Ledger.lean`* ```lean def OutputAt (U : BlockRecord Validator BlockId Payload P honest) (g : ℕ → Option BlockId) (b : BlockId) (k : ℕ) : Prop := (∃ L, g k = some L ∧ Reaches U L b) ∧ ∀ j, j < k → ∀ L, g j = some L → ¬ Reaches U L b ``` `b` enters the ledger at slot `k`: the first committed slot whose leader reaches it. #### `chop` *def, `Common.Record.Chop.lean`* ```lean def chop (U : BlockRecord Validator BlockId Payload P honest) (G : ℕ) : BlockRecord Validator BlockId Payload P honest where ids := U.ids.filter fun i => G ≤ (U.block i).round block := chopBlk U.block G complete := by intro i hi j hj rw [Finset.mem_filter] at hi rcases Nat.lt_or_ge G (U.block i).round with h | h · rw [chopBlk_refs_of_lt h] at hj have hjr := Validity.Mechanised.pred U.block (U.block i) (U.valid i hi.1) j hj exact Finset.mem_filter.mpr ⟨U.complete i hi.1 j hj, by omega⟩ · rw [chopBlk_refs_of_le h] at hj exact absurd hj (Finset.notMem_empty j) valid := by intro i hi rw [Finset.mem_filter] at hi rcases Nat.lt_or_ge G (U.block i).round with h | h · rw [chopBlk_of_lt h] exact Validity.Mechanised.chops U.block G (U.block i) (U.valid i hi.1) h · apply Validity.Mechanised.base · rw [chopBlk_round]; omega · exact chopBlk_refs_of_le h no_equivocation := by intro i hi j hj hic hcc hrr rw [Finset.mem_filter] at hi hj simp only [chopBlk_creator, chopBlk_round] at hic hcc hrr exact U.no_equivocation i hi.1 j hj.1 hic hcc (by omega) ``` **The cut.** #### `View.chop` *def, `Common.Record.Chop.lean`* ```lean def View.chop (V : U.View) (G : ℕ) : (U.chop G).View where ids := V.ids.filter fun i => G ≤ (U.block i).round subset_ids := Finset.filter_subset_filter _ V.subset_ids complete := by intro i hi j hj rw [Finset.mem_filter] at hi change j ∈ (chopBlk U.block G i).refs at hj rcases Nat.lt_or_ge G (U.block i).round with h | h · rw [chopBlk_refs_of_lt h] at hj have hjr := Validity.Mechanised.pred U.block (U.block i) (U.valid i (V.subset_ids hi.1)) j hj exact Finset.mem_filter.mpr ⟨V.complete i hi.1 j hj, by omega⟩ · rw [chopBlk_refs_of_le h] at hj exact absurd hj (Finset.notMem_empty j) ``` **A view, truncated at the horizon**: keep what clears the cut. #### `addGenesis` *def, `Common.Record.Genesis.lean`* ```lean def addGenesis (U : BlockRecord Validator BlockId Payload P honest) (v : Validator) (g : BlockId) (p : Payload) (hg : g ∉ U.ids) (hsev : ∀ b ∈ U.ids, (U.block b).creator ≠ v) : BlockRecord Validator BlockId Payload P honest where ids := insert g U.ids block b := if b ∈ U.ids then U.block b else ⟨0, v, ∅, p⟩ complete := by intro i hi j hj rcases Finset.mem_insert.mp hi with rfl | ho · rw [if_neg hg] at hj exact absurd hj (Finset.notMem_empty j) · rw [if_pos ho] at hj exact Finset.mem_insert_of_mem (U.complete i ho j hj) valid := by intro i hi rcases Finset.mem_insert.mp hi with rfl | ho · rw [if_neg hg] exact Validity.Mechanised.base _ _ rfl rfl · rw [if_pos ho] exact Validity.Mechanised.reads U.block _ U.ids (U.block i) U.complete (U.complete i ho) (fun j hj => if_pos hj) (U.valid i ho) no_equivocation := by intro i hi j hj hic hcc hrr rcases Finset.mem_insert.mp hi with rfl | ho <;> rcases Finset.mem_insert.mp hj with rfl | ho' · rfl · rw [if_neg hg, if_pos ho'] at hcc exact absurd hcc.symm (hsev j ho') · rw [if_pos ho, if_neg hg] at hcc exact absurd hcc (hsev i ho) · rw [if_pos ho] at hic hcc hrr rw [if_pos ho'] at hcc hrr exact U.no_equivocation i ho j ho' hic hcc hrr ``` **Re-genesis.** #### `Invariant.Mechanised` *class, `Common.Record.Invariant.lean`* ```lean class Invariant.Mechanised [P.Mechanised] (I : BlockRecord Validator BlockId Payload P honest → Prop) : Prop where /-- It survives the cut. -/ chop : ∀ {W : BlockRecord Validator BlockId Payload P honest} (G : ℕ), I W → I (W.chop G) /-- It survives the copy fill. -/ copyFill : ∀ [P.CopyStable] {W : BlockRecord Validator BlockId Payload P honest} (sk : SkipData W.ids W.block), I W → I (BlockRecord.copyFill W sk) /-- It survives re-genesis. -/ addGenesis : ∀ {W : BlockRecord Validator BlockId Payload P honest} (v : Validator) (g : BlockId) (p : Payload) (hg : g ∉ W.ids) (hsev : ∀ b ∈ W.ids, (W.block b).creator ≠ v), I W → I (W.addGenesis v g p hg hsev) ``` **What an invariant owes the mechanisms.** #### `supportCommit` *abbrev, `Common.Rules.lean`* ```lean abbrev supportCommit (t : ℕ) (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V t (votesFor U L (r + 1)) ``` **Commit by support**: the view holds `t` authors voting for the candidate one round above it. #### `certCommit` *abbrev, `Common.Rules.lean`* ```lean abbrev certCommit (Vote : (U : BlockRecord Validator BlockId Payload P honest) → BlockId → BlockId → Prop) [∀ U b L, Decidable (Vote U b L)] (t t' off : ℕ) (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V t (certificatesAt U (Vote U) t' L (r + off)) ``` **Commit by certificate**: the view holds `t` authors of certificates for the candidate — the blocks `off` rounds above it carrying `t'` votes for it under the vote relation `Vote`. #### `blameSkip` *abbrev, `Common.Rules.lean`* ```lean abbrev blameSkip [S : Slots Validator] (t : ℕ) (U : BlockRecord Validator BlockId Payload P honest) (V : U.View) (k : ℕ) : Prop := HoldsAtLeast U V t (slotBlamers U k) ``` **Skip by blame**: the view holds `t` authors whose voting-round block references no candidate of the slot. #### `certifiedLink` *abbrev, `Common.Rules.lean`* ```lean abbrev certifiedLink (Vote : (U : BlockRecord Validator BlockId Payload P honest) → BlockId → BlockId → Prop) [∀ U b L, Decidable (Vote U b L)] (t' off : ℕ) (U : BlockRecord Validator BlockId Payload P honest) (A L : BlockId) (r : ℕ) : Prop := LinkedVia U A (certificatesAt U (Vote U) t' L (r + off)) ``` **Link by certificate**: a certificate for the candidate lies in the anchor's cone. #### `coneLink` *abbrev, `Common.Rules.lean`* ```lean abbrev coneLink (t : ℕ) (U : BlockRecord Validator BlockId Payload P honest) (A L : BlockId) (r : ℕ) : Prop := t ≤ (coneSupporters U A L (r + 1)).card ``` **Link by support in the cone**: `t` authors of votes for the candidate lie in the anchor's cone. #### `Slots` *class, `Common.Slots.lean`* ```lean class Slots (Validator : Type*) where /-- The round at which slot `k` is proposed. -/ slotRound : ℕ → ℕ /-- The validator whose block is the slot-`k` candidate. -/ leader : ℕ → Validator /-- Slots are enumerated in round order. -/ mono : Monotone slotRound /-- Slot rounds are unbounded. -/ unbounded : ∀ n, ∃ k, n ≤ slotRound k /-- Distinct slots differ in round or in leader. -/ keyed : Function.Injective (fun k => (slotRound k, leader k)) ``` The leader schedule: which validator proposes at which round, as a sequence of slots. Slots need not be three rounds apart — under pipelining they are one round apart, and under multiple leaders per round they share one — so `slotRound` need only be monotone, and the separation M4's commit half needs is required instead at `Eligible` below. `unbounded` is assumed, not derivable from `mono` alone. `keyed` is a real condition once several leaders share a round: without it one block would be the candidate for two slots, and the ledger would deliver it twice. #### `FairRunOn` *def, `Common.Slots.lean`* ```lean def FairRunOn {Validator : Type*} [S : Slots Validator] (T : Finset Validator) (c : ℕ) : Prop := ∀ k, ∃ k', k ≤ k' ∧ ∀ i, i < c → S.leader (k' + i) ∈ T ``` **A fair schedule offers runs**: past any slot, `c` consecutive `T`-led slots. An assumption about the schedule, not a theorem: `leader` may name faulty validators for ever. #### `FairScheduleOn` *def, `Common.Slots.lean`* ```lean def FairScheduleOn {Validator : Type*} [S : Slots Validator] (T : Finset Validator) : Prop := ∀ k, ∃ k', k ≤ k' ∧ S.leader k' ∈ T ``` The schedule names a correct leader arbitrarily far out. Without it no recurrence statement holds: `Slots.leader` is an arbitrary function and could name Byzantine validators forever, however synchronous the network. #### `FairToEach` *def, `Common.Slots.lean`* ```lean def FairToEach {Validator : Type*} [S : Slots Validator] (T : Finset Validator) : Prop := ∀ v ∈ T, ∀ k, ∃ k', k ≤ k' ∧ S.leader k' = v ``` **Every member of `T` leads arbitrarily far out** — per-validator fairness, strictly stronger than `FairScheduleOn`. Round-robin supplies it, and the rotation-inclusion result of report §11.5 consumes it: a straggler's block enters the ledger when its own author leads. #### `BoundedSpacing` *def, `Common.Slots.lean`* ```lean def BoundedSpacing {Validator : Type*} [S : Slots Validator] (s : ℕ) : Prop := ∀ k, S.slotRound (k + 1) ≤ S.slotRound k + s ``` Consecutive slots are at most `s` rounds apart — the upper companion to such a field. Every real schedule has one; the class omits it because no safety result ever asks. #### `uniform` *def, `Common.Slots.lean`* ```lean def uniform (p m : ℕ) (hp : 0 < p) (hm : 0 < m) (elect : ℕ → Validator) (hblock : ∀ k₁ k₂, k₁ / m = k₂ / m → elect k₁ = elect k₂ → k₁ = k₂) : Slots Validator where slotRound k := p * (k / m) leader k := elect k mono := fun _ _ hab => Nat.mul_le_mul_left p (Nat.div_le_div_right hab) unbounded := fun n => ⟨m * n, by rw [Nat.mul_div_cancel_left n hm] exact Nat.le_mul_of_pos_left n hp⟩ keyed := by intro k₁ k₂ h simp only [Prod.mk.injEq] at h exact hblock k₁ k₂ (Nat.eq_of_mul_eq_mul_left hp h.1) h.2 ``` **The uniform schedule**: `m` leaders in every `p`-th round, slot `k` proposed by `elect k`. `hblock` is the one real condition — the `m` proposers of a round are distinct validators — which round-robin satisfies whenever `m ≤ n`. #### `uniformSingle` *def, `Common.Slots.lean`* ```lean def uniformSingle (p : ℕ) (hp : 0 < p) (elect : ℕ → Validator) : Slots Validator := uniform p 1 hp Nat.one_pos elect (one_hblock elect) ``` **One leader every `p` rounds.** `p = 3` is the schedule the development had before pipelining; `p = 1` is pipelined single-leader. #### `Slots.identity` *def, `Common.Slots.lean`* ```lean def Slots.identity {Validator : Type*} (leader : ℕ → Validator) : Slots Validator := ⟨id, leader, fun _ _ h => h, fun n => ⟨n, le_rfl⟩, fun _ _ h => congrArg Prod.fst h⟩ ``` **The identity schedule** with a given leader map: one slot per round. The three laws are immediate. #### `waveRobin` *def, `Common.Slots.lean`* ```lean def waveRobin (n : ℕ) (hn : 0 < n) : Slots (Fin n) where slotRound k := k leader k := ⟨k / 3 % n, Nat.mod_lt _ hn⟩ mono := fun _ _ h => h unbounded := fun m => ⟨m, le_refl m⟩ keyed := fun _ _ h => congrArg Prod.fst h ``` **The wave-aligned round-robin schedule** on `n` validators: pipelined, with the leader holding for a whole wave of three slots before the rotation advances. A `def`, not an `instance` — a second `Slots` instance would make synthesis ambiguous — so every use passes `(S := waveRobin n hn)` explicitly. #### `Decided` *abbrev, `FinWhale.Model.Decided.lean`* ```lean abbrev Decided (D : Dag Validator BlockId Payload) (V : D.View) : ℕ → Option BlockId → Prop := (finWhaleAnchored Validator BlockId Payload).Decided (S := S) D V ``` The verdicts a validator holding view `V` may reach on slot `k`. #### `Run.ofDoSValid` *def, `FinWhale.Procedure.DoSBridge.lean`* ```lean def Run.ofDoSValid [LinearOrder BlockId] (U : BlockUniverse Validator BlockId Payload) (leader : ℕ → Validator) (hdos : DoSValid U) (horizon : ℕ) (rounds_le : ∀ b ∈ U.ids, (U.block b).round ≤ horizon) (paceHorizon : ℕ) (pace : PaceCore U (Correct : Finset Validator) paceHorizon) (rounds_advance : ∀ u ∈ (Correct : Finset Validator), ∀ n ≤ pace.top u, n ≤ pace.built u n) (stable : ℕ) (gst_le : pace.gst ≤ stable) (liveHorizon : ℕ) (commits : CommitsCorrectLeaders (Slots.identity leader) (Dag.ofDoSValid U leader hdos) stable liveHorizon) (live_le : liveHorizon ≤ paceHorizon) (roundRobin : RoundRobin leader) : Run Validator BlockId Payload where dag := Dag.ofDoSValid U leader hdos sched := (Slots.identity leader) roundId := fun _ => rfl paced := U ids_eq := rfl block_eq := rfl horizon := horizon rounds_le := rounds_le paceHorizon := paceHorizon pace := pace rounds_advance := rounds_advance stable := stable gst_le := gst_le liveHorizon := liveHorizon commits := commits live_le := live_le roundRobin := roundRobin selfParented := selfParented_ofDoSValid hdos ``` **A run over a DoS-valid universe**: the same data a `Run` asks for, less three fields the identification with the universe discharges. #### `Run.ofDoSValidReactive` *def, `FinWhale.Procedure.DoSBridge.lean`* ```lean noncomputable def Run.ofDoSValidReactive [LinearOrder BlockId] (U : BlockUniverse Validator BlockId Payload) (hdos : DoSValid U) {N : ℕ} (rm : ReactiveM U (Correct : Finset Validator) N) (hround : ∀ k, S.slotRound k = k) (hrr : RoundRobin S.leader) (horizon : ℕ) (rounds_le : ∀ b ∈ U.ids, (U.block b).round ≤ horizon) (rounds_advance : ∀ u ∈ (Correct : Finset Validator), ∀ n ≤ rm.top u, n ≤ rm.built u n) (stable : ℕ) (hgst : rm.gst ≤ stable) (hto : ∀ n, stable ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) : Run Validator BlockId Payload := Run.ofDoSValid U S.leader hdos horizon rounds_le N rm.toPaceCore rounds_advance stable hgst N (commits_of_reactive rm rfl rfl hround (fun _ => rfl) (fun _ => rfl) rfl hgst hto) (le_refl N) hrr ``` **A DoS-valid universe on the reactive schedule is a run**: the pace is the reactive one and the liveness input is `commits_of_reactive`, with four of `Run`'s fields discharged by identification with the universe. #### `commitSeq` *def, `FinWhale.Procedure.Model.Order.lean`* ```lean def commitSeq (dec : ℕ → Verdict BlockId) : ℕ → List BlockId | 0 => [] | k + 1 => commitSeq dec k ++ (match dec k with | Verdict.commit b => [b] | Verdict.skip => [] | Verdict.undecided => []) ``` **The committed leader sequence** of the first `k` slots. #### `linearise` *def, `FinWhale.Procedure.Model.Order.lean`* ```lean def linearise (hist : BlockId → List BlockId) (ls : List BlockId) : List BlockId := ls.foldl (fun acc l => acc ++ (hist l).filter (fun b => b ∉ acc)) [] ``` **The delivery order.** Each committed leader contributes the blocks of its causal history that no earlier leader delivered. #### `histOf` *def, `FinWhale.Procedure.Model.Order.lean`* ```lean def histOf [LinearOrder BlockId] (D : Dag Validator BlockId Payload) (l : BlockId) : List BlockId := (historyFrom D.block l).sort (· ≤ ·) ``` **The delivery order's input, concretely.** A leader contributes its causal history, computed by `historyFrom` and listed in the identifier order — the cheapest deterministic sort that lists each block once, and all that Theorems 24 and 26 read of it. #### `slotVerdict` *def, `FinWhale.Procedure.Model.Pass.lean`* ```lean def slotVerdict (S : Slots Validator) (Elig : ℕ → ℕ → Prop) [DecidableRel Elig] (D : Dag Validator BlockId Payload) (choose : BlockId → ℕ → Option BlockId) (N : ℕ) (above : ℕ → Verdict BlockId) (r : ℕ) : Verdict BlockId := if h : (directCommits S D r).Nonempty then Verdict.commit ((directCommits S D r).min' h) else if DirectSkip S D r then Verdict.skip else anchorVerdict Elig choose N above r ``` **One slot's verdict, from the verdicts above it.** #### `passFrom` *def, `FinWhale.Procedure.Model.Pass.lean`* ```lean def passFrom (S : Slots Validator) (Elig : ℕ → ℕ → Prop) [DecidableRel Elig] (D : Dag Validator BlockId Payload) (choose : BlockId → ℕ → Option BlockId) (N : ℕ) (s : ℕ) : ℕ → Verdict BlockId := if h : N < s then fun _ => Verdict.undecided else fun r => if r = s then slotVerdict S Elig D choose N (passFrom S Elig D choose N (s + 1)) s else passFrom S Elig D choose N (s + 1) r termination_by N + 1 - s decreasing_by all_goals omega ``` **The pass, from slot `s` downward.** Slots below `s` are left undecided; slot `s` is decided from the verdicts above it, and those are what the pass from `s + 1` gives. #### `decOf` *def, `FinWhale.Procedure.Model.Pass.lean`* ```lean def decOf (S : Slots Validator) (Elig : ℕ → ℕ → Prop) [DecidableRel Elig] (D : Dag Validator BlockId Payload) (choose : BlockId → ℕ → Option BlockId) (N : ℕ) : ℕ → Verdict BlockId := passFrom S Elig D choose N 0 ``` **The verdicts of a validator whose view is `D`.** #### `Run` *structure, `FinWhale.Procedure.Model.Protocol.lean`* ```lean structure Run (Validator BlockId Payload : Type) [Fintype Validator] [DecidableEq Validator] [Faults Validator] [Params Validator] [DecidableEq BlockId] [LinearOrder BlockId] where /-- Every block any correct validator holds. -/ dag : Dag Validator BlockId Payload /-- The same blocks, as the pacing line reads them. -/ paced : BlockUniverse Validator BlockId Payload /-- The two readings describe the same blocks. -/ ids_eq : dag.ids = paced.ids /-- And denote them the same way. -/ block_eq : dag.block = paced.block /-- No block sits above this round. -/ horizon : ℕ /-- Which is a horizon. -/ rounds_le : ∀ b ∈ dag.ids, (dag.block b).round ≤ horizon /-- The rounds the schedule reaches. -/ paceHorizon : ℕ /-- The schedule and the network. -/ pace : PaceCore paced (Correct : Finset Validator) paceHorizon /-- Rounds advance real time. -/ rounds_advance : ∀ u ∈ (Correct : Finset Validator), ∀ n ≤ pace.top u, n ≤ pace.built u n /-- The leader schedule the execution runs. It belongs to the execution rather than to the DAG: a DAG is blocks, and a schedule is not. -/ sched : Slots Validator /-- FinWhale runs one slot per round, which is what the reverse pass enumerates. -/ roundId : ∀ k, sched.slotRound k = k /-- The network has stabilised by this round. -/ stable : ℕ /-- Which is past GST. -/ gst_le : pace.gst ≤ stable /-- Below this round the liveness argument applies. -/ liveHorizon : ℕ /-- Every correct-led slot below it carries a commit — the input §10 supplies, by either route. -/ commits : CommitsCorrectLeaders sched dag stable liveHorizon /-- The schedule reaches that far. -/ live_le : liveHorizon ≤ paceHorizon /-- Leaders rotate. -/ roundRobin : RoundRobin sched.leader /-- Every block references its author's previous block. -/ selfParented : SelfParented dag ``` **A run of FinWhale.** The blocks every correct validator ever holds, the schedule and network that carried them, and the rotation that names leaders. #### `WellFormed` *structure, `FinWhale.Procedure.Model.Verdict.lean`* ```lean structure WellFormed (Elig : ℕ → ℕ → Prop) (dcommit : ℕ → BlockId → Prop) (dskip : ℕ → Prop) (choose : BlockId → ℕ → Option BlockId) (dec : ℕ → Verdict BlockId) : Prop where /-- A direct commit is taken. -/ direct_commit : ∀ r l, dcommit r l → dec r = Verdict.commit l /-- A direct skip is taken. -/ direct_skip : ∀ r, dskip r → dec r = Verdict.skip /-- Undecided where the anchor is undecided. -/ indirect_undecided : ∀ r a, (¬ ∃ l, dcommit r l) → ¬ dskip r → Anchor Elig dec r a → dec a = Verdict.undecided → dec r = Verdict.undecided /-- Decided by the anchor otherwise. -/ indirect_commit : ∀ r a A, (¬ ∃ l, dcommit r l) → ¬ dskip r → Anchor Elig dec r a → dec a = Verdict.commit A → dec r = (match choose A r with | some b => Verdict.commit b | none => Verdict.skip) /-- A slot decided without a direct rule was decided from an anchor. -/ has_anchor : ∀ r, (¬ ∃ l, dcommit r l) → ¬ dskip r → dec r ≠ Verdict.undecided → ∃ a, Anchor Elig dec r a ``` **The reverse pass, as a condition on the verdicts.** The direct rules are parameters, since each validator evaluates them on its own view; `choose` is shared, reading only the anchor and the round. #### `ChooseSound` *structure, `FinWhale.Procedure.Model.Verdict.lean`* ```lean structure ChooseSound (S : Slots Validator) (D : Dag Validator BlockId Payload) (choose : BlockId → ℕ → Option BlockId) : Prop where /-- Whatever it names is a candidate. -/ sound : ∀ A r b, choose A r = some b → IndirectCommit S D A r b /-- Where there is a candidate, it names one. -/ total : ∀ A r, (∃ b, IndirectCommit S D A r b) → ∃ b, choose A r = some b ``` **What the deterministic rule must satisfy**: it names only blocks the anchor could indirectly commit, and names one whenever there is one to name. #### `chooseLeast` *def, `FinWhale.Procedure.Model.Verdict.lean`* ```lean noncomputable def chooseLeast [LinearOrder BlockId] (S : Slots Validator) (D : Dag Validator BlockId Payload) (A : BlockId) (r : ℕ) : Option BlockId := if h : ((slotBlocks S D r).filter (fun b => IndirectCommit S D A r b)).Nonempty then some (((slotBlocks S D r).filter (fun b => IndirectCommit S D A r b)).min' h) else none ``` **The deterministic rule, exhibited**: the least candidate in the identifier order, sound and total by construction, and a function of the anchor and the round alone, so two validators holding the same anchor make the same choice. #### `hzSupport` *def, `Hydrozoan.Helpers.Commit.lean`* ```lean def hzSupport : Support (rule (Replica := Replica) (BlockId := BlockId)) where wave := 2 Certifies := fun U C L => LeanDag.Hydrozoan.IsCertificate U C L ``` **Hydrozoan's support**: wavelength two, certification the rule's own. #### `CertifiedIn` *abbrev, `Mysticeti.Model.Rule.lean`* ```lean abbrev CertifiedIn (U : BlockUniverse Validator BlockId Payload) (A L : BlockId) (r : ℕ) : Prop := certifiedLink IsVote (quorumCard Validator) 2 U A L r ``` The indirect rule's test: a certificate for `L` lies in the causal history of the anchor `A`. #### `DirectCommitIn` *abbrev, `Mysticeti.Model.Rule.lean`* ```lean abbrev DirectCommitIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := certCommit IsVote (quorumCard Validator) (quorumCard Validator) 2 U V L r ``` Direct commit, as judged from a single view: the view holds certificates for `L` from a quorum of distinct validators. #### `DirectSkipIn` *abbrev, `Mysticeti.Model.Rule.lean`* ```lean abbrev DirectSkipIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (L : BlockId) (r : ℕ) : Prop := HoldsAtLeast U V (quorumCard Validator) (omissionsOf U L (r + 1)) ``` Direct skip, as judged from a single view: the view holds blocks at the round above `L` that omit it, from a quorum of distinct validators. #### `DirectSkipSlotIn` *abbrev, `Mysticeti.Model.Rule.lean`* ```lean abbrev DirectSkipSlotIn (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (k : ℕ) : Prop := blameSkip (quorumCard Validator) U V k ``` **The slot is directly skipped, as judged from a view**: a quorum of distinct validators holds a voting-round block, in view, that references no candidate of the slot. Strictly stronger than the per-candidate `DirectSkipIn`, which it implies. #### `coreAnchored` *def, `Mysticeti.Model.Rule.lean`* ```lean def coreAnchored (Validator BlockId Payload : Type*) [Fintype Validator] [DecidableEq Validator] [Faults Validator] [DecidableEq BlockId] : AnchoredRule Validator BlockId Payload ValidWrt Correct where wave := 2 Commit := fun U V L r => DirectCommitIn U V L r decCommit := fun _ _ _ _ => inferInstance Skip := fun U V S k => DirectSkipSlotIn (S := S) U V k rungs := 1 Link := fun _ U A L S k => CertifiedIn U A L (S.slotRound k) tie := fun _ _ _ => False ``` **The core as an anchored rule.** #### `Decided` *abbrev, `Mysticeti.Model.Rule.lean`* ```lean abbrev Decided (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) : ℕ → Option BlockId → Prop := (coreAnchored Validator BlockId Payload).Decided (S := S) U V ``` **The decision relation**: the anchored relation at the core's data. #### `mysticetiRule` *def, `Mysticeti.Properties.lean`* ```lean def mysticetiRule : DagRule Validator BlockId Payload := (coreAnchored Validator BlockId Payload).toDagRule ``` **The core rule as a carrier.** #### `DecidedWithin` *abbrev, `Mysticeti.Properties.lean`* ```lean abbrev DecidedWithin (U : BlockUniverse Validator BlockId Payload) (V : View Validator BlockId Payload U) (B : ℕ) : ℕ → Option BlockId → Prop := (coreAnchored Validator BlockId Payload).DecidedWithin (S := S) U V B ``` **The bounded decision relation**, at the core. #### `certLive` *def, `Mysticeti.Properties.lean`* ```lean def certLive (S : Slots Validator) {U : BlockUniverse Validator BlockId Payload} (V : View Validator BlockId Payload U) (T : Finset Validator) (lo K : ℕ) : Prop := quorumCard Validator ≤ T.card ∧ ∃ N, V.CoversUpto N ∧ (∀ k, k < K → S.slotRound k + 2 ≤ N) ∧ ∀ k, lo ≤ k → k < K → S.leader k ∈ T → PopulatedOn U T (S.slotRound k) ∧ PopulatedOn U T (S.slotRound k + 2) ∧ ∀ L, IsLeaderBlock (S := S) U k L → CertifiesAt U T (S.slotRound k) L ``` **The core's precondition, in what its commit rule counts.** A quorum, a horizon the view is caught up to with every slot two rounds under it, production at the propose and certificate rounds, and `T` certifying every candidate of every `T`-led slot in the window. #### `Populated` *abbrev, `Network.Delivery.lean`* ```lean abbrev Populated (U : BlockUniverse Validator BlockId Payload) (r : ℕ) : Prop := PopulatedOn U (Correct : Finset Validator) r ``` The all-of-`Correct` case, which is what L1 produces. #### `Delivery` *structure, `Network.Delivery.lean`* ```lean structure Delivery (U : BlockUniverse Validator BlockId Payload) where /-- What `v` held from round `n` when it built its round-`(n+1)` block. -/ held : Validator → ℕ → Finset BlockId /-- Held ids are real blocks of the stated round — what keeps `Delivery` meaningful, since without it `held` could be junk and `includes` would demand blocks reference it. -/ held_spec : ∀ v n, ∀ i ∈ held v n, i ∈ U.ids ∧ (U.block i).round = n /-- What `v` chose to build on: a subset of what it held. -/ accepted : Validator → ℕ → Finset BlockId /-- You can only accept what arrived. -/ accepted_sub : ∀ v n, accepted v n ⊆ held v n /-- **The acceptance rule**: at most one block per author. Forced by `distinct_creators` — a validator holding two blocks by one author must pick one, because it cannot reference both. -/ accepted_inj : ∀ v n, ∀ i ∈ accepted v n, ∀ j ∈ accepted v n, (U.block i).creator = (U.block j).creator → i = j /-- A correct block is always accepted. It never conflicts with anything — its author has only the one block for that round (T1) — so nothing is ever given up by taking it, and L7 needs it. -/ accepts_correct : ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ a ∈ held v n, (U.block a).creator ∈ (Correct : Finset Validator) → a ∈ accepted v n /-- **The protocol rule.** A correct validator references everything it accepted. Implementable and observable — unlike `Synchronised` itself. -/ includes : ∀ v ∈ (Correct : Finset Validator), ∀ n, ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round = n + 1 → accepted v n ⊆ (U.block b).refs ``` What each validator had in hand, one round at a time, and which of it it chose to build on. Two fields because delivery and policy are two things: a structure demanding a correct validator reference everything it `held` would be unsatisfiable the moment it holds both halves of an equivocation, since `distinct_creators` forbids referencing both (`dos-equivocation-and-growth.md` §4). `held` is not deduplicated, since `U` is every block some correct validator held. #### `Synchronised` *abbrev, `Network.Delivery.lean`* ```lean abbrev Synchronised (U : BlockUniverse Validator BlockId Payload) (R : ℕ) : Prop := SynchronisedOn U (Correct : Finset Validator) R ``` The all-of-`Correct` case. #### `EventuallyDelivers` *def, `Network.Delivery.lean`* ```lean def EventuallyDelivers (D : Delivery U) (R : ℕ) : Prop := ∀ n, R ≤ n → ∀ v ∈ (Correct : Finset Validator), ∀ a ∈ U.ids, (U.block a).round = n → (U.block a).creator ∈ (Correct : Finset Validator) → a ∈ D.held v n ``` **The network assumption**: after `R`, correct blocks reach correct validators in time to be built on. This is eventual DAG synchrony proper — pure delivery, no protocol content. #### `optSupport` *def, `OptimalHydrozoan.Carrier.lean`* ```lean def optSupport : Support (optimalRule (Replica := Replica) (BlockId := BlockId)) where wave := 2 Certifies := fun U C L => LeanDag.Hydrozoan.IsCertificate U.toBlockRecord C L ``` **Optimal-Hydrozoan's support.** #### `Agree` *def, `Properties.Agree.lean`* ```lean def Agree (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V₁ V₂ : R.View U) (k : ℕ) (v₁ v₂ : Option BlockId), R.Decided S V₁ k v₁ → R.Decided S V₂ k v₂ → v₁ = v₂ ``` **Agreement.** Under one schedule and over one universe, any two views' verdicts at a slot coincide. #### `Stack` *inductive, `Properties.Arcs.Stack.lean`* ```lean inductive Stack (R : DagRule Validator BlockId Payload) : R.Universe → Slots Validator → R.Universe → Slots Validator → ℕ → ℕ → ℕ → Prop | nil {U : R.Universe} {S : Slots Validator} : Stack R U S U S 0 0 0 | step {U U' U'' : R.Universe} {S S' S'' : Slots Validator} {G R₀ d G' R₀' d' : ℕ} : Rebased R U U' S S' G R₀ d → Stack R U' S' U'' S'' G' R₀' d' → Stack R U S U'' S'' (G + G') (max R₀ (R₀' + G)) (d + d') ``` **A stack of mechanisms**: a finite sequence, each step a `Rebased`. The indices carry the composite's offset, settling round and base slot, accumulated as `Rebased.trans` accumulates them. #### `Banded` *def, `Properties.Band.lean`* ```lean def Banded (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (k : ℕ) (v : Option BlockId), R.Decided S V k v → ∃ top : ℕ, ∀ (g g' d d' : ℕ) (S' : Slots Validator) (U' : R.Universe) (V' : R.View U') (k' : ℕ), k + d' = k' + d → (∀ m m', m + d' = m' + d → S.slotRound m ≤ top → S.slotRound m + g = S'.slotRound m' + g') → (∀ m m', m + d' = m' + d → S.slotRound m ≤ top → S.leader m = S'.leader m') → AgreeBand R U U' (S.slotRound k + g) (top + g) g g' → (∀ b, b ∈ R.viewIds V → S.slotRound k ≤ (R.block U b).round → (R.block U b).round ≤ top → b ∈ R.viewIds V') → R.Decided S' V' k' v ``` **Every verdict reads a band of rounds.** From the slot's own round up to some top, the blocks the view holds and the leaders of the slots sitting there already carry the verdict: any universe carrying the band up to a shift, any view holding those blocks, and any schedule whose slots correspond and whose leaders match inside the band, decides the corresponding slot the same way. `g, g'` put the two universes in one frame of rounds and `d, d'` the two schedules in one frame of slots (slot `m` of `S` answering to `m'` of `S'` when `m + d' = m' + d`); all four are zero for persistence, locality and view monotonicity, and a truncation by `G` from base slot `d` uses `g = 0, g' = G, d' = 0`. #### `DecidedBelow` *def, `Properties.Bounded.lean`* ```lean def DecidedBelow (R : DagRule Validator BlockId Payload) (S : Slots Validator) (B : ℕ) {U : R.Universe} (V : R.View U) (k : ℕ) (v : Option BlockId) : Prop := k < B ∧ R.Decided S V k v ∧ ∀ S' : Slots Validator, S'.slotRound = S.slotRound → (∀ m, m < B → S'.leader m = S.leader m) → R.Decided S' V k v ``` **A verdict decided below `B`**: the slot sits below the bound, the verdict holds, and it is unchanged by any reassignment of the leaders at or above the bound. The round structure is held fixed, which is what reassignment means. #### `CommitsCandidate` *def, `Properties.Candidate.lean`* ```lean def CommitsCandidate (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (k : ℕ) (L : BlockId), R.Decided S V k (some L) → R.IsCandidate S U k L ``` **A commit names the slot's candidate.** Where a rule commits `L` at slot `k`, `L` is a block the universe holds, at that slot's round, authored by that slot's leader. Not derivable from the band. `Banded` says which DAGs a verdict cannot tell apart; it says nothing about what the verdict's payload denotes, and a rule that committed an id it had never seen would satisfy every band. #### `DagRule` *structure, `Properties.Carrier.lean`* ```lean structure DagRule (Validator : Type) [Fintype Validator] [DecidableEq Validator] (BlockId : Type) [DecidableEq BlockId] (Payload : Type) where /-- The universe type of the base development. -/ Universe : Type /-- The view type, indexed by universe. -/ View : Universe → Type /-- The block an id denotes: round, creator and references. -/ block : Universe → BlockId → Block Validator BlockId Payload /-- The ids of the universe. -/ ids : Universe → Finset BlockId /-- The ids a view holds. -/ viewIds : ∀ {U : Universe}, View U → Finset BlockId /-- A view holds only blocks the universe has. Every protocol's view type carries this proof already, so it costs an instance nothing. -/ viewSound : ∀ {U : Universe} (V : View U), viewIds V ⊆ ids U /-- A view is closed under references: it holds what its blocks point at, which is what a window count needs to be measurement-independent (`Barnacle/Window/`). -/ viewComplete : ∀ {U : Universe} (V : View U), ∀ i ∈ viewIds V, ∀ j ∈ (block U i).refs, j ∈ viewIds V /-- **A universe is a block DAG**: every reference is present and sits one round below. A fact about the DAG model, carried by every universe's validity record, rather than a rule-specific property. -/ causal : ∀ U : Universe, CausalStructure (block U) (ids U) /-- The decision relation under a schedule. -/ Decided : Slots Validator → ∀ {U : Universe}, View U → ℕ → Option BlockId → Prop ``` **What a mechanism may read of a protocol**: a universe type, views over it, the projections into `Block`, and the decision relation — smaller than `Barnacle.BaseRule`, which adds what its own mechanism needs. #### `RebasedAbove` *structure, `Properties.Carrier.lean`* ```lean structure RebasedAbove (R : DagRule Validator BlockId Payload) (U U' : R.Universe) (G R₀ : ℕ) : Prop where /-- The same blocks at and above `R₀`. -/ mem : ∀ b, (b ∈ R.ids U ∧ R₀ ≤ (R.block U b).round) ↔ (b ∈ R.ids U' ∧ R₀ ≤ (R.block U' b).round + G) /-- At rounds `G` apart. Additive, so truncated subtraction never appears. -/ round : ∀ b, b ∈ R.ids U → R₀ ≤ (R.block U b).round → (R.block U' b).round + G = (R.block U b).round /-- With the same author. -/ creator : ∀ b, b ∈ R.ids U → R₀ ≤ (R.block U b).round → (R.block U' b).creator = (R.block U b).creator /-- And, strictly above, the same references. -/ refs : ∀ b, b ∈ R.ids U → R₀ < (R.block U b).round → (R.block U' b).refs = (R.block U b).refs ``` **One DAG is another above a round, rebased.** At and above `R₀` the two universes hold the same blocks, at rounds `G` apart, with the same authors; strictly above `R₀`, the same references too. Nothing is said below `R₀`, where a mechanism does its work. One relation serves a truncation (`R₀ = G`), a fill or extension (no rebasing), and plain agreement (the zero offset, `AgreeAbove`). References are compared strictly above `R₀`: a truncation empties its bottom layer's references, and every rule reads a vote from a parent, so a block at exactly `R₀` contributes presence and authorship but no vote. #### `Indirect` *def, `Properties.Commit.lean`* ```lean def Indirect (R : DagRule Validator BlockId Payload) (Elig : (ℕ → ℕ) → ℕ → ℕ → Prop) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (i j : ℕ) (A : BlockId), Elig S.slotRound i j → R.Decided S V j (some A) → (∀ i', i < i' → i' < j → Elig S.slotRound i i' → R.Decided S V i' none) → ∃ v, ∀ S' : Slots Validator, S'.slotRound = S.slotRound → S'.leader i = S.leader i → R.Decided S' V j (some A) → (∀ i', i < i' → i' < j → Elig S.slotRound i i' → R.Decided S' V i' none) → R.Decided S' V i v ``` **The indirect rule.** An anchor eligible for slot `i`, committed, with every eligible slot strictly between them skipped, decides `i`. `Elig` is a parameter and reads the **round structure alone**: every rule here makes an anchor eligible when it sits a wave above the slot, and the property does not care which wave. Reading only `slotRound` also means eligibility is unchanged by a reassignment of leaders, which the second quantifier needs. **The second quantifier is what makes this carry a bound.** A protocol that proves the indirect rule by cases on the evidence at slot `i` — which is how all of them prove it — proves this stronger form without extra work: the case split reads slot `i`'s own candidate and the anchor's history, and a schedule that renames leaders elsewhere changes neither. `Descends` is the payoff, derived in `Derived/Descent.lean` where it was three protocol-specific inductions. Taking `S' := S` gives the plain rule, which is what a mechanism that does not track bounds consumes. #### `Rebased` *structure, `Properties.Compose.lean`* ```lean structure Rebased (R : DagRule Validator BlockId Payload) (U U' : R.Universe) (S S' : Slots Validator) (G R₀ d : ℕ) : Prop extends RebasedAbove R U U' G R₀, Rebases S S' G d ``` **What one mechanism delivers, universe and schedule together.** #### `Descends` *def, `Properties.Derived.Descent.lean`* ```lean def Descends (R : DagRule Validator BlockId Payload) (S : Slots Validator) (c : ℕ) : Prop := ∀ {U : R.Universe} (V : R.View U) (b : ℕ), (∀ j, b ≤ j → j < b + c → ∃ L, DecidedBelow R S (b + c) V j (some L)) → ∀ i, i < b → ∃ v, DecidedBelow R S (b + c) V i v ``` **A committed run decides everything below it.** `c` consecutive slots from `b`, each committed within `b + c`, decide every slot below `b` within `b + c`. #### `LeaderCommits` *def, `Properties.Derived.LeaderCommits.lean`* ```lean def LeaderCommits (R : DagRule Validator BlockId Payload) (Live : Slots Validator → ∀ {U : R.Universe}, R.View U → Finset Validator → ℕ → ℕ → Prop) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (T : Finset Validator) (lo K : ℕ), Live S V T lo K → ∀ k, lo ≤ k → k < K → S.leader k ∈ T → ∃ L, DecidedBelow R S (k + 1) V k (some L) ``` **A reliable leader's slot commits**, within a bound one above it, wherever the protocol's precondition `Live` holds over a slot window containing the slot. #### `live` *def, `Properties.Derived.LeaderCommits.lean`* ```lean def live (rel : Reliability Validator) (S : Slots Validator) {U : R.Universe} (V : R.View U) (T : Finset Validator) (lo K : ℕ) : Prop := rel.IsQuorum T ∧ ∃ N, CoversUpto R V N ∧ (∀ k, k < K → S.slotRound k + sp.wave ≤ N) ∧ ∀ k, lo ≤ k → k < K → S.leader k ∈ T → (∀ n, S.slotRound k ≤ n → n ≤ S.slotRound k + sp.wave → PopulatedOn R U T n) ∧ ∀ L, R.IsCandidate S U k L → sp.certifiesAt U T (S.slotRound k) L ``` **The liveness precondition, in support terms.** A quorum, a horizon the view is caught up to with the window a wave under it, and at every quorum-led slot of the window production and certification. #### `Persist` *def, `Properties.Derived.Persist.lean`* ```lean def Persist (R : DagRule Validator BlockId Payload) : Prop := ∀ (S : Slots Validator) (U U' : R.Universe), Extends R U U' → ∀ (V : R.View U) (V' : R.View U'), R.viewIds V ⊆ R.viewIds V' → ∀ (k : ℕ) (v : Option BlockId), R.Decided S V k v → R.Decided S V' k v ``` **Persistence.** A verdict reached on `V` is reached again on any larger view of any extension. **This carried a grade `Ok` and no longer does.** The core once needed one: its skip quantified over the candidates a universe holds, so a slot with none was skipped for nothing and a later candidate broke the derivation, and the grade named the quorum that would have blamed it. That premise was the defect, not the grade — a skip resting on the absence of a candidate is not final, which is the one thing a skip rule exists to be — and it has been replaced by a count of blockers. The parameter was then kept against a rule that decides on a block's *absence* rather than on the contents of blocks present. Such a rule fails `Banded` too, since the band admits extra blocks inside itself, so the grade would not save a protocol that proves the band; and every route to persistence here runs through the band. The parameter added an argument to every consumer and no generality, so it is gone. #### `LocalTruncate` *def, `Properties.Derived.Truncate.lean`* ```lean def LocalTruncate (R : DagRule Validator BlockId Payload) : Prop := ∀ (S S' : Slots Validator) (U U' : R.Universe) (G d : ℕ), Truncates R U U' S S' G d → ∀ (V : R.View U) (V' : R.View U'), ViewAgreeAbove R V V' G → ∀ (k : ℕ) (v : Option BlockId), R.Decided S V (d + k) v ↔ R.Decided S' V' k v ``` **Truncation invariance.** A replica that has pruned below the horizon reaches exactly the verdicts it would have reached with its whole history, at its own numbering. An `↔`, because both directions are consumed: a joiner needs verdicts to survive the cut, and cross-cut agreement needs them to come back. #### `Extends` *structure, `Properties.Extends.lean`* ```lean structure Extends (R : DagRule Validator BlockId Payload) (U U' : R.Universe) : Prop where /-- Every block of `U` is a block of `U'`. -/ subset : ∀ b, b ∈ R.ids U → b ∈ R.ids U' /-- And denotes the same block: same round, author and references. -/ block : ∀ b, b ∈ R.ids U → R.block U' b = R.block U b ``` **`U'` extends `U`**: it holds everything `U` held, and denotes those blocks the same way. Nothing is said about what it adds — that is `Novel` below, which the two fields already determine. #### `CommitsDirect` *def, `Properties.Optional.Direct.lean`* ```lean def CommitsDirect (R : DagRule Validator BlockId Payload) (Direct : ∀ {U : R.Universe}, R.View U → BlockId → ℕ → Prop) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (k : ℕ) (L : BlockId), R.IsCandidate S U k L → Direct V L (S.slotRound k) → R.Decided S V k (some L) ``` **A direct commit is a verdict.** The converse of `CommitsCandidate`, parameterised by the rule's own direct-commit predicate — what counts as *direct* is the rule's business and not the carrier's, which is why `Direct` is an argument rather than a field. #### `Quorate` *def, `Properties.Optional.Quorate.lean`* ```lean def Quorate (R : DagRule Validator BlockId Payload) (rel : Reliability Validator) : Prop := ∀ U : R.Universe, QuorateOn (R.block U) (R.ids U) rel ``` **A rule's universes are quorate.** Every non-genesis block references blocks by at least `n − slack` distinct authors — the counting clause of validity, which is what density counts against. The fault model comes in as a `Reliability`: which validators the count is about, how many may be outside them, and that those are a minority. Six fault classes are in play across the development and each supplies one in a line, which is why neither this property nor `LeanDag.Density` names any of them. #### `SelfParent` *def, `Properties.Optional.SelfParent.lean`* ```lean def SelfParent (R : DagRule Validator BlockId Payload) : Prop := ∀ (U : R.Universe) (b : BlockId), b ∈ R.ids U → 0 < (R.block U b).round → ∃ p ∈ (R.block U b).refs, (R.block U p).creator = (R.block U b).creator ``` **Every non-genesis block references its author's previous block.** #### `NoEquiv` *def, `Properties.Optional.SelfParent.lean`* ```lean def NoEquiv (R : DagRule Validator BlockId Payload) (rel : Reliability Validator) : Prop := ∀ (U : R.Universe) (b c : BlockId), b ∈ R.ids U → c ∈ R.ids U → (R.block U b).creator ∈ rel.correct → (R.block U b).creator = (R.block U c).creator → (R.block U b).round = (R.block U c).round → b = c ``` **A reliable author has one block per round.** #### `Unsupported` *def, `Properties.Optional.Skip.lean`* ```lean def Unsupported (R : DagRule Validator BlockId Payload) (S : Slots Validator) (U : R.Universe) (V : R.View U) (T : Finset Validator) (k : ℕ) : Prop := ∀ c, c ∈ R.viewIds V → (R.block U c).creator ∈ T → (R.block U c).round = S.slotRound k + 1 → ∀ L, R.IsCandidate S U k L → L ∉ (R.block U c).refs ``` **A slot's candidates are unsupported by `T`**: every `T`-authored block in view one round above the slot references none of them. #### `SkipsUnsupported` *def, `Properties.Optional.Skip.lean`* ```lean def SkipsUnsupported (R : DagRule Validator BlockId Payload) (Ok : Finset Validator → Prop) : Prop := ∀ (S : Slots Validator) (U : R.Universe) (V : R.View U) (T : Finset Validator) (k : ℕ), Ok T → PresentAt R V T (S.slotRound k + 1) → Unsupported R S U V T k → R.Decided S V k none ``` **Skippability, graded.** A slot whose candidates `T` does not support is skipped, provided `T` meets the protocol's condition. #### `DagRule.OnRecord` *structure, `Properties.Record.lean`* ```lean structure DagRule.OnRecord (R : DagRule Validator BlockId Payload) (P : Validity Validator BlockId Payload) (honest : Finset Validator) (I : BlockRecord Validator BlockId Payload P honest → Prop) where /-- A universe, as a record. -/ toRec : R.Universe → BlockRecord Validator BlockId Payload P honest /-- The record of a universe has the invariant. -/ inv : ∀ U, I (toRec U) /-- A record with the invariant, as a universe. -/ ofRec : ∀ W : BlockRecord Validator BlockId Payload P honest, I W → R.Universe ids_to : ∀ U, (toRec U).ids = R.ids U block_to : ∀ U, (toRec U).block = R.block U ids_of : ∀ W h, R.ids (ofRec W h) = W.ids block_of : ∀ W h, R.block (ofRec W h) = W.block /-- **The maps are inverse on the record side.** Reading a record as a universe and back is the identity, which is what lets a construction that starts on the record — a joiner assembling a view out of what it fetched — be read as the rule's. Every carrier here discharges it by `rfl`. -/ toRec_ofRec : ∀ W h, toRec (ofRec W h) = W /-- A view, as a view of the record. -/ toView : ∀ {U : R.Universe}, R.View U → (toRec U).View /-- A view of a record, as a view of the universe it makes. -/ ofView : ∀ {W : BlockRecord Validator BlockId Payload P honest} {h : I W}, W.View → R.View (ofRec W h) viewIds_to : ∀ {U : R.Universe} (V : R.View U), (toView V).ids = R.viewIds V viewIds_of : ∀ {W : BlockRecord Validator BlockId Payload P honest} {h : I W} (V : W.View), R.viewIds (ofView (h := h) V) = V.ids ``` **A carrier read as block records.** #### `chop` *def, `Properties.Record.lean`* ```lean def chop (U : R.Universe) (G : ℕ) : R.Universe := c.ofRec ((c.toRec U).chop G) (Invariant.Mechanised.chop G (c.inv U)) ``` The cut, at the carrier. #### `addGenesis` *def, `Properties.Record.lean`* ```lean def addGenesis (U : R.Universe) (v : Validator) (g : BlockId) (p : Payload) (hg : g ∉ (c.toRec U).ids) (hsev : ∀ b ∈ (c.toRec U).ids, ((c.toRec U).block b).creator ≠ v) : R.Universe := c.ofRec (BlockRecord.addGenesis (c.toRec U) v g p hg hsev) (Invariant.Mechanised.addGenesis v g p hg hsev (c.inv U)) ``` Re-genesis, at the carrier. #### `Support` *structure, `Properties.Support.lean`* ```lean structure Support (R : DagRule Validator BlockId Payload) where /-- The wavelength: certifiers sit `wave` rounds above the candidate. -/ wave : ℕ /-- `Certifies U c L`: block `c` certifies candidate `L`. -/ Certifies : R.Universe → BlockId → BlockId → Prop ``` **A rule's support shape**: how far above a candidate its certifiers sit, and what it means for one of them to certify it. #### `Local` *def, `Properties.Support.lean`* ```lean def Local : Prop := ∀ {U U' : R.Universe} {G R₀ : ℕ}, RebasedAbove R U U' G R₀ → ∀ c L, c ∈ R.ids U → R₀ + sp.wave ≤ (R.block U c).round → L ∈ R.ids U → (R.block U L).round + sp.wave = (R.block U c).round → (sp.Certifies U' c L ↔ sp.Certifies U c L) ``` **Law 1 — certification is local.** `Banded` for the support relation: across any `RebasedAbove`, a certifier whose whole window sits at or above the settling round certifies the same candidates. #### `Commits` *def, `Properties.Support.lean`* ```lean def Commits (rel : Reliability Validator) : Prop := ∀ (S : Slots Validator) {U : R.Universe} (V : R.View U) (T : Finset Validator) (k : ℕ), rel.IsQuorum T → (∀ n, S.slotRound k ≤ n → n ≤ S.slotRound k + sp.wave → PopulatedOn R U T n) → (∀ L, R.IsCandidate S U k L → sp.certifiesAt U T (S.slotRound k) L) → CoversUpto R V (S.slotRound k + sp.wave) → S.leader k ∈ T → ∃ L, DecidedBelow R S (k + 1) V k (some L) ``` **Law 2 — certification commits.** A slot whose every candidate a reliable quorum certifies, on a populated window a view is caught up to, is committed within a bound one above it when a quorum member leads it. `LeaderCommits` with the precondition made explicit; the upper bound on `Certifies`. #### `VotesAt` *def, `Properties.Sustain.lean`* ```lean def VotesAt (R : DagRule Validator BlockId Payload) (U : R.Universe) (T : Finset Validator) (r : ℕ) (L : BlockId) : Prop := ∀ v ∈ T, ∀ c, c ∈ R.ids U → (R.block U c).creator = v → (R.block U c).round = r + 1 → L ∈ (R.block U c).refs ``` **What the commit rules count**: every `T`-authored block one round above `r` references `L`. The carrier's reading of `LeanDag.VotesAt`. #### `PopulatedOn` *def, `Properties.Sustain.lean`* ```lean def PopulatedOn (R : DagRule Validator BlockId Payload) (U : R.Universe) (T : Finset Validator) (r : ℕ) : Prop := PopulatedFrom (R.block U) (R.ids U) T r ``` **Production**: every member of `T` has a block at round `r`. #### `Sustains` *abbrev, `Properties.Sustain.lean`* ```lean abbrev Sustains (R : DagRule Validator BlockId Payload) (U U' : R.Universe) (G R₀ : ℕ) : Prop := RebasedAbove R U U' G R₀ ``` **A mechanism sustains from round `R₀`**, re-indexing by `G`: this is `RebasedAbove`, under the name the obligation is owed in. The relation is the same one a truncation and a plain agreement satisfy (`Properties/Carrier.lean`); what differs is who owes it. #### `Truncates` *structure, `Properties.Truncate.lean`* ```lean structure Truncates (R : DagRule Validator BlockId Payload) (U U' : R.Universe) (S S' : Slots Validator) (G d : ℕ) : Prop extends RebasedAbove R U U' G G, Rebases S S' G d ``` **`U'` is `U` pruned below `G` and renumbered from slot `d`.** `mem` keeps only what lies at or above the horizon, and `refs` compares references strictly above it, so the retained bottom layer may lose what pointed below it. Both are `RebasedAbove`'s, at `R₀ = G`; what this structure adds is the schedule half. #### `r0` *def, `SafeSkip.Data.lean`* ```lean def r0 : ℕ := (blk sk.B1).round ``` The round of the anchor block — the bottom of the gap. #### `fillBlock` *def, `SafeSkip.Data.lean`* ```lean def fillBlock (k : ℕ) : Block Validator BlockId Payload where round := k creator := sk.v1 refs := insert (sk.prev k) (blk (sk.line k)).refs payload := (blk (sk.line k)).payload ``` The filled block at gap round `k`: `v2`'s references at that round, plus the added self reference. #### `SynchronisedOn` *def, `Timed.Coverage.lean`* ```lean def SynchronisedOn (R : DagRule Validator BlockId Payload) (U : R.Universe) (T : Finset Validator) (r : ℕ) : Prop := SynchronisedFrom (R.block U) (R.ids U) T r ``` **Full coverage from a round**: every `T`-block one round up holds every `T`-block below it as a reference. `LeanDag.SynchronisedFrom`, read at the carrier. #### `CoversToward` *def, `Timed.Coverage.lean`* ```lean def CoversToward (R : DagRule Validator BlockId Payload) (U : R.Universe) (T : Finset Validator) (r wave : ℕ) (L : BlockId) : Prop := ∀ n, r ≤ n → n < r + wave → ∀ b, b ∈ R.ids U → (R.block U b).creator ∈ T → (R.block U b).round = n + 1 → ∀ a, a ∈ R.ids U → (R.block U a).creator ∈ T → (R.block U a).round = n → ReachesFrom (R.block U) a L → a ∈ (R.block U b).refs ``` **Coverage toward a candidate** over a window: every `T`-block at each level of the window references every `T`-block one level below it that reaches the candidate. Full coverage restricted to the candidate's support. #### `OfCoverage` *def, `Timed.Coverage.lean`* ```lean def OfCoverage (sp : Support R) (rel : Reliability Validator) : Prop := ∀ (U : R.Universe) (T : Finset Validator), rel.IsQuorum T → ∀ (r : ℕ) (L : BlockId), (∀ n, r ≤ n → n ≤ r + sp.wave → Properties.PopulatedOn R U T n) → CoversToward R U T r sp.wave L → L ∈ R.ids U → (R.block U L).round = r → (R.block U L).creator ∈ T → ∀ c, c ∈ R.ids U → (R.block U c).creator ∈ T → (R.block U c).round = r + sp.wave → sp.Certifies U c L ``` **Coverage certifies**, for a support. #### `Good` *def, `Timed.Coverage.lean`* ```lean def Good (R : DagRule Validator BlockId Payload) (rel : Reliability Validator) (U : R.Universe) (Rnd N : ℕ) : Prop := ∃ T, rel.IsQuorum T ∧ SynchronisedOn R U T Rnd ∧ ∀ r, Rnd ≤ r → r ≤ N → Properties.PopulatedOn R U T r ``` **A good DAG, from `Rnd` to `N`**: some quorum of the fault model is synchronised from `Rnd` and populates every round from `Rnd` to `N`. Everything a timed model asks of the DAG, packaged: a live rule's notion of a good DAG is this at its own carrier and fault model. --- ## Appendix C. The theorem reference The 474 theorems the body or Appendix A names, each the source statement, unabridged. Generated with Appendix B; a theorem the report does not name is a step of an argument rather than a result it presents, and the source is its reference. ### The validator set and the fault model #### `exists_mem_inter_notMem` *theorem, `Common.Counting.lean`* ```lean theorem exists_mem_inter_notMem {A B Bad : Finset α} {m : ℕ} (hbad : Bad.card ≤ m) (h : Fintype.card α + m < A.card + B.card) : ∃ v ∈ A ∩ B, v ∉ Bad ``` **The intersection lemma.** Two sets whose sizes sum past `n + m` share a member outside any set of at most `m`. #### `reliable_eq_correct` *theorem, `Common.Validators.lean`* ```lean theorem reliable_eq_correct {T : Finset Validator} (hfull : F.byzantine.card = F.f) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) : T = (Correct : Finset Validator) ``` **At full fault load the reliable set is forced.** The liveness results run at any `T ⊆ Correct` with `n − f ≤ T.card`, strictly weaker than `T = Correct`; at full fault load `Correct` is exactly `n − f` large, so `T := Correct` is the only instantiation available. #### `exists_correct_mem_inter` *theorem, `Common.Validators.lean`* ```lean theorem exists_correct_mem_inter {Q₁ Q₂ : Finset Validator} (h₁ : quorumCard Validator ≤ Q₁.card) (h₂ : quorumCard Validator ≤ Q₂.card) : ∃ v ∈ Q₁ ∩ Q₂, v ∈ (Correct : Finset Validator) ``` **T0.** Two quorums always share a *correct* validator. This is the form every later proof cites. ### Blocks, validity, and the universe #### `exists_correct_mem_creators_inter` *theorem, `Common.Block.lean`* ```lean theorem exists_correct_mem_creators_inter {blk : BlockId → Block Validator BlockId Payload} {s t : Finset BlockId} (hs : quorumCard Validator ≤ (creatorsOf blk s).card) (ht : quorumCard Validator ≤ (creatorsOf blk t).card) : ∃ v ∈ creatorsOf blk s ∩ creatorsOf blk t, v ∈ (Correct : Finset Validator) ``` **T0'.** Two id-sets whose creator sets are quorums share a *correct* author. Stated on bare `Finset BlockId`s, since call sites intersect arbitrary sets that are nobody's refs; for a block, apply with `s := b.refs` and `ValidWrt.quorum`. #### `ValidWrt.iff_validAt` *theorem, `Common.BlockDag.lean`* ```lean theorem ValidWrt.iff_validAt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : ValidWrt blk b ↔ ValidAt (quorumCard Validator) (Clause.distinct.and Clause.selfParent) blk b ``` **The core's validity is the family** at the core's quorum, with distinct creators and the self-parent clause. #### `votesAt_of_synchronisedOn` *theorem, `Common.BlockDag.lean`* ```lean theorem votesAt_of_synchronisedOn {U : BlockUniverse Validator BlockId Payload} {T : Finset Validator} {R r : ℕ} {L : BlockId} (hs : SynchronisedOn U T R) (hRr : R ≤ r) (hL : L ∈ U.ids) (hLr : (U.block L).round = r) (hLc : (U.block L).creator ∈ T) : VotesAt U T r L ``` Coverage gives the votes: the instantiation of `SynchronisedOn` at `n = r`, with `L` the one block singled out. ### Causal structure #### `round_le_of_reaches` *theorem, `Common.Causality.lean`* ```lean theorem round_le_of_reaches (C : CausalStructure blk ids) {c b : BlockId} (hc : c ∈ ids) (h : ReachesFrom blk c b) : (blk b).round ≤ (blk c).round ``` **Causal history runs downward in rounds.** #### `BlockRecord.causal` *theorem, `Common.CausalHistory.lean`* ```lean theorem BlockRecord.causal [P.Mechanised] (U : BlockRecord Validator BlockId Payload P honest) : CausalStructure U.block U.ids ``` **A block record is a causal structure**: the two facts the history layer consumes, projected out of completeness and the predecessor fact validity owes. #### `round_le_of_reaches` *theorem, `Common.CausalHistory.lean`* ```lean theorem round_le_of_reaches {c b : BlockId} (hc : c ∈ U.ids) (h : Reaches U c b) : (U.block b).round ≤ (U.block c).round ``` **T2.** Causal history runs downward in rounds: anything `c` reaches sits at a round no greater than `c`'s, from the predecessor condition (`CausalStructure`'s second field). #### `View.mem_of_reaches` *theorem, `Common.CausalHistory.lean`* ```lean theorem View.mem_of_reaches {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {c b : BlockId} (hc : c ∈ V.ids) (h : Reaches U c b) : b ∈ V.ids ``` **T6a.** Causal history never escapes a view. #### `View.exists_reaches_iff` *theorem, `Common.CausalHistory.lean`* ```lean theorem View.exists_reaches_iff {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} {Q : BlockId → Prop} {c : BlockId} (hc : c ∈ V.ids) : (∃ b, b ∈ V.ids ∧ Q b ∧ Reaches U c b) ↔ (∃ b, Q b ∧ Reaches U c b) ``` **T6a, in the form the commit rules consume.** Asking whether a `P`-block lies in `c`'s history gives the same answer confined to the view or not — what makes a view-relative certificate check well defined. #### `authorsAt_eq_authorsIn` *theorem, `Common.Support.lean`* ```lean theorem authorsAt_eq_authorsIn (U : BlockRecord Validator BlockId Payload P honest) (n : ℕ) : authorsAt U n = authorsIn U U.ids n ``` `authorsAt` is `authorsIn` over the whole universe: L0's density and the progress rule's trigger are one measure, read off `U.ids` there and off a validator's holdings here. #### `supportersIn_eq_toRecord` *theorem, `Common.Support.lean`* ```lean theorem supportersIn_eq_toRecord {V : U.View} {b : BlockId} {n : ℕ} : supportersIn U V b n = supporters V.toRecord b n ``` The view's count is the record's count at the view as a record. #### `card_supporters_add_card_blames_le` *theorem, `Common.Support.lean`* ```lean theorem card_supporters_add_card_blames_le [Fintype Validator] (hne : U.NoEquivOn Hon) (hm : Honᶜ.card ≤ m) {L : BlockId} {n : ℕ} : (supporters U L n).card + (blames U L n).card ≤ Fintype.card Validator + m ``` **Supporters and blamers together number at most `n + m`.** #### `exists_mem_refs_of_honest_support_of_card` *theorem, `Common.Support.lean`* ```lean theorem exists_mem_refs_of_honest_support_of_card [Fintype Validator] {Q : BlockId → Prop} {n : ℕ} {T : Finset Validator} (hT : ∀ v ∈ T, ∃ b ∈ U.ids, (U.block b).round = n ∧ Q b ∧ (U.block b).creator = v) (hT_honest : ∀ v ∈ T, v ∈ honest) (hcard : Fintype.card Validator < T.card + q) {c : BlockId} (hc : c ∈ U.ids) (hcr : (U.block c).round = n + 1) : ∃ b ∈ (U.block c).refs, Q b ``` **The hitting lemma, uniform form.** More than `n − q` honest backers always suffice. #### `reaches_pred_of_round_le` *theorem, `Common.Support.lean`* ```lean theorem reaches_pred_of_round_le {q : ℕ} [P.Quorate q] {Q : BlockId → Prop} {N : ℕ} (hbase : ∀ c ∈ U.ids, (U.block c).round = N → ∃ b, Q b ∧ Reaches U c b) {c : BlockId} (hc : c ∈ U.ids) (hcr : N ≤ (U.block c).round) : ∃ b, Q b ∧ Reaches U c b ``` **Propagation.** Reaching something is inherited upward: if every block at round `N` reaches a `Q`-block, so does every block above `N`, by nonempty references and transitivity alone. Shared by T3 and M2, both otherwise just a base case. #### `BlockUniverse.exists_common_mem_of_quorums` *theorem, `Common.Support.lean`* ```lean theorem BlockUniverse.exists_common_mem_of_quorums {s t : Finset BlockId} {n : ℕ} (hs : ∀ q ∈ s, q ∈ U.ids ∧ (U.block q).round = n) (ht : ∀ q ∈ t, q ∈ U.ids ∧ (U.block q).round = n) (hsq : quorumCard Validator ≤ (creatorsOf U.block s).card) (htq : quorumCard Validator ≤ (creatorsOf U.block t).card) : ∃ q, q ∈ s ∧ q ∈ t ``` **Two quorum-backed sets of round-`n` blocks share a block**: a correct author common to both has one round-`n` block. #### `exists_correct_common_support` *theorem, `Common.CommonCore.lean`* ```lean theorem exists_correct_common_support {r : ℕ} (hp : quorumCard Validator ≤ (authorsAt U (r + 1)).card) : ∃ bw ∈ U.ids, (U.block bw).round = r ∧ (U.block bw).creator ∈ (Correct : Finset Validator) ∧ (authorsAt U (r + 1)).card + F.f + 1 ≤ (correctSupporters U bw (r + 1)).card + Fintype.card Validator ``` **T3a (Correct-support counting).** Some correct validator's round-`r` block is backed by enough correct round-`(r+1)` validators to satisfy the `p - 2f` coverage threshold. Double counting: each correct round-`(r+1)` block names at least `(n-f) - b` correct round-`r` authors, and there are `l` such blocks spread over `c = n - b` correct validators, so some author `w` collects at least `l(n-f-b)/c`. The arithmetic obligation reduces to `c² ≤ f(l+c)`, which `l ≤ c` turns into `c ≤ 2f` — impossible, since `b ≤ f` and `n ≥ 3f+1` force `c ≥ 2f+1`. The same contradiction as at `n = 3f+1`, verbatim. #### `exists_common_correct_ancestor` *theorem, `Common.CommonCore.lean`* ```lean theorem exists_common_correct_ancestor {r : ℕ} {c₀ : BlockId} (hc₀ : c₀ ∈ U.ids) (hc₀r : (U.block c₀).round = r + 2) : ∃ bw ∈ U.ids, (U.block bw).round = r ∧ (U.block bw).creator ∈ (Correct : Finset Validator) ∧ ∀ c ∈ U.ids, (U.block c).round = r + 2 → Reaches U c bw ``` **T3c (Common correct ancestor).** If any block exists at round `r+2`, some correct validator's round-`r` block lies in the causal history of *every* round-`(r+2)` block. The only premise is that a round-`(r+2)` block exists — a fact about the DAG in hand, not an assumption that anyone makes progress. Its own reference quorum supplies the `2f+1` author bound T3a needs. Note the statement mentions no `Finset BlockId` operation, so unlike T3a it does **not** require decidable equality on ids; the proof supplies that classically. #### `mem_ids_and_round_of_quorum_support` *theorem, `Common.Persistence.lean`* ```lean theorem mem_ids_and_round_of_quorum_support {b : BlockId} {r : ℕ} {Q : Finset BlockId} (hQ : Q ⊆ U.ids) (hQround : ∀ q ∈ Q, (U.block q).round = r + 1) (hQref : ∀ q ∈ Q, b ∈ (U.block q).refs) (hQquorum : quorumCard Validator ≤ (creatorsOf U.block Q).card) : b ∈ U.ids ∧ (U.block b).round = r ``` The quorum hypothesis already forces `b` into the universe at round `r`, so T3 need not assume either: a nonempty `Q` has a member in the universe referencing `b`, which pins it by completeness. #### `reaches_of_quorum_support` *theorem, `Common.Persistence.lean`* ```lean theorem reaches_of_quorum_support {b : BlockId} {r : ℕ} {Q : Finset BlockId} (hQ : Q ⊆ U.ids) (hQround : ∀ q ∈ Q, (U.block q).round = r + 1) (hQref : ∀ q ∈ Q, b ∈ (U.block q).refs) (hQquorum : quorumCard Validator ≤ (creatorsOf U.block Q).card) {c : BlockId} (hc : c ∈ U.ids) (hcr : r + 2 ≤ (U.block c).round) : Reaches U c b ``` **T3 (Persistence).** If `b` is referenced by a quorum of round-`(r+1)` blocks, every block at round `r+2` or later has `b` in its causal history — neither `b ∈ U.ids` nor its round assumed, both following from the quorum hypothesis. ### Slots and the schedule #### `isLeaderBlock_unique_of_honest` *theorem, `Common.Leader.lean`* ```lean theorem isLeaderBlock_unique_of_honest {k : ℕ} {L₁ L₂ : BlockId} (hk : S.leader k ∈ honest) (h₁ : IsLeaderBlock U k L₁) (h₂ : IsLeaderBlock U k L₂) : L₁ = L₂ ``` **An honest leader has at most one candidate**, so a tie between candidates only arises under a dishonest leader. ### The commit rule, and the ledger #### `certificates_eq_empty_of_directSkip` *theorem, `Mysticeti.Rule.lean`* ```lean theorem certificates_eq_empty_of_directSkip {L : BlockId} {r : ℕ} (h : DirectSkip U L r) : certificates U L r = ∅ ``` **M3.** A directly skipped block has no certificate anywhere in the universe, not merely none in some view: `2f+1` blamers cap the supporters at `2f`, below what a certificate's `2f+1` voters need. #### `not_directCommit_of_directSkip` *theorem, `Mysticeti.Rule.lean`* ```lean theorem not_directCommit_of_directSkip {L : BlockId} {r : ℕ} (h : DirectSkip U L r) : ¬ DirectCommit U L r ``` **M1.** No block is both directly committed and directly skipped, immediate from M3: a skip leaves no certificates, and a commit needs `2f+1` of them. #### `exists_certificate_reaches_of_directCommit` *theorem, `Mysticeti.Rule.lean`* ```lean theorem exists_certificate_reaches_of_directCommit {L : BlockId} {r : ℕ} (h : DirectCommit U L r) {c : BlockId} (hc : c ∈ U.ids) (hcr : r + 3 ≤ (U.block c).round) : ∃ C ∈ certificates U L r, Reaches U c C ``` **M2.** Once a block is directly committed, its certificate becomes unavoidable: every block from round `r+3` on has one in its causal history. The bound is tight — a round-`(r+2)` block that is not itself a certificate reaches none — which is why the slot schedule must space leaders at least three rounds apart. #### `eq_of_certificates_nonempty` *theorem, `Mysticeti.Rule.lean`* ```lean theorem eq_of_certificates_nonempty {L₁ L₂ : BlockId} {r : ℕ} (h₁ : (certificates U L₁ r).Nonempty) (h₂ : (certificates U L₂ r).Nonempty) (hcreator : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **M5′ (certificate uniqueness).** A slot admits at most one certifiable block: if certificates exist for two round-`r` blocks by the same author, those blocks coincide. Stronger than M5, and the form the indirect rule needs, since it commits on a single reachable certificate rather than a quorum of them. #### `eq_of_directCommit_of_creator_eq` *theorem, `Mysticeti.Rule.lean`* ```lean theorem eq_of_directCommit_of_creator_eq {L₁ L₂ : BlockId} {r : ℕ} (h₁ : DirectCommit U L₁ r) (h₂ : DirectCommit U L₂ r) (hcreator : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **M5.** At most one block per slot is directly committed — a corollary of M5′, since a direct commit implies a certificate exists. #### `certifiedIn_of_directCommit` *theorem, `Mysticeti.Rule.lean`* ```lean theorem certifiedIn_of_directCommit {L : BlockId} {r : ℕ} (h : DirectCommit U L r) {A : BlockId} (hA : A ∈ U.ids) (hAr : r + 3 ≤ (U.block A).round) : CertifiedIn U A L r ``` **M4, commit half.** A directly committed block is found by every anchor from round `r+3` on — M2 restated as the indirect rule's test. #### `indirect_agrees_with_direct` *theorem, `Mysticeti.Rule.lean`* ```lean theorem indirect_agrees_with_direct {L : BlockId} {r : ℕ} {A : BlockId} (hA : A ∈ U.ids) (hAr : r + 3 ≤ (U.block A).round) : (DirectCommit U L r → CertifiedIn U A L r) ∧ (DirectSkip U L r → ¬ CertifiedIn U A L r) ``` **M4.** Where the direct rule decides, the indirect rule agrees: commit needs the anchor at round `r+3` or beyond, skip needs nothing at all, since there is no certificate anywhere to reach. #### `certifiedIn_iff_of_view` *theorem, `Mysticeti.Rule.lean`* ```lean theorem certifiedIn_iff_of_view {V : View Validator BlockId Payload U} {A L : BlockId} {r : ℕ} (hA : A ∈ V.ids) : (∃ C, C ∈ V.ids ∧ C ∈ certificates U L r ∧ Reaches U A C) ↔ CertifiedIn U A L r ``` The indirect test is view-independent: a validator holding the anchor computes the same verdict from its own local DAG as from the whole universe (T6a). #### `directCommit_of_directCommitIn` *theorem, `Mysticeti.Rule.lean`* ```lean theorem directCommit_of_directCommitIn {V : View Validator BlockId Payload U} {L : BlockId} {r : ℕ} (h : DirectCommitIn U V L r) : DirectCommit U L r ``` **A view can only under-report.** A view-relative direct commit is a genuine one, which is what lets Stage A's universe-level theorems apply to a validator's local judgement unchanged. #### `directSkip_of_directSkipIn` *theorem, `Mysticeti.Rule.lean`* ```lean theorem directSkip_of_directSkipIn {V : View Validator BlockId Payload U} {L : BlockId} {r : ℕ} (h : DirectSkipIn U V L r) : DirectSkip U L r ``` #### `directSkipIn_of_directSkipSlotIn` *theorem, `Mysticeti.Rule.lean`* ```lean theorem directSkipIn_of_directSkipSlotIn {V : View Validator BlockId Payload U} {k : ℕ} (h : DirectSkipSlotIn U V k) {L : BlockId} (hL : IsLeaderBlock U k L) : DirectSkipIn U V L (S.slotRound k) ``` **The slot-level skip implies the per-candidate one.** #### `coreLaws` *theorem, `Mysticeti.Rule.lean`* ```lean theorem coreLaws : (coreAnchored Validator BlockId Payload).Laws where commit_unique ``` **The core's laws.** Every commit-against-commit case is certificate uniqueness; the crossings are M1, the visibility lemma and M3. #### `exists_least` *theorem, `Mysticeti.Rule.lean`* ```lean theorem exists_least {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {A : BlockId} {i k : ℕ} (_ : i < (coreAnchored Validator BlockId Payload).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (coreAnchored Validator BlockId Payload).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (coreAnchored Validator BlockId Payload).Link i U A L S k ∧ (coreAnchored Validator BlockId Payload).Least (S := S) U A i k L ``` No tie: any certified candidate is the rung's choice. ### Delivery, growth, and coverage #### `SynchronisedOn.mono` *theorem, `Common.Participation.lean`* ```lean theorem SynchronisedOn.mono {T T' : Finset Validator} {R : ℕ} (hsub : T ⊆ T') (h : SynchronisedOn U T' R) : SynchronisedOn U T R ``` Coverage is antitone too. #### `certifies_of_synchronisedOn` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem certifies_of_synchronisedOn (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hRr : R ≤ r) (hpop1 : PopulatedOn U T (r + 1)) (hL : L ∈ U.ids) (hLr : (U.block L).round = r) (hLc : (U.block L).creator ∈ T) (hC : C ∈ U.ids) (hCr : (U.block C).round = r + 2) (hCc : (U.block C).creator ∈ T) : Certifies U C L ``` A correct round-`(r+2)` block certifies any correct round-`r` block, once round `r+1` is populated and synchrony has taken hold: both layers of coverage at once. #### `certifiesAt_of_synchronisedOn` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem certifiesAt_of_synchronisedOn (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hRr : R ≤ r) (hpop1 : PopulatedOn U T (r + 1)) (hL : L ∈ U.ids) (hLr : (U.block L).round = r) (hLc : (U.block L).creator ∈ T) : CertifiesAt U T r L ``` Coverage gives the certificates, through the vote layer: the `CertifiesAt` form of the lemma above. #### `directCommit_of_certifiesAt` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem directCommit_of_certifiesAt (hcard : quorumCard Validator ≤ T.card) (hpop2 : PopulatedOn U T (r + 2)) (hc : CertifiesAt U T r L) : DirectCommit U L r ``` **The commit argument, stated once.** A quorum-sized `T` whose decision-round blocks all certify `L` directly commits it. Both pacing disciplines end here, one through `certifiesAt_of_synchronisedOn`, the other through `ReactiveM.certifies`. #### `directCommit_of_synchronisedOn` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem directCommit_of_synchronisedOn (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hRr : R ≤ r) (hpop1 : PopulatedOn U T (r + 1)) (hpop2 : PopulatedOn U T (r + 2)) (hL : L ∈ U.ids) (hLr : (U.block L).round = r) (hLc : (U.block L).creator ∈ T) : DirectCommit U L r ``` **L4, at the round level.** A correct block at round `r` is directly committed, given coverage from `r` and correct blocks at `r+1` and `r+2`. Stated without `Slots`, since the argument only needs `L` correct-authored, not a leader. #### `directCommit_of_leader_mem` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem directCommit_of_leader_mem (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound k) (hpop0 : PopulatedOn U T (S.slotRound k)) (hpop1 : PopulatedOn U T (S.slotRound k + 1)) (hpop2 : PopulatedOn U T (S.slotRound k + 2)) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` **L4.** A slot with a correct leader, whose three rounds are populated and which sits after synchrony, is directly committed. #### `directCommit_of_correct_leader` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem directCommit_of_correct_leader (hs : Synchronised U R) (hR : R ≤ S.slotRound k) (hpop0 : Populated U (S.slotRound k)) (hpop1 : Populated U (S.slotRound k + 1)) (hpop2 : Populated U (S.slotRound k + 2)) (hlead : S.leader k ∈ (Correct : Finset Validator)) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` **L4 at `T := Correct`.** The original statement, recovered. #### `decided_of_leader_mem` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem decided_of_leader_mem (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound k) (hpop0 : PopulatedOn U T (S.slotRound k)) (hpop1 : PopulatedOn U T (S.slotRound k + 1)) (hpop2 : PopulatedOn U T (S.slotRound k + 2)) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` **L4, as a decision.** What L6 consumes and L3 propagates. #### `decided_of_correct_leader` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem decided_of_correct_leader (hs : Synchronised U R) (hR : R ≤ S.slotRound k) (hpop0 : Populated U (S.slotRound k)) (hpop1 : Populated U (S.slotRound k + 1)) (hpop2 : Populated U (S.slotRound k + 2)) (hlead : S.leader k ∈ (Correct : Finset Validator)) : ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` The same at `T := Correct`. #### `decided_none_of_leader_absent` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem decided_none_of_leader_absent {V : View Validator BlockId Payload U} (h : ∀ b ∈ U.ids, (U.block b).round = S.slotRound k → (U.block b).creator ≠ S.leader k) (hq : quorumCard Validator ≤ (creatorsOf U.block (blocksAt U (S.slotRound k + 1) ∩ V.ids)).card) : Decided U V k none ``` **L5 — an absent leader is skipped.** If the slot-`k` leader has no block at its round, every view holding a quorum at the voting round decides `none`. #### `commits_recur_on` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem commits_recur_on (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R : ℕ) (k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ CommitsAt BlockId Payload T R k' ``` **L6 — commits recur.** For every slot `k` there is a later slot `k'` that every sufficiently grown synchronous DAG commits, the slot fixed by the schedule alone before the DAG is named. #### `commits_recur` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem commits_recur (fair : FairSchedule (Validator := Validator)) (R : ℕ) (k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ CommitsAt BlockId Payload (Correct : Finset Validator) R k' ``` **L6 at `T := Correct`.** The original statement, recovered. #### `decided_of_first_eligible_commit` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem decided_of_first_eligible_commit {V : View Validator BlockId Payload U} {k j : ℕ} {A : BlockId} (helig : (coreAnchored Validator BlockId Payload).Eligible k j) (hfirst : ∀ i, k < i → i < j → ¬ (coreAnchored Validator BlockId Payload).Eligible k i) (hj : Decided U V j (some A)) : ∃ v, Decided U V k v ``` **The escape.** If `j` is committed and nothing strictly between `k` and `j` is eligible to anchor `k`, then `k` is decided outright: the intermediate-skip premise is vacuous. This is what keeps pipelining live — slot `j - 1` cannot anchor on `j` but can anchor on `j + 2` with an empty intermediate range, and fair leader election commits `j + 2` whenever `j` is committed. #### `decided_of_committed_above` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem decided_of_committed_above (helig : ∀ a b : ℕ, a < b → (coreAnchored Validator BlockId Payload).Eligible a b) {V : View Validator BlockId Payload U} {n : ℕ} {A : BlockId} (hn : Decided U V n (some A)) : ∀ i, i ≤ n → ∃ v, Decided U V i v ``` **L8.** Given a committed slot, every slot below it is decided, provided every later slot may anchor an earlier one — pure decision-relation combinatorics, with no synchrony, timing or fairness. The work is choosing the nearest committed slot above `i` and reading the intermediate premise off the induction hypothesis. #### `all_decided_below_of_spacing` *theorem, `Mysticeti.Liveness.lean`* ```lean theorem all_decided_below_of_spacing (hsp : ∀ k, S.slotRound k + 3 ≤ S.slotRound (k + 1)) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R : ℕ) (k : ℕ) : ∃ n, k ≤ n ∧ R ≤ S.slotRound n ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound n + 2 ≤ N → ∀ i, i ≤ n → ∃ v, Decided U (View.full U) i v ``` **L8 under three-round spacing.** Combining L6 with L8: for every slot `k` there is a slot `n ≥ k` such that a sufficiently grown synchronous DAG decides every slot up to `n`. A pipelined or multi-leader schedule does not satisfy `hsp`, which is why this is stated conditionally. ### Time: GST, and the rated bounds #### `backoff_ge_of_rate` *theorem, `Mysticeti.Quantitative.lean`* ```lean theorem backoff_ge_of_rate {timeout : ℕ → ℕ} (hrate : Rated timeout) (m : ℕ) : ∀ n, m ≤ n → m ≤ timeout n ``` **A rated backoff clears any threshold by the threshold itself.** Monotonicity is not used: the bound at `n` comes from `n` itself, so it cannot lapse afterwards. #### `unbounded_of_rated` *theorem, `Mysticeti.Quantitative.lean`* ```lean theorem unbounded_of_rated {timeout : ℕ → ℕ} (hrate : Rated timeout) : ∀ m, ∃ n, m ≤ timeout n ``` Every rated backoff is unbounded, so `Rated` really is a strengthening of the retired existential hypothesis rather than a sideways move. #### `FairWithin.fairScheduleOn` *theorem, `Mysticeti.Quantitative.lean`* ```lean theorem FairWithin.fairScheduleOn {w : ℕ} (h : FairWithin T w) : FairScheduleOn T ``` A rated schedule is a fair one, so everything already proved from `FairScheduleOn` applies to it unchanged. #### `commits_recur_within` *theorem, `Mysticeti.Quantitative.lean`* ```lean theorem commits_recur_within (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairWithin T w) (R k : ℕ) : ∃ k', max k (slotAt Validator R) ≤ k' ∧ k' < max k (slotAt Validator R) + w ∧ R ≤ S.slotRound k' ∧ CommitsAt BlockId Payload T R k' ``` **Q4, the schedule half.** L6 with the committing slot bounded: it lies within `w` slots of `max k R`, where the slot past round `R` is named explicitly by `slotAt Validator R` rather than assuming a coincidence a monotone schedule need not give. #### `commits_recur_by_round` *theorem, `Mysticeti.Quantitative.lean`* ```lean theorem commits_recur_by_round {s : ℕ} (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairWithin T w) (hs : BoundedSpacing (Validator := Validator) s) (R k : ℕ) : ∃ k', k ≤ k' ∧ S.slotRound k' ≤ S.slotRound (max k (slotAt Validator R)) + s * w ∧ R ≤ S.slotRound k' ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound (max k (slotAt Validator R)) + s * w + 2 ≤ N → ∃ L, IsLeaderBlock U k' L ∧ Decided U (View.full U) k' (some L) ``` **Q4 in rounds.** The committing slot's round is bounded: the search starts at `slotRound (max k (slotAt R))`, `s * w` is the worst-case cost of walking to the next `T`-leader, and `+ 2` is the certificate round. At the standard settings this reads `3 * (f + 1)` rounds past the starting slot. Blind to multiple leaders — `BoundedSpacing s` cannot see that several leaders may share a round — but the only statement that says anything about an irregular schedule. ### The pacing structures, and the delivery they induce #### `convergesWithin_iff_bounded` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem convergesWithin_iff_bounded (hmono : ∀ v, ∀ s t, s ≤ t → holds v s ⊆ holds v t) : ConvergesWithin holds T gst bound ↔ (∀ v ∈ T, ∀ w ∈ T, ∀ t, gst ≤ t → ∃ d ≤ bound, holds w t ⊆ holds v (t + d)) ``` **The factoring.** Under monotone holdings, convergence within a bound and bounded eventual convergence are the same condition. #### `reached` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem reached (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, ∀ v ∈ T, n ≤ pc.top v ``` **Every reliable validator reaches every round below the horizon** — with `T` a quorum, nobody in `T` is stuck: `holds_mono` carries each `w ∈ T`'s round-`n` block to a common time, `converges` puts a quorum of them in `v`'s hands, and `advances` fires. Proved on the trunk, with no floor, ceiling or timeout mentioned. #### `populatedOn` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem populatedOn (pc : PaceCore U T N) (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, PopulatedOn U T n ``` **Production, once for every discipline**: a round a validator got to is a round it built in. #### `viewAt_ids` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem viewAt_ids (pc : PaceCore U T N) {v : Validator} (hv : v ∈ T) (t : ℕ) : (pc.viewAt v t).ids = pc.holds v t ``` **The view a validator holds is exactly what it holds.** Under closure `viewAt` adds nothing: a reliable validator's view is its holdings, and the local liveness statement is about blocks it actually has. #### `holds_roundBlocks` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem holds_roundBlocks (pc : PaceCore U T N) {n : ℕ} (hn : n ≤ N) (hg : ∀ u ∈ T, pc.gst ≤ pc.built u n) : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = n → b ∈ pc.holds v (pc.latest n + pc.delay) ``` **Delivery, in the form the local argument consumes.** Past GST, every reliable validator holds every `T`-authored round-`n` block by `latest n + delay`: its author holds it when built, `latest` is a common time for the whole round, and convergence carries it across. #### `driftOn_of_catchup` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem driftOn_of_catchup {R : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : pc.gst ≤ R) (hle : ∀ u ∈ T, ∀ n ≤ pc.top u, n ≤ pc.built u n) : DriftOn pc.built T R (pc.delay + pc.proc) N ``` The collapsed spread, in the form the coverage argument consumes — with no base hypothesis anywhere. `hle` is the discipline's `le_built` (rounds advance real time), which each extension proves from its own schedule clauses; everything else is the trunk's. #### `covers_of_converges` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem covers_of_converges {n : ℕ} (hn : n < N) {c : BlockId} (hc : c ∈ U.ids) (hcT : (U.block c).creator ∈ T) (hcr : (U.block c).round = n + 1) {a : BlockId} (ha : a ∈ U.ids) (haT : (U.block a).creator ∈ T) (har : (U.block a).round = n) (hgst : vp.gst ≤ vp.built ((U.block a).creator) n) (hearly : vp.built ((U.block a).creator) n + vp.delay ≤ vp.built ((U.block c).creator) (n + 1)) : a ∈ (U.block c).refs ``` **The separation** — V1's content over the partial schedule: a `T`-block built after GST and early enough is referenced, derivable from `converges` and `references` alone — the block is in its author's hands when built, reaches the builder within `delay`, and is still there when the builder acts. No counting, drift or waiting rule enters until the arrival-time hypothesis itself must be discharged. #### `le_built` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem le_built {v : Validator} (hv : v ∈ T) : ∀ n ≤ vp.top v, n ≤ vp.built v n ``` Rounds advance real time, over the rounds a validator reached. #### `reached` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem reached (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, ∀ v ∈ T, n ≤ vp.top v ``` **Production, with no deadline to beat.** Every round below the horizon is populated, from genesis, view convergence and the progress rule alone, with no drift, backoff, timeout or schedule side condition. By induction: each `w ∈ T` holds its round-`n` block, `holds_mono` carries it to a common time, `converges` puts a quorum of them in `v`'s hands, and `advances` fires. #### `populatedOn` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem populatedOn (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) : ∀ n ≤ N, PopulatedOn U T n ``` **Production**, inherited from the trunk (`PaceCore.populatedOn`). #### `driftOn_of_catchup` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem driftOn_of_catchup (vp : ViewPace U T N) {R : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) : DriftOn vp.built T R (vp.delay + vp.proc) N ``` **Drift is derived**, from the trunk's catch-up rule: the collapsed spread `delay + proc`, from any `R` past GST, with no hypothesis about the start. The quorum bound enters through `reached`, which places those builds past GST. #### `synchronisedOn_of_driftOn` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem synchronisedOn_of_driftOn {R D : ℕ} (hD : DriftOn vp.built T R D N) (hgst : vp.gst ≤ R) (hbackoff : ∀ n, R ≤ n → D + vp.delay ≤ vp.timeout n) : SynchronisedOn U T R ``` **The coverage engine** — the race, run against an arbitrary drift bound `D`. Needs neither production, the quorum bound, nor `T ⊆ Correct`, since `references` and `holds_own` are stated over any block a validator authored. Kept for reliable sets below the quorum, where drift must be supplied from outside. #### `commits_recur_via_pace` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem commits_recur_via_pace (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ) (vp : ViewPace U T N), vp.gst ≤ R → (∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) → S.slotRound k' + 2 ≤ N → ∃ L, IsLeaderBlock U k' L ∧ Decided U (View.full U) k' (some L) ``` **The liveness spine** (V17): commits recur, with the seed at round `0` genesis, and no schedule side condition. The network contributes `converges` and `vp.gst ≤ R`; the protocol contributes genesis, `advances`, `catchup`, `references` and `waits`; no drift appears, since the backoff clears the collapsed spread. #### `commits_recur_local` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem commits_recur_local (hcard : quorumCard Validator ≤ T.card) (fair : FairScheduleOn T) (R k : ℕ) : ∃ k', k ≤ k' ∧ R ≤ S.slotRound k' ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ) (vp : ViewPace U T N), vp.gst ≤ R → (∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) → S.slotRound k' + 2 ≤ N → ∃ L, IsLeaderBlock U k' L ∧ ∀ v ∈ T, Decided U (vp.viewAt v (vp.latest (S.slotRound k' + 2) + vp.delay)) k' (some L) ``` **The liveness spine, localised** (V18): commits recur, and at the recurring slot every reliable validator decides on its own view, the slot fixed by the schedule and the round bound alone. `T ⊆ Correct` is not needed, since production is the pacing structure's own over `T`. #### `decided_of_local` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem decided_of_local (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) (hbackoff : ∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` **The global statement is a corollary**, so V18 strictly strengthens the main line: a reliable validator exists (the quorum bound is nonvacuous), it decides locally, and view monotonicity lifts its verdict to the full view — the band, not L3. `LeaderCommits` reaches the same conclusion without ever naming a validator's own view; this route names one. #### `exists_reliable_parent` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem exists_reliable_parent (hcard : quorumCard Validator ≤ T.card) {b : BlockId} (hb : b ∈ U.ids) (hr : 0 < (U.block b).round) : ∃ a ∈ (U.block b).refs, a ∈ U.ids ∧ (U.block a).creator ∈ T ∧ (U.block a).round + 1 = (U.block b).round ``` **Every valid non-genesis block carries a reliable parent.** Its reference quorum has at least `n − 2f ≥ f + 1` authors in any quorum-sized `T`; one is exhibited. Nothing about pacing enters: this is a fact about validity and cardinalities alone. #### `PaceCore.round_le_top_succ` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem PaceCore.round_le_top_succ (pc : PaceCore U T N) (hcard : quorumCard Validator ≤ T.card) {b : BlockId} (hb : b ∈ U.ids) : ∃ u ∈ T, (U.block b).round ≤ pc.top u + 1 ``` **No block outruns the reliable frontier by more than one round.** Whatever a Byzantine validator publishes, some `T`-validator has reached the round below it — the adversary's whole freedom is the single layer it may build the instant a quorum forms beneath it. #### `ViewPace.exists_honest_floor` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem ViewPace.exists_honest_floor (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) {b : BlockId} (hb : b ∈ U.ids) {n : ℕ} (hbr : (U.block b).round = n + 1) : ∃ u ∈ T, n ≤ vp.top u ∧ vp.built u 0 + (∑ i ∈ Finset.range n, vp.timeout i) ≤ vp.built u n ``` **The honest floor** (CU5): a valid block of round `n + 1` certifies that some reliable validator reached round `n` having genuinely waited out all `n` timeouts, so the author-blind catch-up a deployment runs never pulls a validator past where a reliable peer already is. #### `decided_of_wait` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem decided_of_wait (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hgst : vp.gst ≤ R) (hwait : ∀ n, R ≤ n → 2 * vp.delay + vp.proc ≤ vp.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ Decided U (View.full U) k (some L) ``` **The wait bound, as a decision.** #### `directCommit_of_wait_two_delay` *theorem, `Mysticeti.ViewPace.lean`* ```lean theorem directCommit_of_wait_two_delay (vp : ViewPace U T N) (hcard : quorumCard Validator ≤ T.card) (hproc : vp.proc = 0) (hgst : vp.gst ≤ R) (hwait : ∀ n, R ≤ n → 2 * vp.delay ≤ vp.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` **`Delay(Δ) = 2Δ`** — the threshold at instantaneous entry: when `proc = 0`, the constant is twice the delivery bound, the timer deployed implementations run. The general threshold degrades linearly in the processing bound and in nothing else. #### `heldOf_inj` *theorem, `Mysticeti.PaceDelivery.lean`* ```lean theorem heldOf_inj {v : Validator} (hv : v ∈ (Correct : Finset Validator)) {n : ℕ} (hn : n < N) {i j : BlockId} (hi : i ∈ vp.heldOf v n) (hj : j ∈ vp.heldOf v n) (hij : (U.block i).creator = (U.block j).creator) : i = j ``` **The acceptance rule is derived** (V19). A correct validator never holds two blocks by one author at a build instant, not by deduplication but because P7 would oblige its block to reference both and P2 forbids that. The storage development assumes this; here it is a theorem. ### Denial of service #### `eq_of_mem_refs_of_creator_eq` *theorem, `DoS.Exposure.lean`* ```lean theorem eq_of_mem_refs_of_creator_eq {b i j : BlockId} (hb : b ∈ U.ids) (hi : i ∈ (U.block b).refs) (hj : j ∈ (U.block b).refs) (hcreator : (U.block i).creator = (U.block j).creator) : i = j ``` **D7, the no-equivocation half.** A block's references carry distinct authors, so the layer immediately below a block is equivocation-free. #### `card_exposedTo_le` *theorem, `DoS.Exposure.lean`* ```lean theorem card_exposedTo_le {b : BlockId} (hb : b ∈ U.ids) : (exposedTo U b).card ≤ F.f ``` At most `f` authors can be exposed, since exposure requires equivocation. #### `creators_refs_eq_correct` *theorem, `DoS.Exposure.lean`* ```lean theorem creators_refs_eq_correct (hdos : DoSValid U) {b : BlockId} (hb : b ∈ U.ids) (hround : 0 < (U.block b).round) (hk : F.f ≤ (exposedTo U b).card) : creatorsOf U.block (U.block b).refs = (Correct : Finset Validator) ``` **D15a at the bound.** Once a block has caught the whole fault budget, its references are exactly the correct validators. #### `card_missingAt_le` *theorem, `DoS.Density.lean`* ```lean theorem card_missingAt_le {b : BlockId} (hb : b ∈ U.ids) {δ : ℕ} (hδ : δ < (U.block b).round) : (missingAt U b δ).card ≤ F.f ``` **D25 (density).** A valid block's history contains a block by all but at most `f` of the correct validators at every round strictly below it. #### `card_history_le'` *theorem, `DoS.Pedigree.lean`* ```lean theorem card_history_le' (hdos : DoSValid U) {b : BlockId} (hb : b ∈ U.ids) : (history U b).card ≤ (Fintype.card Validator + (Fintype.card Validator - 1) * F.f ^ F.f) * ((U.block b).round + 1) ``` **The tightened total.** `|H(b)| ≤ (3f+1 + 3f^(f+1))·(r+1)`: the unexposed authors contribute one chain each, the at-most-`f` exposed ones at most `3f·f^(f-1)` chains each. At `f = 1` this is `7(r+1)`, recovering the adoption theorem's constant exactly. #### `card_creators_accepted_of_eventuallyDelivers` *theorem, `DoS.Exclusion.lean`* ```lean theorem card_creators_accepted_of_eventuallyDelivers {R : ℕ} (D : Delivery U) (hd : EventuallyDelivers D R) (hn : R ≤ n) (hpop : Populated U n) {v : Validator} (hv : v ∈ (Correct : Finset Validator)) : quorumCard Validator ≤ (creatorsOf U.block (D.accepted v n)).card ``` **Where the quorum comes from after `R`.** After `R`, a quorum of accepted creators is a consequence rather than a hypothesis: `EventuallyDelivers` and `accepts_correct` supply it from a populated round. Before `R` it is not, which is why L1 holds only from `R`. #### `mem_history_of_correct` *theorem, `DoS.Exclusion.lean`* ```lean theorem mem_history_of_correct {R : ℕ} (hs : SynchronisedOn U (Correct : Finset Validator) R) : ∀ d : ℕ, ∀ c ∈ U.ids, ∀ a ∈ U.ids, (U.block c).creator ∈ (Correct : Finset Validator) → (U.block a).creator ∈ (Correct : Finset Validator) → R ≤ (U.block a).round → (U.block a).round + 1 + d = (U.block c).round → a ∈ history U c ``` **The backbone lemma.** After `R`, correct histories contain the whole correct past. #### `card_history_le_of_stepNovelty` *theorem, `DoS.Novelty.lean`* ```lean theorem card_history_le_of_stepNovelty {κ' : ℕ} (hstep : StepNovelty U κ') (hb : b ∈ U.ids) (hcorr : (U.block b).creator ∈ (Correct : Finset Validator)) : (history U b).card ≤ κ' * (U.block b).round + 1 ``` **The telescope.** Under `StepNovelty`, a correct author's history is linear: `|H(b)| ≤ κ'·r + 1`. Descent along the self-parent chain (S10), one budget per round. #### `UniformBudget.byzBudget` *theorem, `DoS.Novelty.lean`* ```lean theorem UniformBudget.byzBudget {τ : ℕ} (h : UniformBudget D τ) : ByzBudget D τ ``` Dropping a guard weakens nothing: the author-blind cap implies the Byzantine-side budget with the same constant. #### `card_novelty_le_viewGap_add_one` *theorem, `DoS.Novelty.lean`* ```lean theorem card_novelty_le_viewGap_add_one {R : ℕ} (hED : EventuallyDelivers D R) (hn : R ≤ n) (hv : v ∈ (Correct : Finset Validator)) (hb : b ∈ U.ids) (hrefs : (U.block b).refs ⊆ D.accepted w n) : (novelty U (viewUpto D v n) b).card ≤ (viewGap D v w n).card + 1 ``` **C3a.** After `R`, a block built from `w`'s acceptances is, at any correct `v`, at most one plus the gap toward `w`: its correct references are shared (delivered and accepted at `v` too), and its Byzantine references sit whole inside `w`'s view. #### `viewUpto_subset_history` *theorem, `DoS.Novelty.lean`* ```lean theorem viewUpto_subset_history (hw : w ∈ (Correct : Finset Validator)) {b : BlockId} (hb : b ∈ U.ids) (hbc : (U.block b).creator = w) (hbr : (U.block b).round = n + 1) : viewUpto D w n ⊆ history U b ``` A correct validator's block carries everything its author ever accepted: `includes` per round, chained by the self-parent (S10). #### `card_viewGap_succ_le` *theorem, `DoS.Novelty.lean`* ```lean theorem card_viewGap_succ_le {κ R : ℕ} (hbyz : ByzBudget D κ) (hED : EventuallyDelivers D R) (hn : R ≤ n + 1) (hv : v ∈ (Correct : Finset Validator)) (hw : w ∈ (Correct : Finset Validator)) {c : BlockId} (hc : c ∈ U.ids) (hcc : (U.block c).creator = w) (hcr : (U.block c).round = n + 1) : (viewGap D v w (n + 1)).card ≤ F.f * κ ``` **C3′ — the gap is constant, not a drift.** After `R`, the divergence between two correct validators' views is at most one round of Byzantine budget: `w`'s round-`(n+1)` block hands `v` all of `viewUpto w n` at once, the remainder being `w`'s budgeted Byzantine frontier. #### `uniform_of_byzBudget` *theorem, `DoS.Novelty.lean`* ```lean theorem uniform_of_byzBudget {κ R : ℕ} (hbyz : ByzBudget D κ) (hED : EventuallyDelivers D R) (hra : RefsAccepted D) (hv : v ∈ (Correct : Finset Validator)) {n : ℕ} (hn : R ≤ n + 1) (hb : b ∈ D.accepted v (n + 2)) : (novelty U (viewUpto D v (n + 1)) b).card ≤ F.f * κ + 1 ``` **The sandwich, converse direction.** After `R`, a `ByzBudget κ` schedule is uniformly budgeted at `f·κ + 1` with no creator guard: Byzantine acceptances by enforcement, correct ones by C3″. #### `card_byzPool_le` *theorem, `DoS.Novelty.lean`* ```lean theorem card_byzPool_le {κ : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (n : ℕ) : (byzPool D n).card ≤ (Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * κ)) ``` The pool, telescoped: linear from round 0. #### `card_viewUpto_le` *theorem, `DoS.Novelty.lean`* ```lean theorem card_viewUpto_le {κ : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hv : v ∈ (Correct : Finset Validator)) (n : ℕ) : (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * κ))) ``` **B4 — unconditional linear storage.** Under nothing but the enforceable budget and the reference discipline — no synchrony, no `R`, no delivery guarantee — every correct validator's retained view is linear in the round. #### `dos_resistance` *theorem, `DoS.Novelty.lean`* ```lean theorem dos_resistance {τ N : ℕ} {P : Finset Validator} (hpop : ∀ r ≤ N, PopulatedOn U P r) (hu : UniformBudget D τ) (hra : RefsAccepted D) : (∀ r ≤ N, PopulatedOn U P r) ∧ ∀ v ∈ (Correct : Finset Validator), ∀ n, (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + n * ((Correct : Finset Validator).card * (F.f * τ))) ``` **DoS resistance, from enforceable conditions only.** Liveness and linear storage from round 0 under full asynchrony. #### `dos_resistance'` *theorem, `DoS.Novelty.lean`* ```lean theorem dos_resistance' {τ R N : ℕ} {P : Finset Validator} (hpop : ∀ r ≤ N, PopulatedOn U P r) (hED : EventuallyDelivers D R) (hu : UniformBudget D τ) (hra : RefsAccepted D) : (∀ r ≤ N, PopulatedOn U P r) ∧ ∀ v ∈ (Correct : Finset Validator), ∀ n, R + 1 ≤ n → (viewUpto D v n).card ≤ (viewUpto D v (R + 1)).card + (n - (R + 1)) * ((Correct : Finset Validator).card * (F.f * τ + 1) + F.f * τ) ``` The post-`R` incremental form of the headline: the same enforceable conduct, plus the network's `EventuallyDelivers`. #### `card_viewUpto_le_of_allExposed'` *theorem, `DoS.Composition.lean`* ```lean theorem card_viewUpto_le_of_allExposed' {κ : ℕ} (hdos : DoSValid U) (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hexp : AllExposed U m) (hv : v ∈ (Correct : Finset Validator)) (hn : m + 1 ≤ n) (hpop : ∀ r, m + 3 ≤ r → r ≤ n + 1 → Populated U r) : (viewUpto D v n).card ≤ (Correct : Finset Validator).card * (n + 1) + ((Correct : Finset Validator).card * F.f + (m + 1) * ((Correct : Finset Validator).card * (F.f * κ))) ``` **B5, with the constant made explicit by the budget**: the frozen pool is at most `|Correct|·f·(1 + (m+1)·κ)`. ### Garbage collection #### `dosValid_chop` *theorem, `GC.Chop.lean`* ```lean theorem dosValid_chop (hdos : DoSValid U) : DoSValid (chop U G) ``` **G1, DoS half — the one-way door.** The condition survives truncation; the converse fails by design (the statute of limitations, witnessed in `LeanDagTest/GC/Chop.lean`). #### `viewUpto_chopD` *theorem, `GC.Window.lean`* ```lean theorem viewUpto_chopD (m : ℕ) : viewUpto (chopD D G) v m = (viewUpto D v (G + m)).filter fun i => G ≤ (U.block i).round ``` **G14.** The truncated store *is* the store of the truncation: pruning below `G` and accumulating in the window agree exactly. #### `novelty_chop_anti` *theorem, `GC.Window.lean`* ```lean theorem novelty_chop_anti {G' : ℕ} (hGG : G ≤ G') (hb : b ∈ (chop U G').ids) (V : Finset BlockId) : novelty (chop U G') V b ⊆ novelty (chop U G) V b ``` …so it only shrinks novelty: **pruning cheapens blocks** — an block within the budget never falls outside it as the window slides, and no deferral decision ever flips the wrong way. #### `byzBudget_chopD` *theorem, `GC.Window.lean`* ```lean theorem byzBudget_chopD {κ : ℕ} (hbyz : ByzBudget D κ) : ByzBudget (chopD D G) κ ``` #### `refsAccepted_chopD` *theorem, `GC.Window.lean`* ```lean theorem refsAccepted_chopD (hra : RefsAccepted D) : RefsAccepted (chopD D G) ``` #### `populated_chop` *theorem, `GC.Window.lean`* ```lean theorem populated_chop {N : ℕ} (hpop : ∀ r ≤ N, Populated U r) (hG : G ≤ N) : ∀ r ≤ N - G, Populated (chop U G) r ``` **G5.** The truncated universe never stalls above the cut: a round-`r` block of `chop U G` is a round-`(G+r)` block of `U`, so this is the production hypothesis with its index shifted. #### `card_retained_le` *theorem, `GC.Window.lean`* ```lean theorem card_retained_le {κ Λ t : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hv : v ∈ (Correct : Finset Validator)) (hG : G ≤ t) (hΛ : t ≤ G + Λ) : ((viewUpto D v t).filter fun i => G ≤ (U.block i).round).card ≤ (Correct : Finset Validator).card * (Λ + 1) + ((Correct : Finset Validator).card * F.f + Λ * ((Correct : Finset Validator).card * (F.f * κ))) ``` **G6.** A validator whose horizon `G` trails its round `t` by at most `Λ` retains a constant number of blocks, independent of `t`: the retained store is the truncated store (G14), which B4 bounds at window depth `t − G ≤ Λ`. #### `exists_correct_attester_of_mem_base` *theorem, `GC.AttestedBase.lean`* ```lean theorem exists_correct_attester_of_mem_base (hy : y ∈ Base U t G) : ∃ a ∈ U.ids, (U.block a).round = t ∧ (U.block a).creator ∈ (Correct : Finset Validator) ∧ y ∈ history U a ``` **G10, soundness.** Everything in the base has a correct attester — `f+1` authors always include one — and so lies in a correct cone. The adversary cannot smuggle fabrications into anyone's base. #### `correct_mem_base` *theorem, `GC.AttestedBase.lean`* ```lean theorem correct_mem_base {R : ℕ} (hs : Synchronised U R) (hR : R ≤ G) (hGt : G < t) (hpop : Populated U t) (hy : y ∈ U.ids) (hyr : (U.block y).round = G) (hyc : (U.block y).creator ∈ (Correct : Finset Validator)) : y ∈ Base U t G ``` **G10, completeness.** Post-`R`, every correct block of the layer is in every correct attestation (the backbone), so it clears the `f+1` bar in *every* sample — the shared correct layer `C` is in every base, and the adversary cannot filter it out. #### `accepted_mem_base` *theorem, `GC.Bootstrap.lean`* ```lean theorem accepted_mem_base {R m t : ℕ} (hs : Synchronised U R) (hv : v ∈ (Correct : Finset Validator)) (hy : y ∈ viewUpto D v m) (hyr : (U.block y).round = G) (hcar : Populated U (m + 1)) (hpop : Populated U t) (hR : R ≤ m + 1) (hmt : m + 2 ≤ t) : y ∈ Base U t G ``` **G11.** Every round-`G` block a correct validator accepted into its window by `m` — Byzantine-authored included — is in the base attested at any `t ≥ m + 2`. Acceptance → the keeper's round-`(m+1)` block carries it (`viewUpto_subset_history`) → the backbone hands that block to every correct round-`t` cone → every correct author attests it, clearing `f + 1` in **every** sample. Nothing obtainable is ever filtered out. #### `card_joinIds_le` *theorem, `GC.Bootstrap.lean`* ```lean theorem card_joinIds_le {κ Λ R m t : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hED : EventuallyDelivers D R) (hw : w ∈ (Correct : Finset Validator)) (hR : R ≤ t) (hmt : m ≤ t) (hG : G ≤ t) (hΛ : t ≤ G + Λ) : (joinIds D w m t G).card ≤ (Correct : Finset Validator).card * (Λ + 1) + ((Correct : Finset Validator).card * F.f + Λ * ((Correct : Finset Validator).card * (F.f * κ))) ``` **G6b.** The joiner's entire fetch is inside one correct peer's retained store, hence bounded by the G6 constant: sync cost, not just storage, is constant at lag `Λ`. #### `bootstrap_agree` *theorem, `GC.Bootstrap.lean`* ```lean theorem bootstrap_agree {R : Properties.DagRule Validator BlockId Payload} (c : R.OnRecord ValidWrt (Correct : Finset Validator) (BlockRecord.Any (P := ValidWrt) (honest := (Correct : Finset Validator)))) (ha : Properties.Agree R) (hb : Properties.Banded R) [S : Slots Validator] {d : ℕ} (hd : G ≤ S.slotRound d) {R' m t : ℕ} (hs : Synchronised U R') (hw : w ∈ (Correct : Finset Validator)) (hcar : Populated U (m + 1)) (hpop : Populated U t) (hR : R' ≤ m + 1) (hmt : m + 2 ≤ t) {V : R.View (c.ofRec U True.intro)} {k : ℕ} {jv fv : Option BlockId} (hJ : R.Decided (S.chop G d hd) (c.chop_ofRec U True.intro ▸ c.ofView (h := True.intro) (joinView (D := D) (G := G) hs hw hcar hpop hR hmt)) k jv) (hV : R.Decided S V (d + k) fv) : jv = fv ``` **G12 (bootstrap safety).** A joiner that assembles its view from the attested base and a correct peer's window, and runs the rule on the truncation, never conflicts with any full-history validator on any slot. The composition *is* the proof: `joinView` is a view of `chop U G`, and cross-cut agreement never asked whose view it was. Stated for any rule whose universes are the block universes — the round trip of its carrier is what reads the joiner's view, built on the record, as the rule's. #### `card_serve_le` *theorem, `GC.Bootstrap.lean`* ```lean theorem card_serve_le {κ Λ n : ℕ} (hbyz : ByzBudget D κ) (hra : RefsAccepted D) (hw : w ∈ (Correct : Finset Validator)) {b : BlockId} (hb : b ∈ U.ids) (hbc : (U.block b).creator = w) (hbr : (U.block b).round = n + 1) (hG : G ≤ n) (hΛ : n ≤ G + Λ) : (history (chop U G) b).card ≤ ((Correct : Finset Validator).card * (Λ + 1) + ((Correct : Finset Validator).card * F.f + Λ * ((Correct : Finset Validator).card * (F.f * κ)))) + 1 ``` **G7, priced.** Serving cost is the G6 constant plus one. #### `chop_chop` *theorem, `GC.Horizon.lean`* ```lean theorem chop_chop {G₁ G₂ : ℕ} (hG : G₁ ≤ G₂) : chop (chop U G₁) (G₂ - G₁) = chop U G₂ ``` **G8, the composition law.** A deeper cut is just another cut: two admissible horizons are always related by the one operator, so every transfer theorem composes along the tower of truncations. #### `viewUpto_subset_viewUpto_succ` *theorem, `GC.Horizon.lean`* ```lean theorem viewUpto_subset_viewUpto_succ {R m : ℕ} (hED : EventuallyDelivers D R) (hcar : Populated U (m + 1)) (hv : v ∈ (Correct : Finset Validator)) (hw : w ∈ (Correct : Finset Validator)) (hR : R ≤ m + 1) : viewUpto D v m ⊆ viewUpto D w (m + 1) ``` **G9, the engine.** Post-`R`, everything **any** correct validator retains by round `m` is in **every** correct validator's store by `m + 1`: the keeper's round-`(m + 1)` block carries its whole store (`viewUpto_subset_history`, S10 + `includes`), and post-`R` that block is delivered to and accepted by every correct validator. Possession is universal one round deep. #### `pruned_subset_peer_store` *theorem, `GC.Horizon.lean`* ```lean theorem pruned_subset_peer_store {R m : ℕ} (hED : EventuallyDelivers D R) (hcar : Populated U (m + 1)) (hv : v ∈ (Correct : Finset Validator)) (hw : w ∈ (Correct : Finset Validator)) (hR : R ≤ m + 1) : (viewUpto D v m).filter (fun i => (U.block i).round < G) ⊆ viewUpto D w (m + 1) ``` **G9 (no desync).** What a validator prunes at any horizon, every correct peer already holds one round later: pruning below a correct frontier at depth `Λ ≥ 1` discards nothing a correct peer still lacks. A validator outside the envelope is on the bootstrap path, where the attested base takes over (G10–G12). ### Odontoceti #### `not_directSkip_of_directCommit` *theorem, `Odontoceti.Rules.lean`* ```lean theorem not_directSkip_of_directCommit (hc : DirectCommit U L r) (hk : DirectSkip U L r) : False ``` **O1 (thesis Lemma 1).** No leader block is both directly committed and directly skipped: supporters and blamers together number at most `n + f`, and two quorums are more. Needs only `n ≥ 3f+1`. #### `eq_of_directCommit` *theorem, `Odontoceti.Rules.lean`* ```lean theorem eq_of_directCommit {L₁ L₂ : BlockId} (h₁ : DirectCommit U L₁ r) (h₂ : DirectCommit U L₂ r) (hcr : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **O1′ (M5 analogue).** Two directly committed blocks by one author at one round are equal: their support quorums together exceed `n + f`. Needs only `n ≥ 3f+1`. #### `card_supporters_le_of_directSkip` *theorem, `Odontoceti.Rules.lean`* ```lean theorem card_supporters_le_of_directSkip (hk : DirectSkip U L r) : (supporters U L (r + 1)).card ≤ 2 * F.f ``` **O2, the counting half.** A directly skipped leader's supporters — anywhere in the universe — number at most `2f`: supporters and blamers together number at most `n + f`, and the blamers are `n − f`. #### `not_thickLink_of_directSkip` *theorem, `Odontoceti.Rules.lean`* ```lean theorem not_thickLink_of_directSkip (hk : DirectSkip U L r) (A : BlockId) : ¬ ThickLink U A L r ``` **O2 (thesis Lemma 2).** A directly skipped leader fails the indirect test against **every** anchor: `≤ 2f < n − 3f`. This is where `n ≥ 5f+1` is used. #### `thickLink_of_directCommit` *theorem, `Odontoceti.Rules.lean`* ```lean theorem thickLink_of_directCommit (h : DirectCommit U L r) {A : BlockId} (hA : A ∈ U.ids) (hround : r + 2 ≤ (U.block A).round) : ThickLink U A L r ``` **O3 (thesis Lemma 3) — propagation.** If `L` is directly committed, every block from two rounds above it on carries at least `n − 3f` distinct authors of support blocks in its cone — one hop by quorum intersection minus the twin discount, depth by cone monotonicity. #### `eq_of_directCommit_of_thickLink` *theorem, `Odontoceti.Rules.lean`* ```lean theorem eq_of_directCommit_of_thickLink {L₁ L₂ : BlockId} (h₁ : DirectCommit U L₁ r) (ht : ThickLink U A L₂ r) (hcr : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **O4′.** A directly committed block is the only same-author block that can pass the indirect test, at any anchor: `n−f` supporters of `L₁` and `n−3f` in-cone supporters of `L₂` would overlap in at least `n−5f ≥ 1` correct authors, each supporting two twins — impossible. #### `directCommit_of_directCommitIn` *theorem, `Odontoceti.Decision.lean`* ```lean theorem directCommit_of_directCommitIn {V : View Validator BlockId Payload U} (h : DirectCommitIn U V L r) : DirectCommit U L r ``` A view can only under-report: its direct commit is genuine. #### `directSkip_of_directSkipIn` *theorem, `Odontoceti.Decision.lean`* ```lean theorem directSkip_of_directSkipIn {V : View Validator BlockId Payload U} (h : DirectSkipIn U V L r) : DirectSkip U L r ``` A view can only under-report: its direct skip is genuine. #### `odontocetiLaws` *theorem, `Odontoceti.Decision.lean`* ```lean theorem odontocetiLaws : (odontocetiAnchored Validator BlockId Payload).Laws where commit_unique ``` **Odontoceti's laws.** #### `exists_least` *theorem, `Odontoceti.Decision.lean`* ```lean theorem exists_least {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {A : BlockId} {i k : ℕ} (_ : i < (odontocetiAnchored Validator BlockId Payload).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (odontocetiAnchored Validator BlockId Payload).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (odontocetiAnchored Validator BlockId Payload).Link i U A L S k ∧ (odontocetiAnchored Validator BlockId Payload).Least (S := S) U A i k L ``` The rung's tie is the order, so a nonempty rung has a least candidate. #### `directCommit_of_votesAt` *theorem, `Odontoceti.Liveness.lean`* ```lean theorem directCommit_of_votesAt {r : ℕ} (hcard : quorumCard Validator ≤ T.card) (hpop1 : PopulatedOn U T (r + 1)) (hv : VotesAt U T r L) : DirectCommit U L r ``` **The commit argument, stated once**: a quorum-sized `T` whose blocks one round above `r` all vote for `L` directly commits it, since a vote is a support and each `v ∈ T` has one by production. Both pacing disciplines end here. #### `directCommit_of_leader_mem` *theorem, `Odontoceti.Liveness.lean`* ```lean theorem directCommit_of_leader_mem (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound k) (hpop0 : PopulatedOn U T (S.slotRound k)) (hpop1 : PopulatedOn U T (S.slotRound k + 1)) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ DirectCommit U L (S.slotRound k) ``` **O7, commit half (thesis Lemma 8 + Corollary 9).** Post-`R`, a `T`-led slot is directly committed: `SynchronisedOn` makes every `T` block at the decision round reference the leader's block, and `T` carries a quorum. Two populated rounds — propose and decide — and one synchronised step, routed through the targeted interface. #### `decided_of_leader_mem` *theorem, `Odontoceti.Liveness.lean`* ```lean theorem decided_of_leader_mem (hcard : quorumCard Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound k) (hpop0 : PopulatedOn U T (S.slotRound k)) (hpop1 : PopulatedOn U T (S.slotRound k + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound k + 1)) (hlead : S.leader k ∈ T) : ∃ L, IsLeaderBlock U k L ∧ Decided U V k (some L) ``` **O7, as a decision** — on any view caught up to the decision round. #### `decided_of_correct_leader` *theorem, `Odontoceti.Liveness.lean`* ```lean theorem decided_of_correct_leader (hs : Synchronised U R) (hR : R ≤ S.slotRound k) (hpop0 : Populated U (S.slotRound k)) (hpop1 : Populated U (S.slotRound k + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound k + 1)) (hlead : S.leader k ∈ (Correct : Finset Validator)) : ∃ L, IsLeaderBlock U k L ∧ Decided U V k (some L) ``` The same at `T := Correct`. ### The reactive schedule #### `le_built` *theorem, `Reactive.Basic.lean`* ```lean theorem le_built {v : Validator} (hv : v ∈ T) : ∀ n ≤ rc.top v, n ≤ rc.built v n ``` Rounds advance real time, over the rounds a validator reached. #### `driftOn_of_catchup` *theorem, `Reactive.Basic.lean`* ```lean theorem driftOn_of_catchup (hcard : quorumCard Validator ≤ T.card) (hgst : rc.gst ≤ R) : DriftOn rc.built T R (rc.delay + rc.proc) N ``` **Drift is derived here too**, from the trunk's catch-up rule — the same collapse the timed discipline uses, with `le_built` supplied by `built_lt` rather than by the waiting floor. #### `built_succ_le_of_fast` *theorem, `Reactive.Basic.lean`* ```lean theorem built_succ_le_of_fast {δ : ℕ} (hδ : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ rc.holds v (rc.built ((U.block b).creator) (S.slotRound k) + δ)) (hD : ∀ u ∈ T, ∀ v ∈ T, rc.built u (S.slotRound k) ≤ rc.built v (S.slotRound k) + D) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) (hT : T ⊆ (Correct : Finset Validator)) : ∀ v ∈ T, rc.built v (S.slotRound k + 1) ≤ rc.built v (S.slotRound k) + D + δ + rc.proc ``` **Latency tracks delivery.** If every reliable round-`r` block — the leader's among them — reaches every reliable validator within `δ` of its build, then the round above is built within `D + δ + proc` of round entry: drift to the last builder, `δ` to arrive, `proc` to build. The timeout does not appear. #### `no_timeout_of_fast` *theorem, `Reactive.Basic.lean`* ```lean theorem no_timeout_of_fast {δ : ℕ} (hδ : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ rc.holds v (rc.built ((U.block b).creator) (S.slotRound k) + δ)) (hD : ∀ u ∈ T, ∀ v ∈ T, rc.built u (S.slotRound k) ≤ rc.built v (S.slotRound k) + D) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) (hT : T ⊆ (Correct : Finset Validator)) (hfast : D + δ + rc.proc < rc.timeout (S.slotRound k)) : ∀ v ∈ T, rc.built v (S.slotRound k + 1) < rc.built v (S.slotRound k) + rc.timeout (S.slotRound k) ``` **The timeout never fires.** When delivery, drift and processing together undercut the timeout, every reliable validator builds strictly before its deadline — the fallback branch of `vote_or_wait` is never taken, and consensus proceeds at network speed. ### Safe Skip: crash recovery in one message #### `hB1uniq_of_correct` *theorem, `SafeSkip.Basic.lean`* ```lean theorem hB1uniq_of_correct {v1 : Validator} {B1 : BlockId} (hB1 : B1 ∈ U.ids) (hB1c : (U.block B1).creator = v1) (hv1 : v1 ∈ (Correct : Finset Validator)) : ∀ j ∈ U.ids, (U.block j).creator = v1 → (U.block j).round = (U.block B1).round → j = B1 ``` **The boundary condition from correctness.** For a `v1` outside the Byzantine set, non-equivocation pins its round-`r0` block to `B1` — how a `SkipMsg` is built here, and what `hB1uniq` generalises for a crash-prone `v1` in the hybrid model. #### `fillBlock_valid` *theorem, `SafeSkip.Basic.lean`* ```lean theorem fillBlock_valid {k : ℕ} (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) : ValidWrt (fun b => if b ∈ U.ids then U.block b else sk.fillBlock (sk.idx b)) (sk.fillBlock k) ``` **The filled block is valid** under the extended block map: the copied references sit one round below (P1), `v1` appears among the authors exactly once (by `hB1uniq` at the boundary), the reference quorum only grows, and the added self reference is P3′. #### `reaches_fill_old` *theorem, `SafeSkip.Invariance.lean`* ```lean theorem reaches_fill_old {a b : BlockId} (ha : a ∈ U.ids) : Reaches sk.skipFill a b ↔ b ∈ U.ids ∧ Reaches U a b ``` Reachability from an old block never leaves the old ids, in either universe, and coincides between them. #### `eq_selfParent_of_mem` *theorem, `SafeSkip.Jump.lean`* ```lean theorem eq_selfParent_of_mem (hb : b ∈ U.ids) (h0 : 0 < (U.block b).round) {j : BlockId} (hj : j ∈ (U.block b).refs) (hjc : (U.block j).creator = (U.block b).creator) : j = selfParent U b ``` **The crux: the self-parent is unique.** P2 collapses any reference carrying the block's own creator onto `selfParent`. This is what turns a `SkipMsg`'s donor line from data into a derived object. #### `SkipMsg.line_eq_lineOf` *theorem, `SafeSkip.Jump.lean`* ```lean theorem SkipMsg.line_eq_lineOf (sk : SkipMsg U) : ∀ k, sk.r0 ≤ k → k ≤ sk.r → sk.line k = lineOf U (sk.line sk.r) k ``` **SS8.** A `SkipMsg`'s donor line is determined by its top block: on the interval its clauses govern, `line` coincides with the chain derived by following self-parents down from `line r`, by P2's uniqueness of the own-creator reference. #### `SkipMsg.v1_eq_of_B1` *theorem, `SafeSkip.Jump.lean`* ```lean theorem SkipMsg.v1_eq_of_B1 (sk₁ sk₂ : SkipMsg U) (hB1 : sk₁.B1 = sk₂.B1) : sk₁.v1 = sk₂.v1 ``` The recovering validator is determined by the anchor: it is the anchor's creator. #### `SkipMsg.skipFill_eq_of_core` *theorem, `SafeSkip.Jump.lean`* ```lean theorem SkipMsg.skipFill_eq_of_core [DecidableEq BlockId] (sk₁ sk₂ : SkipMsg U) (hB1 : sk₁.B1 = sk₂.B1) (hr : sk₁.r = sk₂.r) (htop : sk₁.line sk₁.r = sk₂.line sk₂.r) (hfresh : sk₁.fresh = sk₂.fresh) : sk₁.skipFill.ids = sk₂.skipFill.ids ∧ ∀ b ∈ sk₁.skipFill.ids, sk₁.skipFill.block b = sk₂.skipFill.block b ``` **SS9.** Two messages naming the same anchor and the same target, drawing fresh identifiers from the same supply, denote observationally equal universes: the identifier sets are equal and the blocks agree at every member, differing only on junk outside their identifiers. The decoder needs no hypothesis, since `block` consults `idx` only at fresh identifiers, where `hidx` pins both to the same index. #### `lineOf_mem_view` *theorem, `SafeSkip.Jump.lean`* ```lean theorem lineOf_mem_view (V : View Validator BlockId Payload U) {B2 : BlockId} (hB2 : B2 ∈ U.ids) (hB2V : B2 ∈ V.ids) : ∀ k, k ≤ (U.block B2).round → lineOf U B2 k ∈ V.ids ``` **SS10a: the receiver holds everything the elaboration reads.** Views are closed downward, so a view holding the target holds the whole derived line — the sender's message points at nothing a receiver lacks. #### `JumpMsg.denote_eq_of_core` *theorem, `SafeSkip.Jump.lean`* ```lean theorem JumpMsg.denote_eq_of_core [DecidableEq BlockId] (j₁ j₂ : JumpMsg U) (hB1 : j₁.B1 = j₂.B1) (hB2 : j₁.B2 = j₂.B2) (hfresh : j₁.fresh = j₂.fresh) : j₁.denote.ids = j₂.denote.ids ∧ ∀ b ∈ j₁.denote.ids, j₁.denote.block b = j₂.denote.block b ``` **SS10b: derivations converge.** Two jump messages with the same compact core denote observationally equal universes — since views share `U.block`, every receiver elaborating the message arrives at this one object. The elaborated lines both being the derived chain, this is SS9 applied to the elaborations. ### Integration: composing the arcs #### `honestNoEquiv_chop` *theorem, `Integration.Preservation.lean`* ```lean theorem honestNoEquiv_chop (hne : HonestNoEquiv U) : HonestNoEquiv (chop U G) ``` **I2.** Truncation preserves honest non-equivocation. #### `honestNoEquiv_skipFill` *theorem, `Integration.Preservation.lean`* ```lean theorem honestNoEquiv_skipFill (sk : SkipMsg U) (hne : HonestNoEquiv U) : HonestNoEquiv sk.skipFill ``` The Safe Skip fill in particular. #### `not_synchronisedOn_skipFill` *theorem, `Integration.Coverage.lean`* ```lean theorem not_synchronisedOn_skipFill (sk : SkipMsg U) {T : Finset Validator} {R k : ℕ} (hv1 : sk.v1 ∈ T) (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) (hk : R ≤ k) {b : BlockId} (hb : b ∈ U.ids) (hbround : (U.block b).round = k + 1) (hbc : (U.block b).creator ∈ T) : ¬ SynchronisedOn sk.skipFill T R ``` **I4, refuted.** The fill does not restore coverage: if the recovering validator is counted reliable, an old reliable block at any gap round `k+1` fails to reference the filled block at `k`. #### `synchronisedOn_skipFill_of_notMem` *theorem, `Integration.Coverage.lean`* ```lean theorem synchronisedOn_skipFill_of_notMem (sk : SkipMsg U) {T : Finset Validator} {R : ℕ} (hs : SynchronisedOn U T R) (hv1 : sk.v1 ∉ T) : SynchronisedOn sk.skipFill T R ``` **The refutation is narrow.** Exclude the recovering validator from the reliable set and coverage is untouched: the fill's blocks are its alone. #### `synchronisedOn_skipFill_above` *theorem, `Integration.Coverage.lean`* ```lean theorem synchronisedOn_skipFill_above (sk : SkipMsg U) {T : Finset Validator} {R R' : ℕ} (hs : SynchronisedOn U T R) (hR : R ≤ R') (hR' : sk.r < R') : SynchronisedOn sk.skipFill T R' ``` **I4, positively.** Coverage holds strictly above the fill: past the target round every block is old and the original condition applies unchanged. The strictness is not slack — `not_synchronisedOn_skipFill` refutes it at `n = sk.r`. #### `synchronisedOn_chop` *theorem, `Integration.Coverage.lean`* ```lean theorem synchronisedOn_chop {T : Finset Validator} {Rs R' : ℕ} (hs : LeanDag.SynchronisedOn U T Rs) (hGR : Rs ≤ G + R') : LeanDag.SynchronisedOn (chop U G) T R' ``` **Synchrony survives the cut, from the rebase** (I2). #### `slotsChop_slotsOf_eq` *theorem, `Integration.Joiner.lean`* ```lean theorem slotsChop_slotsOf_eq (hd : G ≤ S.slotRound d) (hinj : Function.Injective S.slotRound) (a : ℕ → Validator) (hd' : G ≤ (slotsOf hinj a).slotRound d) : (slotsOf hinj a).chop G d hd' = slotsOf (S := S.chop G d hd) (injective_slotRound_chop hd hinj) (fun m => a (d + m)) ``` **And as schedules.** Both sides are rebases of `slotsOf hinj a` by the same offset from the same base slot, so `Rebases.unique` would settle it; at the core the two constructions are definitionally equal. #### `joiner_run_decided_agree` *theorem, `Integration.Joiner.lean`* ```lean theorem joiner_run_decided_agree (hd : G ≤ S.slotRound d) (hs : HorizonStable P G d pick') {V : View Validator BlockId Payload U} (R : Adaptive.Run P U V) (V' : View Validator BlockId Payload (chop U G)) {W : View Validator BlockId Payload (chop U G)} {k : ℕ} {w v : Option BlockId} (hW : Decided (S := slotsOfKeyed (S := S.chop G d hd) (fun m => R.assign (d + m)) (Properties.Rebases.keyed (Validator := Validator) (MysticetiProperties.truncates_chop (U := U) (G := G) hd).toRebases R.keyed)) (chop U G) W k w) (hV : Decided (S := slotsOfKeyed R.assign R.keyed) U V (d + k) v) : w = v ``` **I5, whole.** A joiner that computed its own schedule from its own truncated view, under a horizon-stable rule, agrees with the network's run on every shared slot: *pruning does not split the ledger, even when the schedule is derived from it.* #### `anchor_pruned` *theorem, `Integration.Retention.lean`* ```lean theorem anchor_pruned (sk : SkipMsg U) (hG : (U.block sk.B1).round < G) (sk' : SkipMsg (chop U G)) : sk'.B1 ≠ sk.B1 ``` **I7a.** A horizon past the crash round prunes the anchor, so every `SkipMsg` over the truncation must name a different one. #### `no_blocks_of_no_genesis` *theorem, `Integration.Retention.lean`* ```lean theorem no_blocks_of_no_genesis {v : Validator} (hgen : ∀ b ∈ U.ids, (U.block b).creator = v → (U.block b).round ≠ 0) : ∀ b ∈ U.ids, (U.block b).creator ≠ v ``` **A severed chain cannot restart.** With no block at round `0`, a validator has no block at any round: P3′ walks every block down to genesis one round at a time, and P1 supplies the descent. #### `severed_of_pruned_anchor` *theorem, `Integration.Retention.lean`* ```lean theorem severed_of_pruned_anchor (sk : SkipMsg U) (hG1 : sk.r0 < G) (hG2 : G ≤ sk.r) : ∀ b ∈ (chop U G).ids, ((chop U G).block b).creator ≠ sk.v1 ``` **The recovering validator is severed, not merely unable to fill.** If the horizon has passed the crash round, `hgap` says the validator authored nothing in the truncation's genesis layer, so by `no_blocks_of_no_genesis` it has no block in the truncation at all. #### `outage_bounded_by_lag` *theorem, `Integration.Retention.lean`* ```lean theorem outage_bounded_by_lag (sk : SkipMsg U) {Λ : ℕ} (hlag : G + Λ = sk.r) (hr : sk.r0 ≤ sk.r) : G ≤ sk.r0 ↔ sk.r - sk.r0 ≤ Λ ``` **The lag bounds the recoverable outage.** With the horizon trailing the recovery round by `Λ`, the anchor survives exactly when the outage did not exceed `Λ`: garbage collection at lag `Λ` supports Safe Skip recovery from outages of up to `Λ` rounds, and no more. #### `extends_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem extends_addGenesis : Properties.Extends (MysticetiProperties.mysticetiRule (Payload := Payload)) V (addGenesis V v g p hg hsev) ``` **Re-genesis is an extension.** It adds one block and touches no other. #### `sustains_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem sustains_addGenesis : Properties.Sustains (MysticetiProperties.mysticetiRule (Payload := Payload)) V (addGenesis V v g p hg hsev) 0 1 ``` **And it rebases from round one at no offset.** The block it adds sits at round zero, so at and above round one the two universes hold the same blocks. #### `populatedOn_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem populatedOn_addGenesis {hg : g ∉ V.ids} {hsev : ∀ b ∈ V.ids, (V.block b).creator ≠ v} {T : Finset Validator} (hpop : PopulatedOn V T 0) : PopulatedOn (addGenesis V v g p hg hsev) (insert v T) 0 ``` **The chain restarts.** After re-genesis the stranded validator has a block at round `0`, so it is no longer severed, and an ordinary Safe Skip anchored on the new block fills the rounds above. #### `dosValid_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem dosValid_addGenesis (hdos : DoSValid V) : DoSValid (addGenesis V v g p hg hsev) ``` **I19a.** Re-genesis preserves the exposure condition. A block with no references can neither cite an exposed author nor enlarge anyone else's cone. #### `chop_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem chop_addGenesis (hd : 0 < d) {hg : g ∉ V.ids} {hsev : ∀ b ∈ V.ids, (V.block b).creator ≠ v} : (chop (addGenesis V v g p hg hsev) d).ids = (chop V d).ids ∧ ∀ b ∈ (chop V d).ids, (chop (addGenesis V v g p hg hsev) d).block b = (chop V d).block b ``` **A derived genesis is pruned by the next cut, without trace.** Any further truncation removes the round-`0` block and leaves the ordinary truncation of what lay beneath. #### `regenesis_converges` *theorem, `Integration.ReGenesis.lean`* ```lean theorem regenesis_converges {U : BlockUniverse Validator BlockId Payload} {G₁ G₂ : ℕ} (hG : G₁ < G₂) {hg : g ∉ (chop U G₁).ids} {hsev : ∀ b ∈ (chop U G₁).ids, ((chop U G₁).block b).creator ≠ v} : (chop (addGenesis (chop U G₁) v g p hg hsev) (G₂ - G₁)).ids = (chop U G₂).ids ∧ ∀ b ∈ (chop U G₂).ids, (chop (addGenesis (chop U G₁) v g p hg hsev) (G₂ - G₁)).block b = (chop U G₂).block b ``` **The convergence.** A validator at horizon `G₁`, truncating on to a later horizon `G₂`, holds exactly the blocks of a validator that cut at `G₂` directly. #### `hB1uniq_of_addGenesis` *theorem, `Integration.ReGenesis.lean`* ```lean theorem hB1uniq_of_addGenesis : ∀ j ∈ (addGenesis V v g p hg hsev).ids, ((addGenesis V v g p hg hsev).block j).creator = v → ((addGenesis V v g p hg hsev).block j).round = ((addGenesis V v g p hg hsev).block g).round → j = g ``` **The re-genesis block is a lawful Safe Skip anchor.** Uniqueness at its round is immediate from the absence that licensed it. #### `history_B1_subset_fill` *theorem, `Integration.Exposure.lean`* ```lean theorem history_B1_subset_fill (sk : SkipMsg U) (hne : sk.r0 < sk.r) : history U sk.B1 ⊆ history sk.skipFill (sk.fresh (sk.r0 + 1)) ``` The anchor's whole cone lies in the filled block's cone. Needs the gap to be nonempty, which is when there is anything to fill. #### `exposedIn_skipFill_old` *theorem, `Integration.Exposure.lean`* ```lean theorem exposedIn_skipFill_old {b : BlockId} (hb : b ∈ U.ids) {X : Validator} : ExposedIn sk.skipFill b X ↔ ExposedIn U b X ``` Exposure is unchanged at an old block, in both directions. #### `dosValid_skipFill` *theorem, `Integration.Exposure.lean`* ```lean theorem dosValid_skipFill (hdos : DoSValid U) (hnew : ∀ k, sk.r0 < k → k ≤ sk.r → ∀ i ∈ (sk.skipFill.block (sk.fresh k)).refs, ¬ ExposedIn sk.skipFill (sk.fresh k) (sk.skipFill.block i).creator) : DoSValid sk.skipFill ``` **I1.** The fill can break the exposure condition only at its own blocks. Given `DoSValid U`, the extension is `DoSValid` as soon as each filled block is sound — a condition on the fill alone. #### `fill_cone_subset` *theorem, `Integration.Exposure.lean`* ```lean theorem fill_cone_subset (sk : SkipMsg U) (hcov : ∀ k, sk.r0 < k → k ≤ sk.r → sk.B1 ∈ history U (sk.line k)) : ∀ k, sk.r0 < k → k ≤ sk.r → ∀ i, Reaches sk.skipFill (sk.fresh k) i → i ∈ sk.freshIds ∨ i ∈ history U (sk.line k) ``` #### `dosValid_skipFill_of_covered` *theorem, `Integration.Exposure.lean`* ```lean theorem dosValid_skipFill_of_covered (hdos : DoSValid U) (hcov : ∀ k, sk.r0 < k → k ≤ sk.r → sk.B1 ∈ history U (sk.line k)) (hv1ne : ∀ p ∈ U.ids, ∀ q ∈ U.ids, (U.block p).creator = sk.v1 → (U.block q).creator = sk.v1 → (U.block p).round = (U.block q).round → p = q) : DoSValid sk.skipFill ``` **I14.** The enforceable check reduces to reachability. If each donor block reaches the anchor and `v1` never equivocates, the fill preserves the exposure condition outright. #### `viewUpto_skipFillD` *theorem, `Integration.DeliveryFill.lean`* ```lean theorem viewUpto_skipFillD (v : Validator) : ∀ n, viewUpto (skipFillD sk D hdown) v n = viewUpto D v n ``` Accepted blocks are old, so their cones are unchanged and the accumulated view is literally the same finite set. #### `uniformBudget_skipFillD` *theorem, `Integration.DeliveryFill.lean`* ```lean theorem uniformBudget_skipFillD {T : ℕ} (hu : UniformBudget D T) : UniformBudget (skipFillD sk D hdown) T ``` **I15b — the budget transfers.** Novelty is measured over the cone of an accepted block against the accumulated view, both unchanged, so the author-blind budget holds at the same constant. #### `not_refsAccepted_skipFillD` *theorem, `Integration.DeliveryFill.lean`* ```lean theorem not_refsAccepted_skipFillD (hne : sk.r0 < sk.r) (hv1 : sk.v1 ∈ (Correct : Finset Validator)) : ¬ RefsAccepted (skipFillD sk D hdown) ``` **I15c — the reference discipline does not transfer, and the failure is the mechanism's own.** `RefsAccepted` says a correct validator references only what it accepted; a filled block references the donor's blocks, which `v1` did not accept while down. An alternative model, acceptance at recovery time, would satisfy both at the cost of the budget becoming a property of the fill to check rather than inherit; which model is right is a specification question, left open here. #### `notMem_of_no_blocks` *theorem, `Integration.Margin.lean`* ```lean theorem notMem_of_no_blocks {T : Finset Validator} {r : ℕ} {v : Validator} (hsev : ∀ b ∈ U.ids, (U.block b).creator ≠ v) (hpop : PopulatedOn U T r) : v ∉ T ``` A validator with no blocks belongs to no populated set: production is what membership of a reliable set asserts. #### `card_severed_le` *theorem, `Integration.Margin.lean`* ```lean theorem card_severed_le {T S : Finset Validator} {r : ℕ} (hsev : ∀ v ∈ S, ∀ b ∈ U.ids, (U.block b).creator ≠ v) (hpop : PopulatedOn U T r) (hcard : quorumCard Validator ≤ T.card) : S.card ≤ F.f ``` **I17.** At most `f` validators can be severed at once without costing liveness: a reliable set of quorum size is disjoint from every severed validator. #### `card_novelty_le_of_donor` *theorem, `Integration.Margin.lean`* ```lean theorem card_novelty_le_of_donor {κ R : ℕ} (hbyz : ByzBudget D κ) (hED : EventuallyDelivers D R) (hn : R ≤ n + 1) (hv : v ∈ (Correct : Finset Validator)) (hw : w ∈ (Correct : Finset Validator)) (hb : b ∈ U.ids) (hrefs : (U.block b).refs ⊆ D.accepted w (n + 1)) {c : BlockId} (hc : c ∈ U.ids) (hcc : (U.block c).creator = w) (hcr : (U.block c).round = n + 1) : (novelty U (viewUpto D v (n + 1)) b).card ≤ F.f * κ + 1 ``` **I18.** The novelty budget holds for a block whose references lie inside any correct validator's acceptances, provided that validator has a block at the round — not specifically the block's own author. #### `exists_commonAt` *theorem, `Integration.CommonTarget.lean`* ```lean theorem exists_commonAt {r : ℕ} {c₀ : BlockId} (hc₀ : c₀ ∈ U.ids) (hc₀r : (U.block c₀).round = r + 2) : ∃ b, CommonAt U b r ∧ (U.block b).creator ∈ (Correct : Finset Validator) ``` **Common blocks exist at every round**, and are correct-authored — T3c restated in the vocabulary above. #### `fill_refs_available` *theorem, `Integration.CommonTarget.lean`* ```lean theorem fill_refs_available (sk : SkipMsg U) (hcom : ∀ k, sk.r0 < k → k ≤ sk.r → CommonAt U (sk.line k) k) {k : ℕ} (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) {c : BlockId} (hc : c ∈ U.ids) (hcr : (U.block c).round = k + 2) {i : BlockId} (hi : i ∈ (U.block (sk.line k)).refs) : i ∈ history U c ``` **I19 — a fill against a common donor line transmits nothing.** Every reference the fill copies at a gap round lies in the causal past of every validator holding a block two rounds above it, so naming the target suffices. ### Hybrid fault tolerance: Byzantine and crash faults apart #### `committee_bound_of_admissible` *theorem, `Hybrid.Rules.lean`* ```lean theorem committee_bound_of_admissible {k : ℕ} (hk : Admissible Validator k) : 5 * H.fb + 3 * H.fc + 1 ≤ Fintype.card Validator ``` The converse: an admissible threshold forces the committee bound. Nonemptiness of the interval *is* `n ≥ 5·fb + 3·fc + 1`. #### `not_directSkip_of_directCommit` *theorem, `Hybrid.Rules.lean`* ```lean theorem not_directSkip_of_directCommit (hne : HonestNoEquiv U) (hc : DirectCommit U L r) (hk : DirectSkip U L r) : False ``` **H2 (O1's mirror).** No leader block is both directly committed and directly skipped: honest supporters and blamers together number at most `n + fb`, and two `q`-quorums are more. Needs only `n > 3·fb + 2·fc`. #### `eq_of_directCommit` *theorem, `Hybrid.Rules.lean`* ```lean theorem eq_of_directCommit (hne : HonestNoEquiv U) {L₁ L₂ : BlockId} (h₁ : DirectCommit U L₁ r) (h₂ : DirectCommit U L₂ r) (hcr : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **Twin uniqueness for direct commits (O1′'s mirror).** Needs only `n > 3·fb + 2·fc`. #### `card_supporters_le_of_directSkip` *theorem, `Hybrid.Rules.lean`* ```lean theorem card_supporters_le_of_directSkip (hne : HonestNoEquiv U) (hk : DirectSkip U L r) : (supporters U L (r + 1)).card ≤ 2 * H.fb + H.fc ``` **H3, the counting half.** A directly skipped leader's supporters — anywhere in the universe — number at most `2·fb + fc`: supporters and blamers together number at most `n + fb`, and the blamers are `q`. #### `not_thickLink_of_directSkip` *theorem, `Hybrid.Rules.lean`* ```lean theorem not_thickLink_of_directSkip (hne : HonestNoEquiv U) (hka : 2 * H.fb + H.fc + 1 ≤ k) (hk : DirectSkip U L r) (A : BlockId) : ¬ ThickLink k U A L r ``` **H3 (O2's mirror).** A directly skipped leader fails the indirect test against every anchor, at every admissible threshold: its supporters number at most `2·fb + fc`, below the interval's lower end. This is where the lower half of admissibility is consumed. #### `thickLink_of_directCommit` *theorem, `Hybrid.Rules.lean`* ```lean theorem thickLink_of_directCommit (hne : HonestNoEquiv U) (hkb : k + 3 * H.fb + 2 * H.fc ≤ Fintype.card Validator) (h : DirectCommit U L r) {A : BlockId} (hA : A ∈ U.ids) (hround : r + 2 ≤ (U.block A).round) : ThickLink k U A L r ``` **H4 (O3's mirror) — link integrity.** If `L` is directly committed, every block two or more rounds above it carries at least `k` distinct support authors in its cone: one hop is quorum intersection at `n − 3fb − 2fc ≥ k`, the interval's upper end; depth is cone monotonicity. #### `eq_of_directCommit_of_thickLink` *theorem, `Hybrid.Rules.lean`* ```lean theorem eq_of_directCommit_of_thickLink (hne : HonestNoEquiv U) (hka : 2 * H.fb + H.fc + 1 ≤ k) {L₁ L₂ : BlockId} (h₁ : DirectCommit U L₁ r) (ht : ThickLink k U A L₂ r) (hcr : (U.block L₁).creator = (U.block L₂).creator) : L₁ = L₂ ``` **H5 (O4′'s mirror).** A directly committed block is the only same-author block that can pass the indirect test at any anchor: `q` supporters of `L₁` and `k` in-cone supporters of `L₂` overlap past the Byzantine class, and an honest overlap member supporting two twins is what P2 and honesty jointly forbid. #### `directSkipIn_of_directSkipSlotIn` *theorem, `Hybrid.Decision.lean`* ```lean theorem directSkipIn_of_directSkipSlotIn {V : View Validator BlockId Payload U} {s : ℕ} (h : DirectSkipSlotIn U V s) {L : BlockId} (hL : IsLeaderBlock U s L) : DirectSkipIn U V L (S.slotRound s) ``` **The slot-level skip implies the per-candidate one**, so every theorem stated over `DirectSkipIn` — H3 in particular — applies to it unchanged. A block referencing no candidate references not `L`. #### `directCommit_of_directCommitIn` *theorem, `Hybrid.Decision.lean`* ```lean theorem directCommit_of_directCommitIn {V : View Validator BlockId Payload U} (h : DirectCommitIn U V L r) : DirectCommit U L r ``` A view can only under-report: its direct commit is genuine. #### `directSkip_of_directSkipIn` *theorem, `Hybrid.Decision.lean`* ```lean theorem directSkip_of_directSkipIn {V : View Validator BlockId Payload U} (h : DirectSkipIn U V L r) : DirectSkip U L r ``` A view can only under-report: its direct skip is genuine. #### `exists_least` *theorem, `Hybrid.Decision.lean`* ```lean theorem exists_least {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {A : BlockId} {k i s : ℕ} (_ : i < (hybridAnchored Validator BlockId Payload k).rungs) (h : ∃ L, IsLeaderBlock (S := S) U s L ∧ (hybridAnchored Validator BlockId Payload k).Link i U A L S s) : ∃ L, IsLeaderBlock (S := S) U s L ∧ (hybridAnchored Validator BlockId Payload k).Link i U A L S s ∧ (hybridAnchored Validator BlockId Payload k).Least (S := S) U A i s L ``` The rung's tie is the order, so a nonempty rung has a least candidate. #### `directCommit_of_leader_mem` *theorem, `Hybrid.Liveness.lean`* ```lean theorem directCommit_of_leader_mem (hcard : q Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ DirectCommit U L (S.slotRound s) ``` **H7, commit half (O7's mirror).** Post-`R`, a `T`-led slot is directly committed: coverage makes every `T` block at the decision round reference the leader's block, and `T` carries the quorum. Two populated rounds — propose and decide. #### `decided_of_leader_mem` *theorem, `Hybrid.Liveness.lean`* ```lean theorem decided_of_leader_mem (hcard : q Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ Decided k U V s (some L) ``` **H7, as a decision** — at every threshold `k`, on any view caught up to the decision round. #### `all_decided_below_of_fairRun` *theorem, `Hybrid.Liveness.lean`* ```lean theorem all_decided_below_of_fairRun {c : ℕ} (hc : 0 < c) (hT : T ⊆ (Correct : Finset Validator)) (hcard : q Validator ≤ T.card) (hspan : (hybridAnchored Validator BlockId Payload k).SpansEligible c) (fair : FairRunOn T c) (R : ℕ) (s : ℕ) : ∃ b, s ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ) (V : View Validator BlockId Payload U), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound (b + c - 1) + 1 ≤ N → V.CoversUpto N → ∀ i, i < b → ∃ v, Decided k U V i v ``` **H7 (O10's mirror).** Under post-`R` coverage, growth to the horizon, and a recurring run of `c` reliable-led slots, every slot below the run is decided on any view caught up to the horizon, at every threshold `k` — the reliable set excluding the crash-prone through the derived instance. #### `toHybrid_toFaults` *theorem, `Hybrid.Conservativity.lean`* ```lean theorem toHybrid_toFaults [F : Faults5 Validator] : (HybridFaults.toFaults (H := Faults5.toHybrid)) = (F.toFaults : Faults Validator) ``` The two derived instances are equal, so a block universe over the `Faults5` development *is* one over its crash-free hybrid reading, with no transport. ### Adaptive leaders: the schedule as a fixpoint #### `epochOf_add_of_dvd` *theorem, `Adaptive.Basic.lean`* ```lean theorem epochOf_add_of_dvd {W d : ℕ} (hW : 0 < W) (hdvd : W ∣ d) (k : ℕ) : epochOf W (d + k) = d / W + epochOf W k ``` **Epoch alignment.** When a base slot is a whole number of epochs, a numbering that starts there is the original shifted by a constant, and every epoch window corresponds. This is what a cut must respect under an adaptive schedule: a joiner's slot `k` is the network's `d + k`, so the two agree about which epoch a slot belongs to only when `d` falls on an epoch boundary. #### `slotsOf_base` *theorem, `Adaptive.Basic.lean`* ```lean theorem slotsOf_base (hinj : Function.Injective S.slotRound) : slotsOf hinj S.leader = S ``` The base schedule is its own induced instance — the anchor for conservativity: a constant policy reassigns nothing. #### `pickKeyed_one` *theorem, `Adaptive.Policy.lean`* ```lean theorem pickKeyed_one (pick : (U : R.Universe) → R.View U → (ℕ → Option BlockId) → ℕ → Validator) : PickKeyed (R := R) 1 pick ``` At one leader per round it is free: `k₁ / 1 = k₂ / 1` already says the slots are equal. #### `partialRun_agree` *theorem, `Adaptive.Run.lean`* ```lean theorem partialRun_agree {V₁ V₂ : R.View U} {E₁ E₂ : ℕ} (A₁ : PartialRun P U V₁ E₁) (A₂ : PartialRun P U V₂ E₂) : ∀ k, epochOf P.W k < min E₁ E₂ → A₁.vdct k = A₂.vdct k ``` **The master agreement lemma**: two partial runs over one universe, whatever views and heights, agree on the verdicts of their common epochs — the strong induction the module docstring describes. #### `run_agree` *theorem, `Adaptive.Run.lean`* ```lean theorem run_agree {V₁ V₂ : R.View U} (A₁ : Run P U V₁) (A₂ : Run P U V₂) : (∀ k, A₁.vdct k = A₂.vdct k) ∧ (∀ m, A₁.assign m = A₂.assign m) ``` **Safety: the adaptive fixpoint is unique.** Two total runs over one universe, from any two views and with no synchrony or fairness hypothesis, hold the same verdicts and run the same schedule. #### `run_commitSeq_agree` *theorem, `Adaptive.Run.lean`* ```lean theorem run_commitSeq_agree (ha : Agree R) {V₁ V₂ : R.View U} (A₁ : Run P U V₁) (A₂ : Run P U V₂) (n : ℕ) : commitSeq A₁.vdct n = commitSeq A₂.vdct n ``` **AL6 — the adaptive ledger is agreed.** The commit sequence read off any two total runs is the same list, at every length. #### `Policy.const_run_decided` *theorem, `Adaptive.Run.lean`* ```lean theorem Policy.const_run_decided {W : ℕ} {hW : 0 < W} {hinj : Function.Injective S.slotRound} {V : R.View U} (A : Run (Policy.const (R := R) W hW hinj) U V) (k : ℕ) : R.Decided S V k (A.vdct k) ``` **Conservativity.** Under the constant policy a run's verdicts are ordinary `Decided` verdicts of the base schedule. #### `descends_slotsOf` *theorem, `Adaptive.Liveness.lean`* ```lean theorem descends_slotsOf {R : DagRule Validator BlockId Payload} {wave : ℕ} (hind : Indirect R (fun sr i j => sr i + wave + 1 ≤ sr j)) {c : ℕ} (hc : 0 < c) (hspans : SpansEligibleAt (S := S) wave c) (a : ℕ → Validator) (hk : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂) : Descends R (slotsOfKeyed a hk) c ``` **A rule's descent, at every induced schedule.** The indirect property is stated over the round structure alone, which reassignment fixes, so a spanning clause at the base schedule gives the descent at each induced one. #### `exists_partialRun` *theorem, `Adaptive.Liveness.lean`* ```lean theorem exists_partialRun (hlc : LeaderCommits R Live) (hd : ∀ (a : ℕ → Validator) (h : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂), Descends R (slotsOfKeyed a h) c) (hruns : PlacesRuns P T c) (V : R.View U) (E : ℕ) (hlive : ∀ (E' : ℕ), E' < E → ∀ (A : PartialRun P U V E'), Live (slotsOfKeyed (fun m => P.pick U V A.vdct m) (P.keyed U V A.vdct)) V T P.W (P.W * (E' + 2))) : Nonempty (PartialRun P U V E) ``` **Partial runs exist at every height**, by induction: each stage re-reads the schedule off the verdicts so far and closes one more epoch. #### `epoch_closes_of_support` *theorem, `Adaptive.Liveness.lean`* ```lean theorem epoch_closes_of_support (hcom : sp.Commits rel) (hind : Indirect R (fun sr i j => sr i + sp.wave + 1 ≤ sr j)) (hc : 0 < c) (hspans : SpansEligibleAt (S := S) sp.wave c) (V : R.View U) (v : ℕ → Option BlockId) (E : ℕ) (hruns : PlacesRuns P T c) (hlive : sp.live rel (slotsOfKeyed (fun m => P.pick U V v m) (P.keyed U V v)) V T P.W (P.W * (E + 2))) : ∀ k, epochOf P.W k < E + 1 → ∃ w, DecidedBelow R (slotsOfKeyed (fun m => P.pick U V v m) (P.keyed U V v)) (P.W * (E + 2)) V k w ``` **One epoch closes**, from a support. #### `exists_partialRun_of_support` *theorem, `Adaptive.Liveness.lean`* ```lean theorem exists_partialRun_of_support (hcom : sp.Commits rel) (hind : Indirect R (fun sr i j => sr i + sp.wave + 1 ≤ sr j)) (hc : 0 < c) (hspans : SpansEligibleAt (S := S) sp.wave c) (hruns : PlacesRuns P T c) (V : R.View U) (E : ℕ) (hlive : ∀ (E' : ℕ), E' < E → ∀ (A : PartialRun P U V E'), sp.live rel (slotsOfKeyed (fun m => P.pick U V A.vdct m) (P.keyed U V A.vdct)) V T P.W (P.W * (E' + 2))) : Nonempty (PartialRun P U V E) ``` **Partial runs exist at every height**, from a support. #### `run_exists_of_support` *theorem, `Adaptive.Liveness.lean`* ```lean theorem run_exists_of_support (ha : Agree R) (hcom : sp.Commits rel) (hind : Indirect R (fun sr i j => sr i + sp.wave + 1 ≤ sr j)) (hc : 0 < c) (hspans : SpansEligibleAt (S := S) sp.wave c) (hruns : PlacesRuns P T c) (V : R.View U) (hlive : ∀ (E : ℕ) (A : PartialRun P U V E), sp.live rel (slotsOfKeyed (fun m => P.pick U V A.vdct m) (P.keyed U V A.vdct)) V T P.W (P.W * (E + 2))) : Nonempty (Run P U V) ``` **The adaptive fixpoint exists**, from a support: safety's `Agree`, the support's Law 2, the indirect rule at the support's wave, and the precondition at every height. ### Nemo-Nemo: crash-fault consensus in two rounds #### `ValidWrt.iff_validAt` *theorem, `Nemo.Basic.lean`* ```lean theorem ValidWrt.iff_validAt (blk : BlockId → Block Validator BlockId Payload) (b : Block Validator BlockId Payload) : ValidWrt blk b ↔ ValidAt (majority Validator) Clause.none blk b ``` **Nemo's validity is the family** at the majority, with no clause. #### `eq_of_mem_refs_of_creator_eq` *theorem, `Nemo.Basic.lean`* ```lean theorem eq_of_mem_refs_of_creator_eq {i j k : BlockId} (hi : i ∈ U.ids) (hj : j ∈ (U.block i).refs) (hk : k ∈ (U.block i).refs) (hc : (U.block j).creator = (U.block k).creator) : j = k ``` Distinct creators among references are automatic under crash: two refs of the same block sharing a creator sit at the same round (`predecessor`), so universal `no_equivocation` identifies them. This is why the crash `ValidWrt` has no `distinct_creators` field. #### `certifiedIn_of_directCommit` *theorem, `Nemo.Rules.lean`* ```lean theorem certifiedIn_of_directCommit {L A : BlockId} {r : ℕ} (hdc : DirectCommit U L r) (hA : A ∈ U.ids) (hAr : r + 2 ≤ (U.block A).round) : CertifiedIn U A L r ``` **Link integrity.** A directly committed leader is certified in every block at round `r+2` or above — in particular in every eligible anchor. The depth induction is the generic propagation lemma; the quorum intersection lives entirely in the base case. #### `directCommit_of_directCommitIn` *theorem, `Nemo.Decision.lean`* ```lean theorem directCommit_of_directCommitIn {V : View Validator BlockId Payload U} {L : BlockId} {r : ℕ} (h : DirectCommitIn U V L r) : DirectCommit U L r ``` A view can only under-report: its direct commit is genuine. #### `majority_le_card_live` *theorem, `Nemo.Liveness.lean`* ```lean theorem majority_le_card_live : majority Validator ≤ (Live Validator).card ``` **The bridge** — the arc's only consumer of the fault bound: the live class carries the majority quorum, since `n − f ≥ n/2 + 1` whenever `2f + 1 ≤ n`. #### `directCommit_of_leader_mem` *theorem, `Nemo.Liveness.lean`* ```lean theorem directCommit_of_leader_mem (hcard : majority Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ DirectCommit U L (S.slotRound s) ``` **The commit half.** Post-`R`, a `T`-led slot is directly committed: coverage makes every `T` block at the decision round reference the leader's block, and `T` carries the majority. Two populated rounds — propose and decide, wavelength two. #### `decided_of_leader_mem` *theorem, `Nemo.Liveness.lean`* ```lean theorem decided_of_leader_mem (hcard : majority Validator ≤ T.card) (hs : SynchronisedOn U T R) (hR : R ≤ S.slotRound s) (hpop0 : PopulatedOn U T (S.slotRound s)) (hpop1 : PopulatedOn U T (S.slotRound s + 1)) (V : View Validator BlockId Payload U) (hcov : V.CoversUpto (S.slotRound s + 1)) (hlead : S.leader s ∈ T) : ∃ L, IsLeaderBlock U s L ∧ Decided U V s (some L) ``` The commit half, as a decision — on any view caught up to the decision round. ### Mahi-Mahi: the asynchronous rule at wave w #### `certificates_eq_empty_of_directSkip` *theorem, `MahiMahi.Helpers.Rules.lean`* ```lean theorem certificates_eq_empty_of_directSkip {w : ℕ} {a : Validator} {r : ℕ} {L : BlockId} (hw : 2 ≤ w) (h : DirectSkip U w a r) (hLc : (U.block L).creator = a) (hLr : (U.block L).round = r) : certificates U w L r = ∅ ``` **Skip excludes certificates.** A quorum of blamers and a quorum of voters at the voting round share a block, which would both vote for `L` and blame its slot. #### `eq_of_certificates_nonempty` *theorem, `MahiMahi.Helpers.Rules.lean`* ```lean theorem eq_of_certificates_nonempty {w r : ℕ} {L₁ L₂ : BlockId} (hw : 2 ≤ w) (h₁ : (certificates U w L₁ r).Nonempty) (h₂ : (certificates U w L₂ r).Nonempty) (hc : (U.block L₁).creator = (U.block L₂).creator) (hr : (U.block L₁).round = (U.block L₂).round) : L₁ = L₂ ``` **Certificate uniqueness.** Two voter quorums at the voting round share a block, which votes for one candidate of a given author and round. #### `exists_certificate_reaches_of_directCommit` *theorem, `MahiMahi.Helpers.Rules.lean`* ```lean theorem exists_certificate_reaches_of_directCommit {w : ℕ} {L : BlockId} {r : ℕ} (h : DirectCommit U w L r) {c : BlockId} (hc : c ∈ U.ids) (hcr : decisionRoundAt w r + 1 ≤ (U.block c).round) : ∃ C ∈ certificates U w L r, Reaches U c C ``` **Certificates persist upward** (the paper's Lemma C.1). A block above the decision round references a certificate's correct author at the layer below, and higher blocks reach one through their references. #### `directCommit_of_directCommitIn` *theorem, `MahiMahi.Helpers.Decision.lean`* ```lean theorem directCommit_of_directCommitIn {V : View Validator BlockId Payload U} {w : ℕ} {L : BlockId} {r : ℕ} (h : DirectCommitIn U V w L r) : DirectCommit U w L r ``` #### `directSkip_of_directSkipIn` *theorem, `MahiMahi.Helpers.Decision.lean`* ```lean theorem directSkip_of_directSkipIn {V : View Validator BlockId Payload U} {w : ℕ} {a : Validator} {r : ℕ} (h : DirectSkipIn U V w a r) : DirectSkip U w a r ``` #### `certifiedIn_of_directCommit` *theorem, `MahiMahi.Helpers.Decision.lean`* ```lean theorem certifiedIn_of_directCommit {w : ℕ} {L : BlockId} {r : ℕ} (h : DirectCommit U w L r) {A : BlockId} (hA : A ∈ U.ids) (hAr : decisionRoundAt w r + 1 ≤ (U.block A).round) : CertifiedIn U w A L r ``` The commit half of M4: a directly committed candidate is certified in the cone of every block above its decision round. #### `mahiMahiLaws` *theorem, `MahiMahi.Helpers.Decision.lean`* ```lean theorem mahiMahiLaws {w : ℕ} (hw : 2 ≤ w) : (mahiMahiAnchored Validator BlockId Payload w).Laws where commit_unique ``` **Mahi-Mahi's laws** at any wave of at least two rounds: the core's M6 cases at wave `w`, every commit-against-commit case by certificate uniqueness. #### `exists_least` *theorem, `MahiMahi.Helpers.Decision.lean`* ```lean theorem exists_least {w : ℕ} {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {A : BlockId} {i k : ℕ} (_ : i < (mahiMahiAnchored Validator BlockId Payload w).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (mahiMahiAnchored Validator BlockId Payload w).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (mahiMahiAnchored Validator BlockId Payload w).Link i U A L S k ∧ (mahiMahiAnchored Validator BlockId Payload w).Least (S := S) U A i k L ``` No tie: any linked candidate is the rung's choice. #### `AgreeUpto.history` *theorem, `MahiMahi.Helpers.Liveness.lean`* ```lean theorem AgreeUpto.history (h : AgreeUpto U₁ U₂ d) {q : BlockId} (hq : q ∈ U₁.ids) (hqr : (U₁.block q).round ≤ d) : history U₁ q = history U₂ q ``` #### `holds` *theorem, `MahiMahi.Safety.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `MahiMahi.Counting.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `MahiMahi.Liveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `MahiMahi.Synchrony.Proof.lean`* ```lean theorem holds : Statement ``` ### Black Marlin: the three-round commit rule #### `coneAnchors_subsingleton` *theorem, `BlackMarlin.Helpers.Ledger.lean`* ```lean theorem coneAnchors_subsingleton (hM : M ∈ U.ids) (hMr : (U.block M).round = ρ + 1) : ∀ X ∈ coneAnchors U M ρ, ∀ Y ∈ coneAnchors U M ρ, X = Y ``` **The step is unambiguous.** At most one anchor of round `ρ` lies in the cone of a round-`(ρ + 1)` block: the round-`ρ` members of that cone are its references, and `distinct_creators` allows one block per author. No tie-break is needed where the descent steps by one round. ### FinWhale: the two-round commit rule #### `params_arith` *theorem, `FinWhale.Committee.lean`* ```lean theorem params_arith : (Correct : Finset Validator).card + F.byzantine.card = Fintype.card Validator ∧ F.byzantine.card ≤ F.f ∧ 1 ≤ P.p ∧ P.p ≤ F.f ∧ Fintype.card Validator + 1 = 3 * F.f + 2 * P.p ``` **The standing arithmetic of the committee**, in the form `omega` consumes it: the correct and Byzantine sets partition the validators, at most `f` are Byzantine, `1 ≤ p ≤ f`, and `n + 1 = 3f + 2p`. #### `spQuorum_eq_ceil` *theorem, `FinWhale.Committee.lean`* ```lean theorem spQuorum_eq_ceil : spQuorum Validator = (Fintype.card Validator + F.f + 1 + 1) / 2 ``` The paper's `⌈(n+f+1)/2⌉` is `2f + p` at this committee. #### `spQuorum_le_quorumCard` *theorem, `FinWhale.Committee.lean`* ```lean theorem spQuorum_le_quorumCard : spQuorum Validator ≤ quorumCard Validator ``` **The slow-path quorum is below the parent threshold.** `n − f` is `2f + 2p − 1` at this committee and `spQuorum` is `2f + p`, so at `p ≥ 2` a block's parent set is strictly larger than an SP-certificate quorum. The two coincide only at `p = 1`, where FinWhale's committee is the core's. Every argument that intersects a parent set with a quorum uses this direction. #### `quorumCard_le_fastCard` *theorem, `FinWhale.Committee.lean`* ```lean theorem quorumCard_le_fastCard : quorumCard Validator ≤ fastCard Validator ``` The parent threshold is itself below the fast-path threshold, since `p ≤ f`. #### `honest_voters` *theorem, `FinWhale.Counting.lean`* ```lean theorem honest_voters (voters : Finset Validator) (hvot : fastCard Validator ≤ voters.card) : 2 * F.f + P.p ≤ (voters ∩ (Correct : Finset Validator)).card + 1 ``` **The honest votes.** Of the `n − p` votes a validator sees, at least `n − p − f` are by correct validators, the same in every DAG. #### `nonequivocating_voters` *theorem, `FinWhale.Counting.lean`* ```lean theorem nonequivocating_voters {voters parents : Finset Validator} (hvot : fastCard Validator ≤ voters.card) (hpar : quorumCard Validator ≤ parents.card) : F.f + P.p ≤ (parents ∩ (voters ∩ (Correct : Finset Validator))).card + 1 ``` **Lemma 4, non-equivocating branch**: such a block references at least `f + p − 1` parents voting for `b`. #### `honest_nonvoters` *theorem, `FinWhale.Counting.lean`* ```lean theorem honest_nonvoters {voters : Finset Validator} (hvot : fastCard Validator ≤ voters.card) : ((Correct : Finset Validator) \ voters).card ≤ P.p ``` **At most `p` correct validators fail to vote**: the votes another DAG might not see and the validators `Correct` excludes are the same size, so they cancel. #### `equivocating_voters` *theorem, `FinWhale.Counting.lean`* ```lean theorem equivocating_voters {voters parents : Finset Validator} (hvot : fastCard Validator ≤ voters.card) (hpar : quorumCard Validator ≤ parents.card) (hbyz : (parents ∩ F.byzantine).card + 1 ≤ F.f) : F.f + P.p ≤ (parents ∩ (voters ∩ (Correct : Finset Validator))).card ``` **Lemma 4, equivocating branch**: such a block excludes the equivocating leader, so at most `f − 1` of its parents are Byzantine, and at most `p` of its `n − 2f + 1` honest parents fail to vote. #### `conflicting_voters_le` *theorem, `FinWhale.Counting.lean`* ```lean theorem conflicting_voters_le {voters parents conflicting : Finset Validator} (hvot : fastCard Validator ≤ voters.card) (hbyz : (parents ∩ F.byzantine).card + 1 ≤ F.f) (hdisj : ∀ v ∈ conflicting, v ∈ (Correct : Finset Validator) → v ∉ voters) : (parents ∩ conflicting).card + 1 ≤ F.f + P.p ``` **The conflicting side of Lemma 4**: such a block references at most `f + p − 1` parents voting for any block conflicting with `b`, `conflicting` being any set no correct voter for `b` belongs to. #### `equivocating_margin` *theorem, `FinWhale.Counting.lean`* ```lean theorem equivocating_margin (f p n m : ℕ) (hp : 1 ≤ p) (_hpf : p ≤ f) (hn : n + 1 = 3 * f + 2 * p) (hm : m + 2 = 3 * f + 2 * p) : (n - 2 * f + 1) - p = f + p ∧ (m - 2 * f + 1) - p + 1 = f + p ``` **The committee bound is tight**: at `n = 3f + 2p − 1` the equivocating branch clears its threshold with nothing to spare, and one validator fewer misses it by one, for every `f` and `p` in range. #### `window_margin` *theorem, `FinWhale.Counting.lean`* ```lean theorem window_margin (f p n : ℕ) (hp : 1 ≤ p) (hn : n + 1 = 3 * f + 2 * p) : 3 * f + 3 + 2 * p = n + 4 ∧ (n + 2 ≤ 3 * f + 3 ↔ p ≤ 1) ``` **Lemma 22's window against the cycle**: its `3f + 3`-round window reads as a full cycle plus two rounds exactly at `p ≤ 1`. #### `c3_reachable` *theorem, `FinWhale.Counting.lean`* ```lean theorem c3_reachable (f n j : ℕ) (hn : 2 * f ≤ n) : n - f ≤ j + 1 + f ↔ n - 2 * f - 1 ≤ j ``` **When C3 becomes reachable**: at `j` honest validators ahead of it, C3's threshold `n − f` is within reach exactly from `j = n − 2f − 1`. #### `c3_margin` *theorem, `FinWhale.Counting.lean`* ```lean theorem c3_margin (f p n : ℕ) (hp : 1 ≤ p) (hn : n + 1 = 3 * f + 2 * p) : (n - 2 * f) + 1 = f + 2 * p ∧ (n - 2 * f - 1) + 2 = f + 2 * p ∧ (p = 1 → n - 2 * f - 1 = f) ``` **And what that one member costs**: the pigeonhole margin `|H| − f` drops from `2p − 1` at the paper's `|H|` to `2p − 2` at the correct one, vanishing at `p = 1`. #### `not_refs_conflicting` *theorem, `FinWhale.Evidence.lean`* ```lean theorem not_refs_conflicting {c l l' : BlockId} (hc : c ∈ D.ids) (hconf : Conflicting D l l') (hl : l ∈ (D.block c).refs) (hl' : l' ∈ (D.block c).refs) : False ``` **No block references two blocks of one author.** Validity's `distinct_creators` says so directly. #### `exposed_not_parent` *theorem, `FinWhale.Evidence.lean`* ```lean theorem exposed_not_parent {b : BlockId} (hb : b ∈ D.ids) {w : Validator} (hexp : ExposesEquivocationBy D b w) : w ∉ parentSet D b ``` **A block that exposes a validator's equivocation drops it**: validity's first clause fails at `w`, so the second holds and `w`'s block is not a parent. #### `parents_byzantine_lt` *theorem, `FinWhale.Evidence.lean`* ```lean theorem parents_byzantine_lt {b : BlockId} {w : Validator} (hb : b ∈ D.ids) (hexp : ExposesEquivocationBy D b w) : (parentSet D b ∩ F.byzantine).card + 1 ≤ F.f ``` **The `f − 1` bound**: the equivocator is Byzantine and not a parent, so at most `f − 1` of the parents are Byzantine. #### `lemma4` *theorem, `FinWhale.Evidence.lean`* ```lean theorem lemma4 {b l : BlockId} (hb : b ∈ D.ids) (_hl : l ∈ D.ids) (hround : (D.block b).round = (D.block l).round + 2) (hfast : FastCommit D l) : FPEvidence D b l ``` **Lemma 4.** If `n − p` distinct validators vote for a leader block `l` of round `r`, every round-`(r+2)` block is FP-evidence for `l`. Both branches of the definition are met, and no schedule is needed: the rule reads the equivocation at `l`'s own author. #### `lemma8` *theorem, `FinWhale.Consequences.lean`* ```lean theorem lemma8 {l l' : BlockId} (hconf : Conflicting D l l') (h : spQuorum Validator ≤ (voters D l).card) (h' : spQuorum Validator ≤ (voters D l').card) : False ``` **Lemma 8**: at most one block of a slot gathers a quorum of votes, since two such quorums would share a correct validator who votes once. #### `no_skip_of_quorum` *theorem, `FinWhale.Skip.lean`* ```lean theorem no_skip_of_quorum {l : BlockId} (hvote : spQuorum Validator ≤ (voters D l).card) (hskip : SPSkip D l) : False ``` **Lemma 6, the slow-path side.** A quorum of votes for `l` and a quorum declining to vote for it cannot both exist: they meet in a correct validator, which does one or the other. #### `no_skip_of_fpEvidence` *theorem, `FinWhale.Skip.lean`* ```lean theorem no_skip_of_fpEvidence {l : BlockId} {slot : Finset BlockId} (hl : l ∈ slot) {ev nonev : Finset Validator} (hev : spQuorum Validator ≤ ev.card) (hnon : spQuorum Validator ≤ nonev.card) (hevb : ∀ v ∈ ev, ∃ b ∈ blocksAt D ((D.block l).round + 2), (D.block b).creator = v ∧ FPEvidence D b l) (hnonb : ∀ v ∈ nonev, ∃ b ∈ blocksAt D ((D.block l).round + 2), (D.block b).creator = v ∧ NonFPEvidence D b slot) : False ``` **Lemma 6, the fast-path side.** A quorum of FP-evidence blocks for `l` and a quorum of Non-FP-evidence blocks cannot both exist: the counts meet in `f + 1` authors, and a correct one among them writes a single round-`(r+2)` block that cannot be both. #### `no_nonFPEvidence_of_fastCommit` *theorem, `FinWhale.Skip.lean`* ```lean theorem no_nonFPEvidence_of_fastCommit {b l : BlockId} {slot : Finset BlockId} (hb : b ∈ D.ids) (hl : l ∈ D.ids) (hlslot : l ∈ slot) (hround : (D.block b).round = (D.block l).round + 2) (hfast : FastCommit D l) : ¬ NonFPEvidence D b slot ``` **Lemma 7's fast-path premise.** Under a fast commit, every round-`(r+2)` block is FP-evidence for the committed block, so no round-`(r+2)` block is Non-FP-evidence for its slot. The skip rule's second condition is then unsatisfiable, whatever the first says. #### `voters_of_directCommit` *theorem, `FinWhale.Decision.lean`* ```lean theorem voters_of_directCommit {l : BlockId} (hcom : DirectCommit D l) : spQuorum Validator ≤ (voters D l).card ``` **Every direct commit carries a quorum of voters.** The fast path by its threshold, the slow path through its certificates. #### `direct_commit_unique` *theorem, `FinWhale.Decision.lean`* ```lean theorem direct_commit_unique {r : ℕ} {l l' : BlockId} (hl : l ∈ slotBlocks S D r) (hl' : l' ∈ slotBlocks S D r) (hcom : DirectCommit D l) (hcom' : DirectCommit D l') : l = l' ``` **Corollary 11, the direct half.** Two blocks of one slot cannot both be directly committed. #### `no_directSkip_of_commit` *theorem, `FinWhale.Decision.lean`* ```lean theorem no_directSkip_of_commit {r : ℕ} {l : BlockId} (hl : l ∈ slotBlocks S D r) (hcom : DirectCommit D l) : ¬ DirectSkip S D r ``` **Lemma 6 and Lemma 7, the direct half.** A slot with a directly committed block is not directly skipped. The SP-skip half of the rule is already unsatisfiable, so the FP-evidence half is not needed. #### `indirectCommitOn_iff` *theorem, `FinWhale.Anchor.lean`* ```lean theorem indirectCommitOn_iff {A : BlockId} (hA : A ∈ D.ids) {r : ℕ} {b : BlockId} : IndirectCommitOn S D A r b ↔ IndirectCommit S D A r b ``` **They are the same condition**, for an anchor of the DAG. #### `indirect_view_independent` *theorem, `FinWhale.Anchor.lean`* ```lean theorem indirect_view_independent {A : BlockId} {S : Finset BlockId} (hS : ∀ i ∈ S, ∀ j ∈ (D.block i).refs, j ∈ S) (hA : A ∈ S) {c : BlockId} (hreach : ReachesFrom D.block A c) : c ∈ S ``` **The tie-break reads only the anchor**: a validator whose view holds it holds every block it reaches, so the condition gives the same answer wherever it is evaluated. #### `no_indirectCommit_of_fastCommit` *theorem, `FinWhale.Anchor.lean`* ```lean theorem no_indirectCommit_of_fastCommit {A : BlockId} {r : ℕ} {b b' : BlockId} (hb : b ∈ D.ids) (hb' : b' ∈ D.ids) (hbslot : b ∈ slotBlocks S D r) (hconf : Conflicting D b b') (hfast : FastCommit D b) : ¬ IndirectCommit S D A r b' ``` **A direct commit rules out an indirect commit of a conflicting block**: an SP-certificate would carry a second quorum Lemma 8 forbids, and no round-`(r+2)` block is FP-evidence for a conflicting block under a fast commit. #### `no_indirectCommit_of_directSkip` *theorem, `FinWhale.Anchor.lean`* ```lean theorem no_indirectCommit_of_directSkip {A : BlockId} {r : ℕ} {b : BlockId} (hskip : DirectSkip S D r) : ¬ IndirectCommit S D A r b ``` **A direct skip rules out an indirect commit**: either route needs a quorum the skip pattern denies, meeting its Non-FP-evidence quorum in a correct author whose one block cannot be both. #### `indirectCommit_of_directCommit` *theorem, `FinWhale.Anchor.lean`* ```lean theorem indirectCommit_of_directCommit {A : BlockId} {r : ℕ} {l : BlockId} (hA : A ∈ D.ids) (hAround : S.slotRound r + 3 ≤ (D.block A).round) (hl : l ∈ slotBlocks S D r) (hcom : DirectCommit D l) : IndirectCommit S D A r l ``` **A direct commit is visible from every anchor above it**: Lemma 7's indirect half, leaving a trail — FP-evidence or an SP-certificate — that any block at round `r + 3` or above reaches. #### `no_indirectCommit_of_directCommit` *theorem, `FinWhale.Anchor.lean`* ```lean theorem no_indirectCommit_of_directCommit {A : BlockId} {r : ℕ} {b b' : BlockId} (hb : b ∈ D.ids) (hb' : b' ∈ D.ids) (hbslot : b ∈ slotBlocks S D r) (hconf : Conflicting D b b') (hcom : DirectCommit D b) : ¬ IndirectCommit S D A r b' ``` **A direct commit rules out an indirect commit of a conflicting block**, by either path. #### `reaches_fpEvidence_quorum` *theorem, `FinWhale.Propagation.lean`* ```lean theorem reaches_fpEvidence_quorum {c l : BlockId} (hc : c ∈ D.ids) (hl : l ∈ D.ids) (hround : (D.block l).round + 3 ≤ (D.block c).round) (hfast : FastCommit D l) : ∃ ev : Finset Validator, quorumCard Validator ≤ ev.card ∧ ∀ v ∈ ev, ∃ b ∈ blocksAt D ((D.block l).round + 2), ReachesFrom D.block c b ∧ (D.block b).creator = v ∧ FPEvidence D b l ``` **Lemma 5, at any height.** Under a fast commit for `l`, every block at round `r + 3` or above reaches `n − f` round-`(r+2)` blocks, from distinct validators, all FP-evidence for `l` — reached by descending to round `r + 3` first, where Lemma 5 applies directly. #### `mem_view_of_voters` *theorem, `FinWhale.View.lean`* ```lean theorem mem_view_of_voters {c l : BlockId} (hc : c ∈ V.ids) (hcround : (D.block c).round = (D.block l).round + 2) (hvote : spQuorum Validator ≤ (voters D l).card) : l ∈ V.ids ``` **Closure, in its counting form.** A view holding one round-`(r+2)` block holds every block a quorum of round-`(r+1)` validators votes for: the two quorums meet in a correct author, whose vote and parent coincide. #### `fpEvidence_restrict` *theorem, `FinWhale.View.lean`* ```lean theorem fpEvidence_restrict {b l : BlockId} (hb : b ∈ V.ids) : FPEvidence (V.toRecord) b l ↔ FPEvidence D b l ``` **FP-evidence is view-independent** for a block the view holds. The equivocating branch bounds the parents voting for anything conflicting; a conflicting block outside the view has no such parents, and the bound holds of it for nothing. #### `directCommit_restrict` *theorem, `FinWhale.View.lean`* ```lean theorem directCommit_restrict {l : BlockId} (h : DirectCommit (V.toRecord) l) : DirectCommit D l ``` So its direct commit is one of the universe: the condition safety took as a hypothesis. #### `no_directSkip_of_commit_view` *theorem, `FinWhale.View.lean`* ```lean theorem no_directSkip_of_commit_view {r : ℕ} {l : BlockId} (hl : l ∈ slotBlocks S D r) (hcom : DirectCommit D l) : ¬ DirectSkip S (V.toRecord) r ``` **A view's direct skip is incompatible with a direct commit**: a single round-`(r+2)` block of the skip's quorum puts the committed block in the view, and Lemma 4 or Lemma 2 makes it evidence for it, which Non-FP-evidence denies. #### `no_indirectCommit_of_directSkip_view` *theorem, `FinWhale.View.lean`* ```lean theorem no_indirectCommit_of_directSkip_view {A : BlockId} {r : ℕ} {b : BlockId} (hskip : DirectSkip S (V.toRecord) r) : ¬ IndirectCommit S D A r b ``` **A view's direct skip is incompatible with an indirect commit.** Either route puts the candidate in the view, and the skip's own conditions deny it there. #### `directSkip_mono` *theorem, `FinWhale.View.lean`* ```lean theorem directSkip_mono {V' : D.View} (hsub : V.ids ⊆ V'.ids) {k : ℕ} (h : DirectSkip S (V.toRecord) k) : DirectSkip S (V'.toRecord) k ``` **The direct skip survives the view growing.** A held candidate's blames and no-evidence blocks carry over; a new candidate is referenced by no block the smaller view holds, so it collects no votes and no evidence either. #### `finWhaleLaws` *theorem, `FinWhale.View.lean`* ```lean theorem finWhaleLaws [LinearOrder BlockId] : (finWhaleAnchored Validator BlockId Payload).Laws where commit_unique ``` **FinWhale's laws.** #### `reaches_of_same_creator` *theorem, `FinWhale.Validity.lean`* ```lean theorem reaches_of_same_creator (hself : SelfParented D) {b c : BlockId} (hb : b ∈ D.ids) (hc : c ∈ D.ids) (hbc : (D.block b).creator ∈ (Correct : Finset Validator)) (hcc : (D.block c).creator = (D.block b).creator) (hle : (D.block b).round ≤ (D.block c).round) : ReachesFrom D.block c b ``` **And so a correct validator's block lies in the causal history of every later block of its own.** #### `three_correct_of_roundRobin` *theorem, `FinWhale.Rotation.lean`* ```lean theorem three_correct_of_roundRobin {leader : ℕ → Validator} (h : RoundRobin leader) (r₀ : ℕ) : ∃ r, r₀ ≤ r ∧ r < r₀ + Fintype.card Validator ∧ leader r ∈ (Correct : Finset Validator) ∧ leader (r + 1) ∈ (Correct : Finset Validator) ∧ leader (r + 2) ∈ (Correct : Finset Validator) ``` **Lemma 22, the cyclic half**: some triple of consecutive correct leaders lies within `n + 2` rounds, since otherwise every cyclic position would carry a Byzantine leader within two of it, and each Byzantine validator covers at most three positions, giving `n ≤ 3f` against the fault model. #### `exists_round_led_by` *theorem, `FinWhale.Rotation.lean`* ```lean theorem exists_round_led_by {leader : ℕ → Validator} (h : RoundRobin leader) (v : Validator) (r₀ : ℕ) : ∃ s, r₀ ≤ s ∧ s < r₀ + Fintype.card Validator ∧ leader s = v ``` **Round robin names every validator once a cycle**, within any starting round. #### `three_correct_window` *theorem, `FinWhale.Rotation.lean`* ```lean theorem three_correct_window {leader : ℕ → Validator} (h : RoundRobin leader) (hwide : 3 * F.f + 3 ≤ Fintype.card Validator) (r₀ : ℕ) : ∃ r, r₀ ≤ r ∧ r + 2 < r₀ + (3 * F.f + 3) ∧ leader r ∈ (Correct : Finset Validator) ∧ leader (r + 1) ∈ (Correct : Finset Validator) ∧ leader (r + 2) ∈ (Correct : Finset Validator) ``` **Lemma 22, the pigeonhole half**, where the window fits inside a cycle: `f + 1` disjoint triples would need `f + 1` distinct Byzantine leaders. #### `lemma22` *theorem, `FinWhale.Rotation.lean`* ```lean theorem lemma22 [P : Params Validator] {leader : ℕ → Validator} (h : RoundRobin leader) (r₀ : ℕ) : ∃ r, r₀ ≤ r ∧ r + 2 < r₀ + (3 * F.f + 3) ∧ leader r ∈ (Correct : Finset Validator) ∧ leader (r + 1) ∈ (Correct : Finset Validator) ∧ leader (r + 2) ∈ (Correct : Finset Validator) ``` **Lemma 22.** In any window of `3f + 3` rounds, round robin names three consecutive correct-led rounds — the pigeonhole where the window fits inside a cycle, the cyclic count at `p = 1` otherwise, though the paper gives only the latter. #### `spCertificate_of_certifies` *theorem, `FinWhale.Reactive.lean`* ```lean theorem spCertificate_of_certifies (hblk : D.block = U.block) {c : BlockId} (h : Certifies U c L) : SPCertificate D c L ``` **Mysticeti's certificate is FinWhale's**: both count voting parents, and FinWhale's `2f + p` is no larger than the validity quorum. #### `spCommit_of_reactive` *theorem, `FinWhale.Reactive.lean`* ```lean theorem spCommit_of_reactive (rm : ReactiveM U T N) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hgst : rm.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 2 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) : SPCommitBy D L T ``` **Lemma 20 on the reactive route**: the reliable validators' round-`(r+2)` blocks are certificates for a reliable leader's block, `n − f ≥ 2f + p` of them. #### `fastCommit_of_reactive` *theorem, `FinWhale.Reactive.lean`* ```lean theorem fastCommit_of_reactive (rc : ReactivePace U T N) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hTeq : T = (Correct : Finset Validator)) (hfew : F.byzantine.card ≤ P.p) (hgst : rc.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rc.delay + rc.proc ≤ rc.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) : FastCommit D L ``` **Theorem 21 on the reactive route**: where at most `p` validators are Byzantine, the reliable validators' votes alone are a fast commit. #### `fastCommit_latency` *theorem, `FinWhale.Reactive.lean`* ```lean theorem fastCommit_latency (rc : ReactivePace U T N) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hTeq : T = (Correct : Finset Validator)) (hfew : F.byzantine.card ≤ P.p) (hgst : rc.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rc.delay + rc.proc ≤ rc.timeout n) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) {δ : ℕ} (hδ : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ rc.holds v (rc.built ((U.block b).creator) (S.slotRound k) + δ)) : FastCommit D L ∧ ∀ v ∈ T, rc.built v (S.slotRound k + 1) ≤ rc.built v (S.slotRound k) + rc.delay + δ + 2 * rc.proc ``` **The fast commit, and when its votes are built**: under `δ`-propagation past GST and at most `p` actual faults, every correct round-`(r+1)` block votes and is built within `Δ + δ + 2·proc` of its author entering round `r`, with no timeout involved. #### `no_timeout_of_fast` *theorem, `FinWhale.Reactive.lean`* ```lean theorem no_timeout_of_fast (rc : ReactivePace U T N) (hTeq : T = (Correct : Finset Validator)) (hgst : rc.gst ≤ R) (hR : R ≤ S.slotRound k) (hN : S.slotRound k + 1 ≤ N) (hlead : S.leader k ∈ T) (hL : IsLeaderBlock U k L) {δ : ℕ} (hδ : ∀ v ∈ T, ∀ b ∈ U.ids, (U.block b).creator ∈ T → (U.block b).round = S.slotRound k → b ∈ rc.holds v (rc.built ((U.block b).creator) (S.slotRound k) + δ)) (hfast : rc.delay + δ + 2 * rc.proc < rc.timeout (S.slotRound k)) : ∀ v ∈ T, rc.built v (S.slotRound k + 1) < rc.built v (S.slotRound k) + rc.timeout (S.slotRound k) ``` **And the timeout never fires**, where actual delivery beats it. This is Definition 1's "momentarily synchronous" clause: the fallback branch of the vote rule is dead, and the round advances at network speed. #### `commits_of_reactive` *theorem, `FinWhale.Reactive.lean`* ```lean theorem commits_of_reactive (rm : ReactiveM U T N) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hround : ∀ k, S.slotRound k = k) (hfr : ∀ k, FS.slotRound k = k) (hleader : ∀ k, FS.leader k = S.leader k) (hTeq : T = (Correct : Finset Validator)) (hgst : rm.gst ≤ R) (hto : ∀ n, R ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) : CommitsCorrectLeaders FS D R N ``` **The reactive route supplies the liveness interface.** Every correct-led slot below the horizon carries a direct commit, with the schedule's two wait clauses in place of coverage. `hround` and `hleader` say the ambient slot schedule is the DAG's: one slot per round, and the same leader. #### `le_built` *theorem, `FinWhale.Creation.lean`* ```lean theorem le_built {v : Validator} (hv : v ∈ T) : ∀ n ≤ cr.top v, n ≤ cr.built v n ``` Rounds advance real time. #### `driftOn_of_catchup` *theorem, `FinWhale.Creation.lean`* ```lean theorem driftOn_of_catchup {R : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : cr.gst ≤ R) : DriftOn cr.built T R (cr.delay + cr.proc) N ``` Drift, from the trunk's catch-up rule. #### `holds_of_timeout` *theorem, `FinWhale.Creation.lean`* ```lean theorem holds_of_timeout {R n : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) (hR : R ≤ n) (hN : n + 1 ≤ N) {v : Validator} (hv : v ∈ T) (hwait : cr.built v n + cr.timeout n ≤ cr.built v (n + 1)) {b : BlockId} (hb : b ∈ U.ids) (hbT : (U.block b).creator ∈ T) (hbr : (U.block b).round = n) : b ∈ cr.holds v (cr.built v (n + 1)) ``` **A timeout-triggered builder holds every reliable block of the round below**: convergence carries it across within `delay`, and the full timeout places that arrival before the waiter's build. #### `lemma18` *theorem, `FinWhale.Creation.lean`* ```lean theorem lemma18 {R n : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) (hR : R ≤ n) (hN : n + 1 ≤ N) {L : BlockId} (hL : L ∈ U.ids) (hLr : (U.block L).round = n) (hLc : (U.block L).creator = lead n) (hlead : lead n ∈ T) : ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 1 → L ∈ (U.block c).refs ``` **Lemma 18, derived from the creation rule.** Past the coverage round, every reliable round-`(n+1)` block references a reliable leader's round-`n` block: C1 by its own L1, C2 by the timeout, C3 through a strictly earlier reliable builder the induction hypothesis covers. #### `lemma19` *theorem, `FinWhale.Creation.lean`* ```lean theorem lemma19 {R n : ℕ} (hcard : quorumCard Validator ≤ T.card) (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) (hR : R ≤ n) (hN : n + 2 ≤ N) {L : BlockId} (hL : L ∈ U.ids) (hLr : (U.block L).round = n) (hLc : (U.block L).creator = lead n) (hlead : lead n ∈ T) : ∀ v ∈ T, ∀ c ∈ U.ids, (U.block c).creator = v → (U.block c).round = n + 2 → CertifiesSP U c L ``` **Lemma 19, derived from the creation rule.** Every reliable round-`(n+2)` block carries a slow-path quorum of parents voting for a reliable leader's round-`n` block: C1's skip branch is refuted by Lemma 18, C2 by the timeout one round up, C3 through a strictly earlier reliable builder. #### `Creation.lemma20` *theorem, `FinWhale.Creation.lean`* ```lean theorem Creation.lemma20 (cr : Creation U T N S.leader) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hcard : quorumCard Validator ≤ T.card) {R n : ℕ} (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) (hR : R ≤ n) (hN : n + 2 ≤ N) {L : BlockId} (hL : L ∈ D.ids) (hLr : (D.block L).round = n) (hLc : (D.block L).creator = S.leader n) (hlead : S.leader n ∈ T) : SPCommitBy D L T ``` **Lemma 20, from the creation rule**: a reliable leader's block is committed by the slow path, at `n − f ≥ 2f + p` certifiers. #### `commits_of_creation` *theorem, `FinWhale.Creation.lean`* ```lean theorem commits_of_creation (cr : Creation U T N S.leader) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hTeq : T = (Correct : Finset Validator)) {R : ℕ} (hid : ∀ k, S.slotRound k = k) (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) : CommitsCorrectLeaders S D R N ``` **The liveness interface, from the creation rule**: every correct-led slot below the horizon carries a direct commit, with the vote and certificate clauses derived rather than assumed. #### `Creation.theorem21` *theorem, `FinWhale.Creation.lean`* ```lean theorem Creation.theorem21 (cr : Creation U T N S.leader) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hTeq : T = (Correct : Finset Validator)) (hfew : F.byzantine.card ≤ P.p) {R n : ℕ} (hgst : cr.gst ≤ R) (hto : ∀ m, R ≤ m → 2 * cr.delay + cr.proc ≤ cr.timeout m) (hR : R ≤ n) (hN : n + 1 ≤ N) {L : BlockId} (hL : L ∈ D.ids) (hLr : (D.block L).round = n) (hLc : (D.block L).creator = S.leader n) (hlead : S.leader n ∈ T) : FastCommit D L ``` **Theorem 21, from the creation rule**: where at most `p` validators are Byzantine, the reliable validators' votes alone are a fast commit. #### `held_of_pace` *theorem, `FinWhale.Holdings.lean`* ```lean theorem held_of_pace (pc : PaceCore U T M) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hle : ∀ u ∈ T, ∀ n ≤ pc.top u, n ≤ pc.built u n) (hcard : quorumCard Validator ≤ T.card) {R : ℕ} (hgst : pc.gst ≤ R) {v : Validator} (hv : v ∈ T) : ∀ n, R ≤ n → n ≤ M → ∀ b ∈ blocksAt D n, (D.block b).creator ∈ T → b ∈ pc.holds v (settled pc) ``` **And by then the view holds every reliable block from the coverage round up.** Byzantine authors are not covered, and no schedule covers them: nothing obliges a validator to receive what a faulty validator never sent. #### `leaderClause_of_dosValid` *theorem, `FinWhale.DoSBridge.lean`* ```lean theorem leaderClause_of_dosValid (hdos : DoSValid U) {b : BlockId} (hb : b ∈ U.ids) : Clause.leaderExcluded U.block (U.block b) ``` **The DoS condition implies the leader clause**: two conflicting versions an inconsistent parent set votes for are both in the block's history, so the leader is exposed and may not be cited. #### `selfParented_ofDoSValid` *theorem, `FinWhale.DoSBridge.lean`* ```lean theorem selfParented_ofDoSValid {leader : ℕ → Validator} (hdos : DoSValid U) : SelfParented (Dag.ofDoSValid U leader hdos) ``` **And the self-parent edge comes with it**, which the FinWhale model drops and Validity asks for. Theorem 26 needs no hypothesis here. #### `citable_of_correct` *theorem, `FinWhale.DoSBridge.lean`* ```lean theorem citable_of_correct {b : BlockId} (hb : b ∈ U.ids) {X : Validator} (hX : X ∈ (Correct : Finset Validator)) : ¬ ExposedIn U b X ``` **Correct authors are always citable**: an exposed author has equivocated, hence is Byzantine, so the condition never forbids citing a correct validator. #### `quorumCard_le_citable` *theorem, `FinWhale.DoSBridge.lean`* ```lean theorem quorumCard_le_citable {b : BlockId} (hb : b ∈ U.ids) : quorumCard Validator ≤ ((exposedTo U b)ᶜ).card ``` **The condition never exhausts a builder's parents.** The authors it withdraws are convicted equivocators, hence Byzantine and at most `f`, so the citable authors always include a validity quorum of `n − f`. ### Minnow: the minimal commit rule #### `quorum_of_committedAt` *theorem, `Minnow.Blocking.lean`* ```lean theorem quorum_of_committedAt : ∀ {k : ℕ} {l : BlockId}, CommittedAt D L k l → Quorum D l | 0, _, h => ``` **A commit needs a quorum**, at every position in `leaders`. #### `not_committedAt_of_dead` *theorem, `Minnow.Blocking.lean`* ```lean theorem not_committedAt_of_dead {k j : ℕ} (hj : j ≤ k) {l : BlockId} (h : ∀ v ∈ slotBlocks D (L j), ¬ Reaches D l v ∧ ¬ Quorum D v ∧ ¬ Skipped D v) : ¬ CommittedAt D L (k + 1) l ``` **A dead slot blocks every later leader.** If every vertex of the `j`-th leader slot lies outside `l`'s causal past, carries no quorum and cannot be skipped, then the second clause of `Φ*s` is unsatisfiable for `l`, whatever the recursion decides elsewhere. ### Barnacle: the adaptive leader count #### `Config.uniform_sched` *theorem, `Barnacle.Helpers.Schedule.lean`* ```lean theorem Config.uniform_sched (getLeader : ℕ → Validator) {w : ℕ} (hw : 0 < w) (hk : Keyed getLeader w) (m : ℕ) (hm : 0 < m) (hmax : m ≤ w) (I : ℕ) : (Config.uniform (leadOf getLeader) (leadKeyed_of_keyed hw hk) m hm hmax I).sched = Sched getLeader hk m hm hmax ``` **The bridge.** A uniform configuration's schedule is the schedule the arc ran on before configurations carried their leaders, so every clause stated at `Sched` is the same clause at a `Config`. #### `ofAnchored_laws` *theorem, `Barnacle.Helpers.Anchored.lean`* ```lean theorem ofAnchored_laws (hl : R.Laws) : (ofAnchored R).Laws where full_ids ``` **An anchored rule with its laws satisfies Barnacle's.** #### `progress` *theorem, `Barnacle.Helpers.Progress.lean`* ```lean theorem progress (hR : Properties.Agree R.toBaseRule.toDagRule) (hupd : UpdBounded P upd) {c K Rnd N : ℕ} {V : R.View U} (hcov : R.toBaseRule.CoversUpto U V N) (Rn : PartialRun R.toBaseRule P upd C₀ U V K) (hlive : R.LiveOn (Rn.cfg K).sched c) (hgood : R.Good U Rnd N) (hRnd : Rnd ≤ Rn.start K + 1) (hN : Rn.start K + P.maxInterval + 1 + 2 * c + R.waveLength ≤ N) : Nonempty (PartialRun R.toBaseRule P upd C₀ U V (K + 1)) ``` #### `holds` *theorem, `Barnacle.Window.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Agreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Ledger.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Conservativity.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Aimd.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Progress.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Heads.Proof.lean`* ```lean theorem holds : Statement ``` ### Hydrozoan: the dual-path rule under hybrid faults #### `ValidWrt.refs_empty_of_round_zero` *theorem, `Hydrozoan.Helpers.Block.lean`* ```lean theorem ValidWrt.refs_empty_of_round_zero {blk : BlockId → Block Replica BlockId} {b : Block Replica BlockId} (hv : ValidWrt blk b) (h0 : b.round = 0) : b.refs = ∅ ``` Genesis blocks reference nothing: at round `0` the (additive) predecessor condition is unsatisfiable — the derivation promised in the `ValidWrt` docstring. #### `holds` *theorem, `Hydrozoan.ThresholdArithmetic.Proof.lean`* ```lean theorem holds : Statement ``` #### `eq_of_certificates_nonempty` *theorem, `Hydrozoan.DirectSafety.Proof.lean`* ```lean theorem eq_of_certificates_nonempty {L₁ L₂ : BlockId} {r : ℕ} (hcreator : (U.block L₁).creator = (U.block L₂).creator) (h₁ : (certificates U L₁ r).Nonempty) (h₂ : (certificates U L₂ r).Nonempty) : L₁ = L₂ ``` Universe-level certificate-uniqueness core: two certificates' vote sets exceed `n + f`, so they share a vote block, which cites one author once. #### `holds` *theorem, `Hydrozoan.DirectSafety.Proof.lean`* ```lean theorem holds : Statement ``` #### `hydrozoanLaws` *theorem, `Hydrozoan.SlotAgreement.Proof.lean`* ```lean theorem hydrozoanLaws : (hydrozoanAnchored Replica BlockId).Laws where commit_unique ``` **Hydrozoan's laws.** #### `decided_unique` *theorem, `Hydrozoan.SlotAgreement.Proof.lean`* ```lean theorem decided_unique {V₁ : View U} {k : ℕ} {v₁ : Option BlockId} (h₁ : Decided U V₁ k v₁) : ∀ (V₂ : View U) (v₂ : Option BlockId), Decided U V₂ k v₂ → v₁ = v₂ ``` **Slot agreement**: the relation's, at Hydrozoan's laws. #### `holds` *theorem, `Hydrozoan.SlotAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Hydrozoan.PrefixAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Hydrozoan.DirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `exists_least` *theorem, `Hydrozoan.Helpers.IndirectLiveness.lean`* ```lean theorem exists_least {A : BlockId} {i k : ℕ} (hi : i < (hydrozoanAnchored Replica BlockId).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (hydrozoanAnchored Replica BlockId).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (hydrozoanAnchored Replica BlockId).Link i U A L S k ∧ (hydrozoanAnchored Replica BlockId).Least (S := S) U A i k L ``` **Each rung has a choice.** #### `holds` *theorem, `Hydrozoan.IndirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Hydrozoan.EventualDecision.Proof.lean`* ```lean theorem holds : Statement ``` #### `ledgerProgress` *theorem, `Hydrozoan.EventualDecision.Proof.lean`* ```lean theorem ledgerProgress : ∀ (Replica BlockId : Type) [Fintype Replica] [DecidableEq Replica] [DecidableEq BlockId] [LinearOrder BlockId] [LeanDag.Hydrozoan.Faults Replica] [S : Slots Replica], ∀ (T : Finset Replica) (R k c : ℕ), T ⊆ (Correct : Finset Replica) → q Replica ≤ T.card → 0 < c → (hydrozoanAnchored Replica BlockId).SpansEligible c → FairRunOn T c → ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : BlockUniverse Replica BlockId), SynchronisedOn U T R → (∀ r, S.slotRound b ≤ r → r ≤ S.slotRound (b + c - 1) + 2 → PopulatedOn U T r) → ∀ V : View U, V.CoversUpto (S.slotRound (b + c - 1) + 2) → ∀ i, i < b → ∃ v, Decided U V i v ``` **The ledger does not stall** (the composed corollary): under a fair schedule, past every slot `k` and round `R` there is a bound `b` such that any universe in which `T` is synchronised and fills the run's span has every slot below `b` decided on any view caught up to the run's last decision round. #### `holds` *theorem, `Hydrozoan.Grounding.Proof.lean`* ```lean theorem holds : Statement ``` ### Optimal-Hydrozoan: the fast path at Hydrangea's bound #### `holds` *theorem, `OptimalHydrozoan.ThresholdArithmetic.Proof.lean`* ```lean theorem holds : Statement ``` #### `OptUniverse.leader_excluded` *theorem, `OptimalHydrozoan.Helpers.Universe.lean`* ```lean theorem OptUniverse.leader_excluded (U : OptUniverse Replica BlockId) : LeaderExcluded (S := S) U.toBlockRecord ``` **An Optimal universe is leader-excluded at every schedule**: the clause at each block, read at the slot. #### `exists_least` *theorem, `OptimalHydrozoan.Helpers.Decided.lean`* ```lean theorem exists_least {A : BlockId} {i k : ℕ} (_ : i < (optimalAnchored Replica BlockId).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (optimalAnchored Replica BlockId).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (optimalAnchored Replica BlockId).Link i U A L S k ∧ (optimalAnchored Replica BlockId).Least (S := S) U A i k L ``` **Each rung has a choice**: no tie, so any linked candidate. #### `holds` *theorem, `OptimalHydrozoan.DirectSafety.Proof.lean`* ```lean theorem holds : Statement ``` #### `optimalLaws` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem optimalLaws : (optimalAnchored Replica BlockId).Laws (fun S U => LeaderExcluded (S := S) U) where commit_unique ``` **Optimal-Hydrozoan's laws**, under leader exclusion at the schedule. #### `decided_unique` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem decided_unique {V₁ : LeanDag.Hydrozoan.View U.toBlockRecord} {k : ℕ} {v₁ : Option BlockId} (h₁ : DecidedOpt U V₁ k v₁) : ∀ (V₂ : LeanDag.Hydrozoan.View U.toBlockRecord) (v₂ : Option BlockId), DecidedOpt U V₂ k v₂ → v₁ = v₂ ``` **Slot agreement**: the relation's, at Optimal's laws and the universe's exclusion. #### `holds` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.PrefixAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.DirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.IndirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.EventualDecision.Proof.lean`* ```lean theorem holds : Statement ``` #### `ledgerProgress` *theorem, `OptimalHydrozoan.EventualDecision.Proof.lean`* ```lean theorem ledgerProgress : ∀ (Replica BlockId : Type) [Fintype Replica] [DecidableEq Replica] [DecidableEq BlockId] [OptimalFaults Replica] [S : Slots Replica], ∀ (T : Finset Replica) (R k c : ℕ), T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → q Replica ≤ T.card → 0 < c → (optimalAnchored Replica BlockId).SpansEligible c → FairRunOn T c → ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : OptUniverse Replica BlockId), SynchronisedOn U.toBlockRecord T R → (∀ r, S.slotRound b ≤ r → r ≤ S.slotRound (b + c - 1) + 2 → PopulatedOn U.toBlockRecord T r) → ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, V.CoversUpto (S.slotRound (b + c - 1) + 2) → ∀ i, i < b → ∃ v, DecidedOpt U V i v ``` **The ledger does not stall** (the composed corollary): under a fair schedule, past every slot `k` and round `R` there is a bound `b` such that any Optimal universe in which `T` is synchronised and fills the run's span has every slot below `b` decided on any view caught up to the run's last decision round. #### `holds` *theorem, `OptimalHydrozoan.Grounding.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.ThresholdArithmetic.Proof.lean`* ```lean theorem holds : Statement ``` #### `OptUniverse.leader_excluded` *theorem, `OptimalHydrozoan.Helpers.Universe.lean`* ```lean theorem OptUniverse.leader_excluded (U : OptUniverse Replica BlockId) : LeaderExcluded (S := S) U.toBlockRecord ``` **An Optimal universe is leader-excluded at every schedule**: the clause at each block, read at the slot. #### `exists_least` *theorem, `OptimalHydrozoan.Helpers.Decided.lean`* ```lean theorem exists_least {A : BlockId} {i k : ℕ} (_ : i < (optimalAnchored Replica BlockId).rungs) (h : ∃ L, IsLeaderBlock (S := S) U k L ∧ (optimalAnchored Replica BlockId).Link i U A L S k) : ∃ L, IsLeaderBlock (S := S) U k L ∧ (optimalAnchored Replica BlockId).Link i U A L S k ∧ (optimalAnchored Replica BlockId).Least (S := S) U A i k L ``` **Each rung has a choice**: no tie, so any linked candidate. #### `holds` *theorem, `OptimalHydrozoan.DirectSafety.Proof.lean`* ```lean theorem holds : Statement ``` #### `optimalLaws` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem optimalLaws : (optimalAnchored Replica BlockId).Laws (fun S U => LeaderExcluded (S := S) U) where commit_unique ``` **Optimal-Hydrozoan's laws**, under leader exclusion at the schedule. #### `decided_unique` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem decided_unique {V₁ : LeanDag.Hydrozoan.View U.toBlockRecord} {k : ℕ} {v₁ : Option BlockId} (h₁ : DecidedOpt U V₁ k v₁) : ∀ (V₂ : LeanDag.Hydrozoan.View U.toBlockRecord) (v₂ : Option BlockId), DecidedOpt U V₂ k v₂ → v₁ = v₂ ``` **Slot agreement**: the relation's, at Optimal's laws and the universe's exclusion. #### `holds` *theorem, `OptimalHydrozoan.SlotAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.PrefixAgreement.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.DirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.IndirectLiveness.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `OptimalHydrozoan.EventualDecision.Proof.lean`* ```lean theorem holds : Statement ``` #### `ledgerProgress` *theorem, `OptimalHydrozoan.EventualDecision.Proof.lean`* ```lean theorem ledgerProgress : ∀ (Replica BlockId : Type) [Fintype Replica] [DecidableEq Replica] [DecidableEq BlockId] [OptimalFaults Replica] [S : Slots Replica], ∀ (T : Finset Replica) (R k c : ℕ), T ⊆ (LeanDag.Hydrozoan.Correct : Finset Replica) → q Replica ≤ T.card → 0 < c → (optimalAnchored Replica BlockId).SpansEligible c → FairRunOn T c → ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : OptUniverse Replica BlockId), SynchronisedOn U.toBlockRecord T R → (∀ r, S.slotRound b ≤ r → r ≤ S.slotRound (b + c - 1) + 2 → PopulatedOn U.toBlockRecord T r) → ∀ V : LeanDag.Hydrozoan.View U.toBlockRecord, V.CoversUpto (S.slotRound (b + c - 1) + 2) → ∀ i, i < b → ∃ v, DecidedOpt U V i v ``` **The ledger does not stall** (the composed corollary): under a fair schedule, past every slot `k` and round `R` there is a bound `b` such that any Optimal universe in which `T` is synchronised and fills the run's span has every slot below `b` decided on any view caught up to the run's last decision round. #### `holds` *theorem, `OptimalHydrozoan.Grounding.Proof.lean`* ```lean theorem holds : Statement ``` ### Not otherwise grouped #### `Rebases.keyed` *theorem, `Adaptive.Joiner.lean`* ```lean theorem Rebases.keyed {S S' : Slots Validator} {G d : ℕ} (h : Rebases S S' G d) {a : ℕ → Validator} (hk : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂) : ∀ k₁ k₂, S'.slotRound k₁ = S'.slotRound k₂ → a (d + k₁) = a (d + k₂) → k₁ = k₂ ``` A rebase preserves the schedule law: the rebased rounds are the original's, shifted, so slots sharing a rebased round shared one before, where the assignment already separated them. #### `Rebases.slotsOfKeyed` *theorem, `Adaptive.Joiner.lean`* ```lean theorem Rebases.slotsOfKeyed {S S' : Slots Validator} {G d : ℕ} (h : Rebases S S' G d) (a : ℕ → Validator) (hk : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂) : Rebases (slotsOfKeyed (S := S) a hk) (slotsOfKeyed (S := S') (fun m => a (d + m)) (h.keyed hk)) G d where slotRound ``` **Rebasing commutes with adapting**, at a lawful assignment. #### `Truncates.slotsOfKeyed` *theorem, `Adaptive.Joiner.lean`* ```lean theorem Truncates.slotsOfKeyed {U U' : R.Universe} {S S' : Slots Validator} {G d : ℕ} (h : Truncates R U U' S S' G d) (a : ℕ → Validator) (hk : ∀ k₁ k₂, S.slotRound k₁ = S.slotRound k₂ → a k₁ = a k₂ → k₁ = k₂) : Truncates R U U' (slotsOfKeyed (S := S) a hk) (slotsOfKeyed (S := S') (fun m => a (d + m)) (h.toRebases.keyed hk)) G d ``` And so a cut at the base schedule is a cut at the adaptive one. #### `Rebases.slotsOf` *theorem, `Adaptive.Joiner.lean`* ```lean theorem Rebases.slotsOf {S S' : Slots Validator} {G d : ℕ} (h : Rebases S S' G d) (hinj : Function.Injective S.slotRound) (a : ℕ → Validator) : Rebases (slotsOf (S := S) hinj a) (slotsOf (S := S') (h.injective hinj) (fun m => a (d + m))) G d where slotRound ``` **Rebasing commutes with adapting.** Rebase a schedule and then install an assignment shifted past the base slot, or install the assignment first and rebase: the same rounds, the same leaders. #### `Truncates.slotsOf` *theorem, `Adaptive.Joiner.lean`* ```lean theorem Truncates.slotsOf {U U' : R.Universe} {S S' : Slots Validator} {G d : ℕ} (h : Truncates R U U' S S' G d) (hinj : Function.Injective S.slotRound) (a : ℕ → Validator) : Truncates R U U' (slotsOf (S := S) hinj a) (slotsOf (S := S') (h.toRebases.injective hinj) (fun m => a (d + m))) G d ``` And so a cut at the base schedule is a cut at the adaptive one. #### `joiner_run_decided_agree` *theorem, `Adaptive.Joiner.lean`* ```lean theorem joiner_run_decided_agree (ha : Agree R) (hb : Banded R) (hs : HorizonStable P G d pick') {U U' : R.Universe} (ht : Truncates R U U' S S' G d) {V : R.View U} (A : Run P U V) (V' : R.View U') {V₀ : R.View U'} (hv : ViewAgreeAbove R V V₀ G) {W : R.View U'} {k : ℕ} {w v : Option BlockId} (hk' : ∀ k₁ k₂, S'.slotRound k₁ = S'.slotRound k₂ → pick' U' V' (fun j => A.vdct (d + j)) k₁ = pick' U' V' (fun j => A.vdct (d + j)) k₂ → k₁ = k₂) (hW : R.Decided (slotsOfKeyed (S := S') (fun m => pick' U' V' (fun j => A.vdct (d + j)) m) hk') W k w) (hV : R.Decided (slotsOfKeyed A.assign A.keyed) V (d + k) v) : w = v ``` **The joiner, whole**: under a horizon-stable rule, a joiner's own computed schedule agrees with the network's run on every shared slot. #### `holds` *theorem, `Barnacle.Healthy.Proof.lean`* ```lean theorem holds : Statement ``` #### `coversUpto_full` *theorem, `Barnacle.Helpers.Cover.lean`* ```lean theorem coversUpto_full (hfull : ∀ U : R.Universe, R.viewIds (R.full U) = R.ids U) (U : R.Universe) (N : ℕ) : R.CoversUpto U (R.full U) N ``` **The full view is caught up to every horizon.** Only the view law pinning `full` is needed, which for a rule built by `ofAnchored` is `rfl`. #### `delivers_core` *theorem, `Barnacle.Helpers.Delivery.lean`* ```lean theorem delivers_core [F : Faults Validator] (R : AnchoredRule Validator BlockId Payload ValidWrt (Correct : Finset Validator)) : (liveOfAnchored R (coreReliability Validator)).Delivers F.f where reaches ``` **Every core rule delivers**, at slack `f`. #### `holds` *theorem, `Barnacle.Live.Proof.lean`* ```lean theorem holds : Statement ``` #### `holds` *theorem, `Barnacle.Validity.Proof.lean`* ```lean theorem holds : Statement ``` #### `agree` *theorem, `Common.Anchored.Band.lean`* ```lean theorem agree (hl : R.Laws) : Agree R.toDagRule ``` **Two views decide alike.** #### `indirect` *theorem, `Common.Anchored.Band.lean`* ```lean theorem indirect (hcongr : R.LinkCongr) (hleast : ∀ {S : Slots Validator} {U : BlockRecord Validator BlockId Payload P honest} {A : BlockId} {i k : ℕ}, i < R.rungs → (∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k) → ∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k ∧ R.Least (S := S) U A i k L) : Indirect R.toDagRule (fun sr i j => sr i + R.wave + 1 ≤ sr j) ``` **The indirect rule is a property.** Given the anchor, the verdict is determined by the rungs: the first rung holding a candidate commits the tie-break's choice, and no rung holding any skips. The verdict survives a reassignment of leaders elsewhere, since the case split reads only slot `i`'s candidates and the anchor's history. What it needs of the tie is that a nonempty rung has a choice, `hleast`. #### `agree` *theorem, `Common.Anchored.Bounded.lean`* ```lean theorem agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {B₁ B₂ k : ℕ} {v₁ v₂ : Option BlockId} (h₁ : R.DecidedWithin U V₁ B₁ k v₁) (h₂ : R.DecidedWithin U V₂ B₂ k v₂) : v₁ = v₂ ``` Two bounded verdicts agree — agreement, through the embedding. #### `decidedWithin_congr_of_slotRound` *theorem, `Common.Anchored.Bounded.lean`* ```lean theorem decidedWithin_congr_of_slotRound (hl : R.Laws I) {S₁ S₂ : Slots Validator} (hI : I S₁ U) (hround : S₁.slotRound = S₂.slotRound) {V : U.View} {B k : ℕ} {v : Option BlockId} (ha : ∀ m, m < B → S₁.leader m = S₂.leader m) (h : R.DecidedWithin (S := S₁) U V B k v) : R.DecidedWithin (S := S₂) U V B k v ``` **The bounded relation moves with the schedule**, for any two schedules naming the same rounds and the same leaders below the bound: the candidate set reads the schedule only through `IsLeaderBlock`, the direct skip only at its slot, and the links not at all. #### `decided_below_of_committed_run` *theorem, `Common.Anchored.Bounded.lean`* ```lean theorem decided_below_of_committed_run (hleast : ∀ {A : BlockId} {i k : ℕ}, i < R.rungs → (∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k) → ∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k ∧ R.Least (S := S) U A i k L) {V : U.View} {b n : ℕ} (hbn : b ≤ n) (hspan : ∀ i, i < b → R.Eligible (S := S) i n) (hrun : ∀ j, b ≤ j → j ≤ n → ∃ L, R.Decided (S := S) U V j (some L)) : ∀ i, i < b → ∃ v, R.Decided (S := S) U V i v ``` **The descent, unbounded**: every derivation is bounded, so the run sits within one bound and the bounded descent applies. #### `total_of_least` *theorem, `Common.Anchored.Bounded.lean`* ```lean theorem total_of_least (hleast : ∀ {A : BlockId} {i k : ℕ}, i < R.rungs → (∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k) → ∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k ∧ R.Least (S := S) U A i k L) : R.Total (S := S) U ``` Totality, from a choice at every nonempty rung. #### `decidedBelowRun_of_least` *theorem, `Common.Anchored.Bounded.lean`* ```lean theorem decidedBelowRun_of_least (hleast : ∀ {A : BlockId} {i k : ℕ}, i < R.rungs → (∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k) → ∃ L, IsLeaderBlock (S := S) U k L ∧ R.Link i U A L S k ∧ R.Least (S := S) U A i k L) : R.DecidedBelowRun (S := S) U ``` The descent below a committed run, from a choice at every nonempty rung. #### `eligibleAt_of_lt_of_spacing` *theorem, `Common.Anchored.lean`* ```lean theorem eligibleAt_of_lt_of_spacing {wave : ℕ} (hsp : ∀ k, S.slotRound k + wave + 1 ≤ S.slotRound (k + 1)) {k j : ℕ} (h : k < j) : EligibleAt (S := S) wave k j ``` Under a schedule whose consecutive slots are spaced past the wave, every later slot is an eligible anchor. #### `spansEligible_of_identity` *theorem, `Common.Anchored.lean`* ```lean theorem spansEligible_of_identity (hid : ∀ s, S.slotRound s = s) : R.SpansEligible (R.wave + 1) ``` Under an identity-round schedule, `wave + 1` consecutive slots span. #### `Decided.indirectCommit_single` *theorem, `Common.Anchored.lean`* ```lean theorem Decided.indirectCommit_single {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} (h1 : R.rungs = 1) (hno : ∀ L L', ¬ R.tie 0 L L') {k j : ℕ} {A L : BlockId} (hkj : k < j) (helig : R.Eligible k j) (hj : R.Decided U V j (some A)) (hmid : ∀ m, k < m → m < j → R.Eligible k m → R.Decided U V m none) (hL : IsLeaderBlock U k L) (hlink : R.Link 0 U A L S k) : R.Decided U V k (some L) ``` **The indirect commit at a single rung with no tie**: the shape the core, Nemo and Mahi-Mahi take. #### `Decided.indirectSkip_single` *theorem, `Common.Anchored.lean`* ```lean theorem Decided.indirectSkip_single {U : BlockRecord Validator BlockId Payload P honest} {V : U.View} (h1 : R.rungs = 1) {k j : ℕ} {A : BlockId} (hkj : k < j) (helig : R.Eligible k j) (hj : R.Decided U V j (some A)) (hmid : ∀ m, k < m → m < j → R.Eligible k m → R.Decided U V m none) (hnone : ∀ L, IsLeaderBlock U k L → ¬ R.Link 0 U A L S k) : R.Decided U V k none ``` **The indirect skip at a single rung.** #### `eq_of_indirect` *theorem, `Common.Anchored.lean`* ```lean theorem eq_of_indirect (hl : R.Laws I) (hI : I S U) {k j i₁ i₂ : ℕ} {L₁ L₂ A : BlockId} (hL₁ : IsLeaderBlock U k L₁) (hL₂ : IsLeaderBlock U k L₂) (hA : IsLeaderBlock U j A) (helig : R.Eligible k j) (hi₁ : i₁ < R.rungs) (hemp₁ : ∀ i', i' < i₁ → R.RungEmpty U A i' k) (hlink₁ : R.Link i₁ U A L₁ S k) (hmin₁ : R.Least U A i₁ k L₁) (hi₂ : i₂ < R.rungs) (hemp₂ : ∀ i', i' < i₂ → R.RungEmpty U A i' k) (hlink₂ : R.Link i₂ U A L₂ S k) (hmin₂ : R.Least U A i₂ k L₂) : L₁ = L₂ ``` Two tie-break choices at two rungs from one anchor are one block: at one rung by `link_unique`, and at different rungs the higher rung's emptiness premise contradicts the lower rung's link. #### `decided_unique` *theorem, `Common.Anchored.lean`* ```lean theorem decided_unique (hl : R.Laws I) (hI : I S U) {V₁ : U.View} {k : ℕ} {v₁ : Option BlockId} (h₁ : R.Decided U V₁ k v₁) : ∀ (V₂ : U.View) (v₂ : Option BlockId), R.Decided U V₂ k v₂ → v₁ = v₂ ``` **Agreement.** No two validators reach conflicting decisions for a slot. Structural induction on the first derivation: commit-against-commit closes by a uniqueness law, direct-against-indirect by visibility or the skip law, and indirect-against-indirect by comparing the two anchors. #### `decided_agree` *theorem, `Common.Anchored.lean`* ```lean theorem decided_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {k : ℕ} {v₁ v₂ : Option BlockId} (h₁ : R.Decided U V₁ k v₁) (h₂ : R.Decided U V₂ k v₂) : v₁ = v₂ ``` Agreement, in the shape callers want. #### `eq_of_decided_commit` *theorem, `Common.Anchored.lean`* ```lean theorem eq_of_decided_commit (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {k : ℕ} {L₁ L₂ : BlockId} (h₁ : R.Decided U V₁ k (some L₁)) (h₂ : R.Decided U V₂ k (some L₂)) : L₁ = L₂ ``` No two validators commit *different* blocks for one slot. #### `not_decided_skip_of_decided_commit` *theorem, `Common.Anchored.lean`* ```lean theorem not_decided_skip_of_decided_commit (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {k : ℕ} {L : BlockId} (h₁ : R.Decided U V₁ k (some L)) (h₂ : R.Decided U V₂ k none) : False ``` No validator commits a slot another has skipped. #### `decided_full` *theorem, `Common.Anchored.lean`* ```lean theorem decided_full (hl : R.Laws I) (hI : I S U) {V : U.View} {k : ℕ} {v : Option BlockId} (h : R.Decided U V k v) : R.Decided U (BlockRecord.View.full U) k v ``` Whatever any validator decides on any view, the same verdict holds on the full view. #### `commitSeq_agree` *theorem, `Common.Anchored.lean`* ```lean theorem commitSeq_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {n : ℕ} {g₁ g₂ : ℕ → Option BlockId} (h₁ : ∀ k, k < n → R.Decided U V₁ k (g₁ k)) (h₂ : ∀ k, k < n → R.Decided U V₂ k (g₂ k)) : commitSeq g₁ n = commitSeq g₂ n ``` **The committed-leader sequence is agreed.** #### `ledgerSet_agree` *theorem, `Common.Anchored.lean`* ```lean theorem ledgerSet_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {n : ℕ} {g₁ g₂ : ℕ → Option BlockId} (h₁ : ∀ k, k < n → R.Decided U V₁ k (g₁ k)) (h₂ : ∀ k, k < n → R.Decided U V₂ k (g₂ k)) : ledgerSet U g₁ n = ledgerSet U g₂ n ``` **Two validators output the same blocks.** #### `outputAt_agree` *theorem, `Common.Anchored.lean`* ```lean theorem outputAt_agree (hl : R.Laws I) (hI : I S U) {V₁ V₂ : U.View} {n : ℕ} {g₁ g₂ : ℕ → Option BlockId} {b : BlockId} {k : ℕ} (h₁ : ∀ j, j < n → R.Decided U V₁ j (g₁ j)) (h₂ : ∀ j, j < n → R.Decided U V₂ j (g₂ j)) (hk : k < n) (ho : OutputAt U g₁ b k) : OutputAt U g₂ b k ``` **And validators agree on which slot a block enters at.** #### `View.coversUpto_full` *theorem, `Common.BlockRecord.lean`* ```lean theorem View.coversUpto_full (U : BlockRecord Validator BlockId Payload P honest) (N : ℕ) : (View.full U).CoversUpto N ``` The full view is caught up to every horizon. #### `Mechanised.of_iff` *theorem, `Common.BlockRecord.lean`* ```lean theorem Mechanised.of_iff {Q : Validity Validator BlockId Payload} [Q.Mechanised] (h : ∀ blk b, P blk b ↔ Q blk b) : P.Mechanised where pred ``` The obligations transfer along an equivalence of predicates. #### `distinct_creators` *theorem, `Common.BlockRecord.lean`* ```lean theorem distinct_creators [P.Distinct] {i j k : BlockId} (hi : i ∈ U.ids) (hj : j ∈ (U.block i).refs) (hk : k ∈ (U.block i).refs) (hc : (U.block j).creator = (U.block k).creator) : j = k ``` Two references of one block by one author are one reference. #### `ledgerSet_mono` *theorem, `Common.Ledger.lean`* ```lean theorem ledgerSet_mono {g : ℕ → Option BlockId} {n m : ℕ} (h : n ≤ m) : ledgerSet U g n ⊆ ledgerSet U g m ``` **Nothing is ever dropped.** The ledger only grows as more slots settle. #### `ledgerSet_agree_of` *theorem, `Common.Ledger.lean`* ```lean theorem ledgerSet_agree_of {g₁ g₂ : ℕ → Option BlockId} {n : ℕ} (hg : ∀ k, k < n → g₁ k = g₂ k) : ledgerSet U g₁ n = ledgerSet U g₂ n ``` Two assignments that agree below `n` output the same blocks. #### `outputAt_unique` *theorem, `Common.Ledger.lean`* ```lean theorem outputAt_unique {g : ℕ → Option BlockId} {b : BlockId} {k₁ k₂ : ℕ} (h₁ : OutputAt U g b k₁) (h₂ : OutputAt U g b k₂) : k₁ = k₂ ``` **A block enters the ledger once.** Its position is not merely stable over time — there is no second slot it could have entered at. #### `outputAt_agree_of` *theorem, `Common.Ledger.lean`* ```lean theorem outputAt_agree_of {g₁ g₂ : ℕ → Option BlockId} {n : ℕ} {b : BlockId} {k : ℕ} (hg : ∀ j, j < n → g₁ j = g₂ j) (hk : k < n) (ho : OutputAt U g₁ b k) : OutputAt U g₂ b k ``` Two assignments that agree below `n` concur on the slot a block enters at. #### `waveRobin_fairRun` *theorem, `Common.WaveRobin.lean`* ```lean theorem waveRobin_fairRun (n : ℕ) (hn : 0 < n) [F : Faults (Fin n)] : FairRunOn (S := waveRobin n hn) (Correct : Finset (Fin n)) 3 ``` **A fair schedule exists — wave-aligned rotation, unconditionally.** The witness for slot `k` is a correct validator `v`'s wave in the `k`-th rotation cycle: slot `3 * (v + n * k)` opens a wave led by `v`, past `k`, with all three slots `v`-led. #### `waveRobin_spansEligible` *theorem, `Common.WaveRobin.lean`* ```lean theorem waveRobin_spansEligible (n : ℕ) (hn : 0 < n) : SpansEligibleAt (S := waveRobin n hn) 2 3 ``` **`SpansEligibleAt 2 3`, the core's pipelined shape, at every `n`.** A run of three consecutive slots reaches three rounds past everything below it. #### `waveRobin_fairSchedule` *theorem, `Common.WaveRobin.lean`* ```lean theorem waveRobin_fairSchedule (n : ℕ) (hn : 0 < n) [F : Faults (Fin n)] : FairScheduleOn (S := waveRobin n hn) (Correct : Finset (Fin n)) ``` The wave-aligned rotation is fair in the single-slot sense too, so L6 and the `ViewPace` results apply to it unchanged. #### `directSkip` *theorem, `FinWhale.Band.lean`* ```lean theorem directSkip (hrk : S.slotRound k + g = S'.slotRound k' + g') (hlk : S.leader k = S'.leader k') (h1 : lo ≤ S.slotRound k + g) (h2 : S.slotRound k + g + 2 ≤ hi) (h : DirectSkip S D k) : DirectSkip S' D' k' ``` **The direct skip survives the band**, new candidates and all. #### `agree` *theorem, `FinWhale.Carrier.lean`* ```lean theorem agree : Agree (finWhaleRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **Two views decide alike.** Lemma 12 under the property's name: the relation's agreement at FinWhale's laws. #### `indirect` *theorem, `FinWhale.Carrier.lean`* ```lean theorem indirect : Indirect (finWhaleRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) (fun sr i j => sr i + 3 ≤ sr j) ``` **The indirect rule, with its bound.** The relation's indirect property at the rung's choice, read at the three-round eligibility: every rule FinWhale applies at a slot reads the schedule at that slot alone, which is the relation's `link_congr`. #### `spQuorum_le_quorumCard` *theorem, `FinWhale.Carrier.lean`* ```lean theorem spQuorum_le_quorumCard : LeanDag.FinWhale.spQuorum Validator ≤ quorumCard Validator ``` The slow-path quorum fits inside any quorum of the fault model: `n + 1 = 3f + 2p` with `p ≥ 1` gives `2f + p ≤ n − f`. #### `safety` *theorem, `FinWhale.Carrier.lean`* ```lean theorem safety : Properties.Safe (finWhaleRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` #### `progress` *theorem, `FinWhale.Carrier.lean`* ```lean theorem progress : Properties.Support.Progresses (fwSupport (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) (coreReliability Validator) ``` #### `exists_least` *theorem, `FinWhale.Least.lean`* ```lean theorem exists_least {A : BlockId} {i k : ℕ} (_ : i < (finWhaleAnchored Validator BlockId Payload).rungs) (h : ∃ L, IsLeaderBlock (S := S) D k L ∧ (finWhaleAnchored Validator BlockId Payload).Link i D A L S k) : ∃ L, IsLeaderBlock (S := S) D k L ∧ (finWhaleAnchored Validator BlockId Payload).Link i D A L S k ∧ (finWhaleAnchored Validator BlockId Payload).Least (S := S) D A i k L ``` **The rung has a choice.** #### `chooseSound_least` *theorem, `FinWhale.Procedure.Consistency.lean`* ```lean theorem chooseSound_least : ChooseSound S D (chooseLeast S D) where sound ``` The exhibited tie-break names only candidates, and one whenever there is one. #### `chooseLeast_least` *theorem, `FinWhale.Procedure.Consistency.lean`* ```lean theorem chooseLeast_least {A : BlockId} {r : ℕ} {b : BlockId} (h : chooseLeast S D A r = some b) : (finWhaleAnchored Validator BlockId Payload).Least (S := S) D A 0 r b ``` And what it names is the least candidate: the relation's choice at the rung. #### `lemma23` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem lemma23 {dc : ℕ → BlockId → Prop} {ds : ℕ → Prop} {choose : BlockId → ℕ → Option BlockId} {dec : ℕ → Verdict BlockId} (hEl : ∀ r a, Elig r a ↔ r + 2 < a) (hwf : WellFormed Elig dc ds choose dec) {r a : ℕ} (hra : r < a) (htri : ∀ s, a ≤ s → s ≤ a + 2 → dec s ≠ Verdict.undecided ∧ dec s ≠ Verdict.skip) : dec r ≠ Verdict.undecided ``` **Lemma 23.** Every slot below a committed triple is decided: taking the highest undecided slot below it, the first non-skipped slot above it is a commit and serves as its anchor. #### `committed_triple` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem committed_triple {dc : ℕ → BlockId → Prop} {ds : ℕ → Prop} {choose : BlockId → ℕ → Option BlockId} {dec : ℕ → Verdict BlockId} (hwf : WellFormed Elig dc ds choose dec) {R N t : ℕ} (hsees : SeesCommits S D dc R N) (hrr : RoundRobin S.leader) (hid : ∀ s, S.slotRound s = s) (hR : R ≤ t) (hN : t + (3 * F.f + 5) ≤ N) : ∃ a, t < a ∧ a + 4 ≤ N ∧ ∀ s, a ≤ s → s ≤ a + 2 → dec s ≠ Verdict.undecided ∧ dec s ≠ Verdict.skip ``` **A committed triple above every round.** #### `all_decided` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem all_decided {dc : ℕ → BlockId → Prop} {ds : ℕ → Prop} {choose : BlockId → ℕ → Option BlockId} {dec : ℕ → Verdict BlockId} (hwf : WellFormed Elig dc ds choose dec) {R N r : ℕ} (hsees : SeesCommits S D dc R N) (hrr : RoundRobin S.leader) (hEl : ∀ r a, Elig r a ↔ r + 2 < a) (hid : ∀ s, S.slotRound s = s) (hN : max r R + (3 * F.f + 5) ≤ N) : dec r ≠ Verdict.undecided ``` **Lemma 23, composed.** Every slot below the horizon is decided, including those before GST, decided from an anchor above them; only the triple must sit past the coverage round, hence the maximum. #### `theorem24` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem theorem24 {dec dec' : ℕ → Verdict BlockId} {k : ℕ} (hagree : ∀ s, dec s ≠ Verdict.undecided → dec' s ≠ Verdict.undecided → dec s = dec' s) (hdec : ∀ s, s < k → dec s ≠ Verdict.undecided) (hdec' : ∀ s, s < k → dec' s ≠ Verdict.undecided) (hist : BlockId → List BlockId) : linearise hist (commitSeq dec k) = linearise hist (commitSeq dec' k) ``` **Theorem 24 (Agreement)**: two validators that have decided every slot below `k` deliver the same sequence, from Lemma 12's agreement through `commitSeq_congr`. #### `mem_histOf` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem mem_histOf [LinearOrder BlockId] {l c : BlockId} (hl : l ∈ D.ids) (h : ReachesFrom D.block l c) : c ∈ histOf D l ``` `histOf` is the causal history, discharging the faithfulness condition Theorem 26 asks for. #### `nodup_histOf` *theorem, `FinWhale.Procedure.Decided.lean`* ```lean theorem nodup_histOf [LinearOrder BlockId] {l : BlockId} : (histOf D l).Nodup ``` And it lists each block once, which is the condition Theorem 15 asks for. #### `all_decided_of_pass` *theorem, `FinWhale.Procedure.Holdings.lean`* ```lean theorem all_decided_of_pass (pc : PaceCore U (Correct : Finset Validator) M) (hids : D.ids = U.ids) (hblk : D.block = U.block) (hle : ∀ u ∈ (Correct : Finset Validator), ∀ n ≤ pc.top u, n ≤ pc.built u n) {R : ℕ} (hgst : pc.gst ≤ R) {v : Validator} (hv : v ∈ (Correct : Finset Validator)) {choose : BlockId → ℕ → Option BlockId} {Np N r : ℕ} (hhorizon : ∀ b ∈ pc.holds v (settled pc), (D.block b).round ≤ Np) (hcommits : CommitsCorrectLeaders S D R N) (hrr : RoundRobin S.leader) (hEl : ∀ r a, Elig r a ↔ r + 2 < a) (hid : ∀ k, S.slotRound k = k) (hNM : N ≤ M) (hN : max r R + (3 * F.f + 5) ≤ N) : decOf S Elig (holdsView pc hids hblk hv (settled pc)).toRecord choose Np r ≠ Verdict.undecided ``` **Every slot below the horizon is decided**, by a validator whose view is its own holdings and whose verdicts are the reverse pass. Nothing about the validator itself is a hypothesis; what remains is about the schedule and the DAG reaching the rounds. #### `theorem14` *theorem, `FinWhale.Procedure.Order.lean`* ```lean theorem theorem14 (hist : BlockId → List BlockId) {ls ls' : List BlockId} (h : ls <+: ls') : linearise hist ls <+: linearise hist ls' ``` **Theorem 14 (Total Order).** A longer committed leader sequence delivers an extension of what a shorter one delivers, so two validators never disagree on the order of what both have delivered. With Lemma 13 this is the total order property: the two leader sequences are prefix-comparable, hence so are the two delivery orders. #### `theorem15` *theorem, `FinWhale.Procedure.Order.lean`* ```lean theorem theorem15 (hist : BlockId → List BlockId) (hnd : ∀ l, (hist l).Nodup) (ls : List BlockId) : (linearise hist ls).Nodup ``` **Theorem 15 (Integrity).** No block is delivered twice: each leader appends only what the accumulator does not already hold, and a causal history lists each block once. #### `decOf_eq` *theorem, `FinWhale.Procedure.Pass.lean`* ```lean theorem decOf_eq (hlt : ∀ r a, Elig r a → r < a) {r : ℕ} (hr : r ≤ N) : decOf S Elig D choose N r = slotVerdict S Elig D choose N (decOf S Elig D choose N) r ``` **The equation the pass satisfies.** At or below the horizon, a slot's verdict is `slotVerdict` applied to the pass itself. #### `decOf_of_gt` *theorem, `FinWhale.Procedure.Pass.lean`* ```lean theorem decOf_of_gt {r : ℕ} (hr : N < r) : decOf S Elig D choose N r = Verdict.undecided ``` Above the horizon the pass decides nothing. #### `wellFormed_decOf` *theorem, `FinWhale.Procedure.Pass.lean`* ```lean theorem wellFormed_decOf {N M : ℕ} (hN : ∀ b ∈ D.ids, (D.block b).round ≤ N) (hlt : ∀ r a, Elig r a → r < a) (hrle : ∀ r, S.slotRound r ≤ N → r ≤ M) (choose : BlockId → ℕ → Option BlockId) : WellFormed Elig (fun r l => l ∈ slotBlocks S D r ∧ DirectCommit D l) (fun r => DirectSkip S D r) choose (decOf S Elig D choose M) where direct_commit r l ``` **The reverse pass is well formed.** Its direct rules are the DAG's own, and `choose` is whatever deterministic rule the validator applies. `hN` is the horizon: no block of the view sits above it. #### `mem_slotBlocks_of_decOf` *theorem, `FinWhale.Procedure.Pass.lean`* ```lean theorem mem_slotBlocks_of_decOf {D' : Dag Validator BlockId Payload} {N : ℕ} {choose : BlockId → ℕ → Option BlockId} (hsub : ∀ r, slotBlocks S D' r ⊆ slotBlocks S D r) (hch : ChooseSound S D choose) (hlt : ∀ r a, Elig r a → r < a) {r : ℕ} {A : BlockId} (h : decOf S Elig D' choose N r = Verdict.commit A) : A ∈ slotBlocks S D r ``` **A committed verdict names a block of its slot.** Either the pass took a direct commit, which is one, or the tie-break named it, and `ChooseSound` says what it names is a candidate. #### `held` *theorem, `FinWhale.Procedure.Protocol.lean`* ```lean theorem held (hv : v ∈ (Correct : Finset Validator)) : ∀ n, run.stable ≤ n → n ≤ run.liveHorizon → ∀ b ∈ blocksAt run.dag n, (run.dag.block b).creator ∈ (Correct : Finset Validator) → b ∈ run.view v ``` Past the stable round, a validator holds every reliable block of every round below the horizon. #### `theorem26_of_selfParent` *theorem, `FinWhale.Procedure.Validity.lean`* ```lean theorem theorem26_of_selfParent (hself : SelfParented D) {dc : ℕ → BlockId → Prop} {ds : ℕ → Prop} {choose : BlockId → ℕ → Option BlockId} {dec : ℕ → Verdict BlockId} (hwf : WellFormed Elig dc ds choose dec) {R N : ℕ} (hsees : SeesCommits S D dc R N) (hrr : RoundRobin S.leader) (hid : ∀ s, S.slotRound s = s) [LinearOrder BlockId] {b : BlockId} {k : ℕ} (hb : b ∈ D.ids) (hbc : (D.block b).creator ∈ (Correct : Finset Validator)) (hbound : max ((D.block b).round) R + Fintype.card Validator + 2 ≤ N) (hk : max ((D.block b).round) R + Fintype.card Validator < k) : b ∈ linearise (histOf D) (commitSeq dec k) ``` **Theorem 26 (Validity), on any schedule.** A correct validator's block is delivered, once the rotation has named its author a leader above it and that slot is committed. Nothing here is a coverage assumption, so the reactive schedule carries it as readily as the timed one: the block reaches the leader block because the leader block is the *same validator's*, later. #### `decided_of_wellFormed` *theorem, `FinWhale.Procedure.View.lean`* ```lean theorem decided_of_wellFormed {V : D.View} {dec : ℕ → Verdict BlockId} (hwf : WellFormed (EligibleAt (S := S) 2) (viewCommit S D V) (viewSkip S D V) (chooseLeast S D) dec) {N : ℕ} (hN : ∀ s, N ≤ s → dec s = Verdict.undecided) : ∀ r, dec r ≠ Verdict.undecided → Decided D V r (dec r).optOf ``` **The reverse pass lands in the relation.** Every slot a well-formed assignment decides, it decides as the relation does: direct verdicts by the direct constructor, indirect ones by the induction hypothesis at the nearest eligible committed anchor. #### `agreement_of_commits` *theorem, `FinWhale.Procedure.View.lean`* ```lean theorem agreement_of_commits [LinearOrder BlockId] {V V' : D.View} {dec dec' : ℕ → Verdict BlockId} (hwf : WellFormed (EligibleAt (S := S) 2) (viewCommit S D V) (viewSkip S D V) (chooseLeast S D) dec) (hwf' : WellFormed (EligibleAt (S := S) 2) (viewCommit S D V') (viewSkip S D V') (chooseLeast S D) dec') {M : ℕ} (hbound : ∀ s, M ≤ s → dec s = Verdict.undecided ∧ dec' s = Verdict.undecided) {R N : ℕ} (hsees : SeesCommits S D (viewCommit S D V) R N) (hsees' : SeesCommits S D (viewCommit S D V') R N) (hrr : RoundRobin S.leader) (hid : ∀ s, S.slotRound s = s) {k : ℕ} (hkN : max k R + (3 * F.f + 5) ≤ N) (hist : BlockId → List BlockId) : linearise hist (commitSeq dec k) = linearise hist (commitSeq dec' k) ``` **Theorem 24 (Agreement), end to end.** Two validators running the reverse pass on their own views deliver the same sequence at every horizon: the relation's agreement settles verdicts both have decided, and Lemma 23 (via `hsees`) makes them decided. #### `selfParent` *theorem, `Hybrid.Carrier.lean`* ```lean theorem selfParent (k : ℕ) : SelfParent (hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) k) ``` **P3′ at the carrier.** #### `agree` *theorem, `Hybrid.Carrier.lean`* ```lean theorem agree {k : ℕ} (hk : Hybrid.Admissible Validator k) : Agree (hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) k) ``` **Two views decide alike.** H6 under the property's name, and unconditional because non-equivocation is now a field of the universe rather than a premise. #### `indirect` *theorem, `Hybrid.Properties.lean`* ```lean theorem indirect (kt : ℕ) : Indirect (hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt) (fun sr i j => sr i + (Hybrid.hybridAnchored Validator BlockId Payload kt).wave + 1 ≤ sr j) ``` **H-A3 as a property**: the relation's indirect property, committing the least thick-linked candidate. #### `safety` *theorem, `Hybrid.Properties.lean`* ```lean theorem safety {kt : ℕ} (hpos : 0 < kt) (hk : Hybrid.Admissible Validator kt) : Properties.Safe (hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt) ``` #### `extends_fill` *theorem, `Hybrid.Record.lean`* ```lean theorem extends_fill (sk : SkipMsg U.val) : Extends (HybridProperties.hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt) U (fill U sk) ``` **The fill is an extension of Hybrid's carrier.** #### `decided_none_fresh` *theorem, `Hybrid.Record.lean`* ```lean theorem decided_none_fresh {U : (HybridProperties.hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt).Universe} (sk : SkipMsg U.val) {V : View Validator BlockId Payload U.val} {T : Finset Validator} {k : ℕ} (hq : Hybrid.q Validator ≤ T.card) (hlead : S.leader k = sk.v1) (hk1 : sk.r0 < S.slotRound k) (hk2 : S.slotRound k ≤ sk.r) (hpres : PresentAt (HybridProperties.hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt) V T (S.slotRound k + 1)) : (HybridProperties.hybridRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) kt).Decided S (U := fill U sk) (sk.liftView V) k none ``` **SS3 for Hybrid**, from its `SkipsUnsupported`: the slot the recovering replica leads at a gap round is skipped at once. #### `agree` *theorem, `Hydrozoan.Helpers.Commit.lean`* ```lean theorem agree : Agree (rule (Replica := Replica) (BlockId := BlockId)) ``` **Slot agreement as a property.** #### `indirect` *theorem, `Hydrozoan.Helpers.Commit.lean`* ```lean theorem indirect : Indirect (rule (Replica := Replica) (BlockId := BlockId)) (fun sr i j => sr i + 3 ≤ sr j) ``` **HZ6 as a property.** The relation's indirect property at the graded rule's rung choices, read at the three-round eligibility. #### `holds` *theorem, `Hydrozoan.Properties.Proof.lean`* ```lean theorem holds : Statement ``` #### `safety` *theorem, `Hydrozoan.Properties.Proof.lean`* ```lean theorem safety : LeanDag.Properties.Safe (rule (Replica := Replica) (BlockId := BlockId)) ``` #### `progress` *theorem, `Hydrozoan.Properties.Proof.lean`* ```lean theorem progress : LeanDag.Properties.Support.Progresses (hzSupport (Replica := Replica) (BlockId := BlockId)) (hzReliability Replica) ``` #### `not_synchronisedOn_copyFill_hz` *theorem, `Integration.HydrozoanMechanisms.lean`* ```lean theorem not_synchronisedOn_copyFill_hz {sk : SkipData U.ids U.block} {T : Finset Replica} {R k : ℕ} (hv1 : sk.v1 ∈ T) (hk1 : sk.r0 < k) (hk2 : k ≤ sk.r) (hk : R ≤ k) {b : BlockId} (hb : b ∈ U.ids) (hbround : (U.block b).round = k + 1) (hbc : (U.block b).creator ∈ T) : ¬ Timed.SynchronisedOn (LeanDag.Hydrozoan.rule (Replica := Replica) (BlockId := BlockId)) (BlockRecord.copyFill U sk) T R ``` **The copy fill does not restore coverage either.** The generic refutation at the record's `extends_copyFill`: a reliable set holding the recovering replica is uncovered at every gap round, for the same reason the fill is safe. #### `decided_none_fresh_hz` *theorem, `Integration.HydrozoanMechanisms.lean`* ```lean theorem decided_none_fresh_hz (S : Slots Replica) {V : LeanDag.Hydrozoan.View U} {T : Finset Replica} {k : ℕ} (hq : LeanDag.Hydrozoan.qFast Replica ≤ T.card) (hlead : S.leader k = sk.v1) (hk1 : sk.r0 < S.slotRound k) (hk2 : S.slotRound k ≤ sk.r) (hpres : PresentAt (LeanDag.Hydrozoan.rule (Replica := Replica) (BlockId := BlockId)) V T (S.slotRound k + 1)) : (LeanDag.Hydrozoan.rule (Replica := Replica) (BlockId := BlockId)).Decided S (U := BlockRecord.copyFill U sk) (V.liftCopy (sk := sk)) k none ``` **SS3 for Hydrozoan**, from its `SkipsUnsupported`: the slot the recovering replica leads at a gap round is skipped at once, at the grade `qFast ≤ |T|`. #### `stack_core` *theorem, `Integration.StackRules.lean`* ```lean theorem stack_core (sk : SkipMsg U) (hd : G ≤ S.slotRound d) : Stack (MysticetiProperties.mysticetiRule (Payload := Payload)) U S (chop sk.skipFill G) (S.chop G d hd) G (max (sk.r + 1) G) d ``` **The core's fill-then-cut is a stack**, settling at the later of the gap's top and the horizon, shifted by the horizon, re-indexed from the base slot. #### `selfParent` *theorem, `MahiMahi.Carrier.lean`* ```lean theorem selfParent (w : ℕ) : SelfParent (mahiMahiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) w) ``` **P3′ at the carrier.** #### `agree` *theorem, `MahiMahi.Carrier.lean`* ```lean theorem agree {w : ℕ} (hw : 2 ≤ w) : Agree (mahiMahiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) w) ``` **Two views decide alike.** MM2 under the property's name, at the widths its safety arc covers. #### `indirect` *theorem, `MahiMahi.Properties.lean`* ```lean theorem indirect {w : ℕ} (hw : 1 ≤ w) : Indirect (mahiMahiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) w) (fun sr i j => sr i + w ≤ sr j) ``` **MM-A3 as a property**: the relation's indirect property, with no tie to break — two certificates at one slot name the same candidate. #### `safety` *theorem, `MahiMahi.Properties.lean`* ```lean theorem safety (hw : 2 ≤ w) : Properties.Safe (mahiMahiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload) w) ``` #### `selfParent` *theorem, `Mysticeti.Properties.lean`* ```lean theorem selfParent : SelfParent (mysticetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **P3′ at the carrier**: every non-genesis block references its author's previous block. #### `agree` *theorem, `Mysticeti.Properties.lean`* ```lean theorem agree : Agree (mysticetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **M6 as a property.** #### `certLive_of_coreLive` *theorem, `Mysticeti.Properties.lean`* ```lean theorem certLive_of_coreLive {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {V : View Validator BlockId Payload U} {T : Finset Validator} {lo K : ℕ} (h : coreLive S (U := U) V T lo K) : certLive S (U := U) V T lo K ``` **Coverage is one bridge.** Full reference coverage certifies every candidate of every reliably-led slot, which is `certifiesAt_of_synchronisedOn` at each slot of the window. #### `indirect` *theorem, `Mysticeti.Properties.lean`* ```lean theorem indirect : Indirect (mysticetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) (fun sr i j => sr i + 3 ≤ sr j) ``` **A3 as a property**: the relation's indirect property at the core, with no tie to break, read at the three-round eligibility. #### `all_decided_below_of_fairRun` *theorem, `Mysticeti.Properties.lean`* ```lean theorem all_decided_below_of_fairRun {c : ℕ} (hc : 0 < c) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hspan : (coreAnchored Validator BlockId Payload).SpansEligible c) (fair : FairRunOn T c) (R : ℕ) (k : ℕ) : ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound (b + c - 1) + 2 ≤ N → ∀ i, i < b → ∃ v, Decided U (View.full U) i v ``` **L10.** For every slot `k` there is a `b ≥ k` such that every slot below `b` is decided, in any sufficiently grown synchronous DAG: the ledger advances rather than merely committing, unlike L6. #### `safety` *theorem, `Mysticeti.Properties.lean`* ```lean theorem safety : Properties.Safe (mysticetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **Safety**, across any stack of mechanisms, for the core and for its reactive execution alike. #### `sustains_chop` *theorem, `Mysticeti.Record.lean`* ```lean theorem sustains_chop : Sustains (MysticetiProperties.mysticetiRule (Payload := Payload)) U (chop U G) G G ``` **The cut sustains the core from its horizon.** The record's witness. #### `truncates_chop` *theorem, `Mysticeti.Record.lean`* ```lean theorem truncates_chop (hd : G ≤ S.slotRound d) : Truncates (MysticetiProperties.mysticetiRule (Payload := Payload)) U (chop U G) S (S.chop G d hd) G d ``` **The cut is a truncation.** The witness `Truncates` was written to have, exhibited before anything is proved from it. #### `decided_chop_iff` *theorem, `Mysticeti.Record.lean`* ```lean theorem decided_chop_iff (hd : G ≤ S.slotRound d) {V : View Validator BlockId Payload U} {k : ℕ} {v : Option BlockId} : Decided U V (d + k) v ↔ Decided (S := S.chop G d hd) (chop U G) (V.chop G) k v ``` **G3 re-derived, with no induction of its own.** Both directions of the cut's verdict transport, from the band. #### `decided_agree_chop` *theorem, `Mysticeti.Record.lean`* ```lean theorem decided_agree_chop (hd : G ≤ S.slotRound d) {W : View Validator BlockId Payload (chop U G)} {V : View Validator BlockId Payload U} {k : ℕ} {w v : Option BlockId} (hW : Decided (S := S.chop G d hd) (chop U G) W k w) (hV : Decided U V (d + k) v) : w = v ``` **G4 re-derived.** `GC/ChopDecided.decided_agree_chop` proves this by running the core's uniqueness inside the truncation and carrying the verdict across by induction. Here it is two properties applied. #### `decided_fill_of_persist` *theorem, `Mysticeti.Record.lean`* ```lean theorem decided_fill_of_persist [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {k : ℕ} {v : Option BlockId} (h : Decided U V k v) : Decided sk.skipFill (sk.liftView V) k v ``` **Verdicts survive the core's fill**, from the core's `Persist`. #### `decided_fill_agree_of_properties` *theorem, `Mysticeti.Record.lean`* ```lean theorem decided_fill_agree_of_properties [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {W : View Validator BlockId Payload sk.skipFill} {k : ℕ} {v w : Option BlockId} (hv : Decided U V k v) (hw : Decided sk.skipFill W k w) : v = w ``` **Agreement across the core's recovery**: a verdict reached before agrees with any reached after. #### `decided_none_fresh` *theorem, `Mysticeti.Record.lean`* ```lean theorem decided_none_fresh [S : Slots Validator] (sk : SkipMsg U) {V : View Validator BlockId Payload U} {T : Finset Validator} {k : ℕ} (hcard : quorumCard Validator ≤ T.card) (hlead : S.leader k = sk.v1) (hk1 : sk.r0 < S.slotRound k) (hk2 : S.slotRound k ≤ sk.r) (hpres : PresentAt MysticetiProperties.mysticetiRule V T (S.slotRound k + 1)) : Decided sk.skipFill (sk.liftView V) k none ``` **SS3, as a verdict, from the properties.** The slot the recovering replica leads at a gap round is decided `none` on the lifted view, given a quorum of the pre-crash view present one round above it. No induction; the fill is an extension, and the core skips what nothing supports. #### `agree` *theorem, `Nemo.Carrier.lean`* ```lean theorem agree : Agree (nemoRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **Two views decide alike.** Nemo's `decided_unique` under the property's name. #### `indirect` *theorem, `Nemo.Properties.lean`* ```lean theorem indirect : Indirect (nemoRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) (fun sr i j => sr i + (Nemo.nemoAnchored Validator BlockId Payload).wave + 1 ≤ sr j) ``` **A3 as a property**: the relation's indirect property, with no tie to break. #### `all_decided_below_of_fairRun` *theorem, `Nemo.Properties.lean`* ```lean theorem all_decided_below_of_fairRun {c : ℕ} (hc : 0 < c) (hT : T ⊆ Live Validator) (hcard : majority Validator ≤ T.card) (hspan : (Nemo.nemoAnchored Validator BlockId Payload).SpansEligible c) (fair : FairRunOn T c) (R : ℕ) (s : ℕ) : ∃ b, s ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : Universe Validator BlockId Payload) (N : ℕ) (V : View Validator BlockId Payload U), (∀ r ≤ N, Populated U r) → SynchronisedOn U T R → S.slotRound (b + c - 1) + 1 ≤ N → V.CoversUpto N → ∀ i, i < b → ∃ v, Decided U V i v ``` **Liveness.** Under post-`R` coverage, growth to the horizon, and a recurring run of `c` reliable-led slots, every slot below the run is decided, the run placed past both the target and `R` by fairness. The slot `b` is fixed by the schedule alone, before any universe is named, so "eventually" means any DAG grown past this schedule-fixed slot; crashed-leader slots are settled here and only here, via `indirectSkip`. #### `all_decided_below_of_fairRun_live` *theorem, `Nemo.Properties.lean`* ```lean theorem all_decided_below_of_fairRun_live {c : ℕ} (hc : 0 < c) (hspan : (Nemo.nemoAnchored Validator BlockId Payload).SpansEligible c) (fair : FairRunOn (Live Validator) c) (R : ℕ) (s : ℕ) : ∃ b, s ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : Universe Validator BlockId Payload) (N : ℕ) (V : View Validator BlockId Payload U), (∀ r ≤ N, Populated U r) → Synchronised U R → S.slotRound (b + c - 1) + 1 ≤ N → V.CoversUpto N → ∀ i, i < b → ∃ v, Decided U V i v ``` **Liveness at `T := Live`** — the whole live class, which the tight committee `n = 2f + 1` requires exactly. #### `safety` *theorem, `Nemo.Properties.lean`* ```lean theorem safety : Properties.Safe (nemoRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` #### `progress` *theorem, `Nemo.Properties.lean`* ```lean theorem progress (hn : 0 < Fintype.card Validator) : Properties.Support.Progresses (Properties.voteSupport (nemoRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload))) (nemoReliability Validator hn) ``` #### `card_authorsAt_of_lt` *theorem, `Network.Delivery.lean`* ```lean theorem card_authorsAt_of_lt {r n : ℕ} (hn : n < r) {i : BlockId} (hi : i ∈ U.ids) (hir : (U.block i).round = r) : quorumCard Validator ≤ (authorsAt U n).card ``` **L0 — the DAG is dense below its frontier.** If any block exists at round `r`, every round `n < r` has at least `2f+1` distinct authors: downward induction on the gap `r - n`, generalised over `n` so the step can re-enter at `n+1`. #### `selfParent` *theorem, `Odontoceti.Carrier.lean`* ```lean theorem selfParent : SelfParent (odontocetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **P3′ at the carrier.** #### `agree` *theorem, `Odontoceti.Carrier.lean`* ```lean theorem agree : Agree (odontocetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` **Two views decide alike.** O5 under the property's name. #### `indirect` *theorem, `Odontoceti.Properties.lean`* ```lean theorem indirect : Indirect (odontocetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) (fun sr i j => sr i + (Odontoceti.odontocetiAnchored Validator BlockId Payload).wave + 1 ≤ sr j) ``` **O-A3 as a property**: the relation's indirect property, committing the least thick-linked candidate. #### `all_decided_below_of_fairRun` *theorem, `Odontoceti.Properties.lean`* ```lean theorem all_decided_below_of_fairRun {c : ℕ} (hc : 0 < c) (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (hspan : (odontocetiAnchored Validator BlockId Payload).SpansEligible c) (fair : FairRunOn T c) (R : ℕ) (k : ℕ) : ∃ b, k ≤ b ∧ R ≤ S.slotRound b ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ) (V : View Validator BlockId Payload U), (∀ r, R ≤ r → r ≤ N → PopulatedOn U T r) → SynchronisedOn U T R → S.slotRound (b + c - 1) + 1 ≤ N → V.CoversUpto N → ∀ i, i < b → ∃ v, Decided U V i v ``` **O10 (thesis Theorem 12).** Under production and post-`R` synchrony, a recurring run of `c` correct-led slots decides every slot below it, on any view caught up to the horizon — with the run placed past both the target and `R` by fairness. Note the horizon: the run's last slot needs rounds up to its `slotRound + 1` only. #### `safety` *theorem, `Odontoceti.Properties.lean`* ```lean theorem safety : Properties.Safe (odontocetiRule (Validator := Validator) (BlockId := BlockId) (Payload := Payload)) ``` #### `agree` *theorem, `OptimalHydrozoan.Carrier.lean`* ```lean theorem agree : Agree (optimalRule (Replica := Replica) (BlockId := BlockId)) ``` **Two views decide alike.** OH5 under the property's name. #### `indirect` *theorem, `OptimalHydrozoan.Carrier.lean`* ```lean theorem indirect : Indirect (optimalRule (Replica := Replica) (BlockId := BlockId)) (fun sr i j => sr i + 3 ≤ sr j) ``` **The graded rule is total, at a bound**: the relation's indirect property at the rule's rung choices, read at the three-round eligibility. Every clause reads slot `k`'s own candidates and the anchor's history, and none moves when the leaders of other slots are reassigned — the relation's `link_congr`. #### `safety` *theorem, `OptimalHydrozoan.Carrier.lean`* ```lean theorem safety : Properties.Safe (optimalRule (Replica := Replica) (BlockId := BlockId)) ``` #### `progress` *theorem, `OptimalHydrozoan.Carrier.lean`* ```lean theorem progress : Properties.Support.Progresses (optSupport (Replica := Replica) (BlockId := BlockId)) (LeanDag.Hydrozoan.hzReliability Replica) ``` #### `decided_of_truncate` *theorem, `Properties.Arcs.GC.lean`* ```lean theorem decided_of_truncate (h : LocalTruncate R) (ht : Truncates R U U' S S' G d) (hv : ViewAgreeAbove R V V' G) {k : ℕ} {v : Option BlockId} (hd : R.Decided S V (d + k) v) : R.Decided S' V' k v ``` **A verdict survives the cut**, at the replica's own numbering. #### `decided_agree_horizons` *theorem, `Properties.Arcs.GC.lean`* ```lean theorem decided_agree_horizons (ha : Agree R) (hlt : LocalTruncate R) {U₁ U₂ : R.Universe} {S₁ S₂ : Slots Validator} {G₁ d₁ G₂ d₂ : ℕ} (ht₁ : Truncates R U U₁ S S₁ G₁ d₁) (ht₂ : Truncates R U U₂ S S₂ G₂ d₂) {V₁ : R.View U₁} {V₂ : R.View U₂} (hv₁ : ViewAgreeAbove R V V₁ G₁) (hv₂ : ViewAgreeAbove R V V₂ G₂) {W₁ : R.View U₁} {W₂ : R.View U₂} {k₁ k₂ : ℕ} (halign : d₁ + k₁ = d₂ + k₂) {w₁ w₂ v : Option BlockId} (hW₁ : R.Decided S₁ W₁ k₁ w₁) (hW₂ : R.Decided S₂ W₂ k₂ w₂) (hV : R.Decided S V (d₁ + k₁) v) : w₁ = w₂ ``` **And across two horizons.** Validators cut at different depths agree on every shared slot, matched through the absolute slot index. Horizons need never be negotiated. #### `safety` *theorem, `Properties.Arcs.Headline.lean`* ```lean theorem safety (hb : Banded R) (ha : Agree R) (hcc : CommitsCandidate R) : Safe R ``` **The safety headline**, assembled from `Banded`, `Agree` and `CommitsCandidate`. #### `Safe.prefix_agree` *theorem, `Properties.Arcs.Headline.lean`* ```lean theorem Safe.prefix_agree (hs : Safe R) {U U' : R.Universe} {S S' : Slots Validator} {G R₀ d : ℕ} (st : Stack R U S U' S' G R₀ d) {V : R.View U} {V' : R.View U'} (hv : ViewAgreeAbove R V V' R₀) {W : R.View U'} {g g' : ℕ → Option BlockId} {n : ℕ} (hn : ∀ k, k < n → R₀ ≤ S.slotRound (d + k)) (hg : ∀ k, k < n → R.Decided S V (d + k) (g k)) (hg' : ∀ k, k < n → R.Decided S' W k (g' k)) : ∀ k, k < n → g' k = g k ``` **The ledger reading.** Two validators' verdict functions agree on every slot both have decided above the settling round — the common prefix of their ledgers is one prefix. #### `progress` *theorem, `Properties.Arcs.Headline.lean`* ```lean theorem progress (hlc : sp.Commits rel) : sp.Progresses rel ``` **Progress, from Law 2.** #### `card_coveredAt_ge_of_decided` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem card_coveredAt_ge_of_decided (hq : Quorate R rel) (hcc : CommitsCandidate R) (h : R.Decided S V k (some L)) (hδ : δ < (R.block U L).round) : rel.correct.card - rel.slack ≤ (coveredAt R rel U L δ).card ``` **CQ1.** A committed leader's flush covers all but the slack of the reliable validators at every round below it — any route, any view, no synchrony. #### `card_correct_le_two_mul_coveredAt_of_decided` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem card_correct_le_two_mul_coveredAt_of_decided (hq : Quorate R rel) (hcc : CommitsCandidate R) (hhalf : 2 * rel.slack ≤ rel.correct.card) (h : R.Decided S V k (some L)) (hδ : δ < (R.block U L).round) : rel.correct.card ≤ 2 * (coveredAt R rel U L δ).card ``` **CQ2.** #### `ledger_coverage` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem ledger_coverage (hq : Quorate R rel) (hcc : CommitsCandidate R) {g : ℕ → Option BlockId} {n : ℕ} (hdec : R.Decided S V k (some L)) (hg : g k = some L) (hk : k < n) (hδ : δ < (R.block U L).round) : ∃ W : Finset Validator, W ⊆ rel.correct ∧ rel.correct.card - rel.slack ≤ W.card ∧ ∀ v ∈ W, ∃ i ∈ ledgerSetOf R U g n, (R.block U i).creator = v ∧ (R.block U i).round = δ ``` **CQ3 (ledger coverage, cumulative).** For a verdict assignment `g` with a committed slot `k < n` whose leader sits at round `r`: for every `δ < r`, at least `|correct| − slack` reliable validators each have a round-`δ` block in the ledger. The set is exhibited, so no choice and no decidability of the ledger is needed. #### `mem_history_of_decided_commit` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem mem_history_of_decided_commit (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hdec : R.Decided S V k (some L)) (hLc : (R.block U L).creator ∈ rel.correct) (hb : b ∈ R.ids U) (hbc : (R.block U b).creator = (R.block U L).creator) (hle : (R.block U b).round ≤ (R.block U L).round) : b ∈ historyFrom (R.block U) L ``` **CQ5.** A reliable block is in the cone of every committed leader block by the same author at or above its round — any commit route, any view, no synchrony. #### `committed_of_correct_block` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem committed_of_correct_block {Live : Slots Validator → ∀ {U : R.Universe}, R.View U → Finset Validator → ℕ → ℕ → Prop} (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hlc : LeaderCommits R Live) (S : Slots Validator) {T : Finset Validator} (hT : T ⊆ rel.correct) (fair : ∀ v ∈ T, ∀ n, ∃ k, n ≤ k ∧ S.leader k = v) (m : ℕ) {v : Validator} (hv : v ∈ T) : ∃ k', m ≤ S.slotRound k' ∧ S.leader k' = v ∧ ∀ (U : R.Universe) (V : R.View U), Live S V T k' (k' + 1) → ∃ L, R.Decided S V k' (some L) ∧ ∀ b ∈ R.ids U, (R.block U b).creator = v → (R.block U b).round = m → b ∈ historyFrom (R.block U) L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSetOf R U g n ``` **CQ6 (inclusion liveness).** Under a schedule returning to every reliable validator, every reliable block is in the ledger of the slot its author next leads and commits. Fairness is per validator, since the argument runs along one author's chain. #### `chain_quality` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem chain_quality {Live : Slots Validator → ∀ {U : R.Universe}, R.View U → Finset Validator → ℕ → ℕ → Prop} (hq : Quorate R rel) (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hlc : LeaderCommits R Live) (hhalf : 2 * rel.slack ≤ rel.correct.card) (S : Slots Validator) {T : Finset Validator} (hT : T ⊆ rel.correct) (fair : ∀ v ∈ T, ∀ n, ∃ k, n ≤ k ∧ S.leader k = v) (m : ℕ) : (∀ (U : R.Universe) (V : R.View U) (k : ℕ) (L : BlockId) (δ : ℕ), R.Decided S V k (some L) → δ < (R.block U L).round → rel.correct.card ≤ 2 * (coveredAt R rel U L δ).card) ∧ ∀ v ∈ T, ∃ k', m ≤ S.slotRound k' ∧ S.leader k' = v ∧ ∀ (U : R.Universe) (V : R.View U), Live S V T k' (k' + 1) → ∃ L, R.Decided S V k' (some L) ∧ ∀ b ∈ R.ids U, (R.block U b).creator = v → (R.block U b).round = m → b ∈ historyFrom (R.block U) L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSetOf R U g n ``` **CQ7 (the capstone).** Chain quality in one statement: every commit's flush covers half the reliable validators, and under a returning schedule every reliable block is in the flush of a slot its author leads. #### `committed_of_correct_block_within` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem committed_of_correct_block_within (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hlc : LeaderCommits R Live) (hT : T ⊆ rel.correct) (fair : FairToEachWithin T w) (m : ℕ) {v : Validator} (hv : v ∈ T) : ∃ k', slotAt Validator m ≤ k' ∧ k' < slotAt Validator m + w ∧ m ≤ S.slotRound k' ∧ S.leader k' = v ∧ ∀ (U : R.Universe) (V : R.View U), Live S V T k' (k' + 1) → ∃ L, R.Decided S V k' (some L) ∧ ∀ b ∈ R.ids U, (R.block U b).creator = S.leader k' → (R.block U b).round = m → b ∈ historyFrom (R.block U) L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSetOf R U g n ``` **CQ7, windowed.** The committing slot for `v`'s round-`m` blocks lies within `w` slots of the first slot at or above round `m`. #### `committed_of_correct_block_by_round` *theorem, `Properties.Arcs.Quality.lean`* ```lean theorem committed_of_correct_block_by_round (hsp : SelfParent R) (hne : NoEquiv R rel) (hcc : CommitsCandidate R) (hlc : LeaderCommits R Live) (hT : T ⊆ rel.correct) (fair : FairToEachWithin T w) (hs : BoundedSpacing (Validator := Validator) s) (m : ℕ) {v : Validator} (hv : v ∈ T) : ∃ k', m ≤ S.slotRound k' ∧ S.slotRound k' ≤ S.slotRound (slotAt Validator m) + s * w ∧ S.leader k' = v ∧ ∀ (U : R.Universe) (V : R.View U), Live S V T k' (k' + 1) → ∃ L, R.Decided S V k' (some L) ∧ ∀ b ∈ R.ids U, (R.block U b).creator = S.leader k' → (R.block U b).round = m → b ∈ historyFrom (R.block U) L ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSetOf R U g n ``` **CQ7, by round.** With bounded slot spacing the committing slot's round is within `s * w` rounds of the first slot at or above `m`. #### `decided_chop_iff` *theorem, `Properties.Arcs.Record.lean`* ```lean theorem decided_chop_iff (hb : Banded R) (hd : G ≤ S.slotRound d) {V : R.View U} {k : ℕ} {v : Option BlockId} : R.Decided S V (d + k) v ↔ R.Decided (S.chop G d hd) (c.chopView V G) k v ``` **Verdict transport across the cut.** A validator that pruned below the horizon reaches exactly the verdicts it would have reached with its whole history, at the re-indexed slot. #### `decided_agree_chop` *theorem, `Properties.Arcs.Record.lean`* ```lean theorem decided_agree_chop (ha : Agree R) (hb : Banded R) (hd : G ≤ S.slotRound d) {W : R.View (c.chop U G)} {V : R.View U} {k : ℕ} {w v : Option BlockId} (hW : R.Decided (S.chop G d hd) W k w) (hV : R.Decided S V (d + k) v) : w = v ``` **Cross-cut agreement**, from an arbitrary view of the truncation. #### `decided_agree_copyFill` *theorem, `Properties.Arcs.Record.lean`* ```lean theorem decided_agree_copyFill (ha : Agree R) (hb : Banded R) (sk : SkipData (c.toRec U).ids (c.toRec U).block) {V : R.View U} {W : R.View (c.copyFill U sk)} {k : ℕ} {v w : Option BlockId} (hV : R.Decided S V k v) (hW : R.Decided S W k w) : v = w ``` **And agreement across it.** #### `decided_none_of_novel` *theorem, `Properties.Arcs.SafeSkip.lean`* ```lean theorem decided_none_of_novel {R : DagRule Validator BlockId Payload} {Ok : Finset Validator → Prop} (hsk : SkipsUnsupported R Ok) {U U' : R.Universe} (he : Extends R U U') (S : Slots Validator) {V' : R.View U'} {T : Finset Validator} {k : ℕ} (hok : Ok T) (hpres : PresentAt R V' T (S.slotRound k + 1)) (hnov : ∀ L, R.IsCandidate S U' k L → L ∉ R.ids U) (hold : ∀ c, c ∈ R.viewIds V' → (R.block U' c).creator ∈ T → (R.block U' c).round = S.slotRound k + 1 → c ∈ R.ids U) : R.Decided S V' k none ``` **A slot whose candidates are all novel is skipped**, promptly. #### `extends_of_skipFill` *theorem, `Properties.Arcs.SafeSkip.lean`* ```lean theorem extends_of_skipFill (R : DagRule Validator BlockId Payload) {U U' : R.Universe} {D : BlockUniverse Validator BlockId Payload} (sk : SkipMsg D) (hi : R.ids U = D.ids) (hb : R.block U = D.block) (hi' : R.ids U' = sk.skipFill.ids) (hb' : R.block U' = sk.skipFill.block) : Extends R U U' where subset ``` **The fill is an extension**: it holds every old block unchanged. Stated through four equations rather than a type equality, so a rule whose universes are core block universes applies it with `rfl`. #### `decided_skipFill` *theorem, `Properties.Arcs.SafeSkip.lean`* ```lean theorem decided_skipFill {R : DagRule Validator BlockId Payload} (hp : Persist R) {S : Slots Validator} {U U' : R.Universe} {D : BlockUniverse Validator BlockId Payload} (sk : SkipMsg D) (hi : R.ids U = D.ids) (hb : R.block U = D.block) (hi' : R.ids U' = sk.skipFill.ids) (hb' : R.block U' = sk.skipFill.block) {V : R.View U} {V' : R.View U'} (hV : R.viewIds V ⊆ R.viewIds V') {k : ℕ} {v : Option BlockId} (h : R.Decided S V k v) : R.Decided S V' k v ``` **Verdicts survive the recovery**, for any protocol with `Persist`. #### `decided_none_of_novel_agree` *theorem, `Properties.Arcs.SafeSkip.lean`* ```lean theorem decided_none_of_novel_agree {R : DagRule Validator BlockId Payload} (ha : Agree R) (hb : Banded R) {Ok : Finset Validator → Prop} (hsk : SkipsUnsupported R Ok) {U U' : R.Universe} (he : Extends R U U') (S : Slots Validator) {V' : R.View U'} {T : Finset Validator} {k : ℕ} (hok : Ok T) (hpres : PresentAt R V' T (S.slotRound k + 1)) (hnov : ∀ L, R.IsCandidate S U' k L → L ∉ R.ids U) (hold : ∀ c, c ∈ R.viewIds V' → (R.block U' c).creator ∈ T → (R.block U' c).round = S.slotRound k + 1 → c ∈ R.ids U) {U'' : R.Universe} (he' : Extends R U' U'') {V'' W : R.View U''} (hsub : R.viewIds V' ⊆ R.viewIds V'') {v : Option BlockId} (hW : R.Decided S W k v) : v = none ``` **The prompt skip conflicts with no verdict**, on any further extension a caught-up view reaches. #### `Stack.rebased` *theorem, `Properties.Arcs.Stack.lean`* ```lean theorem Stack.rebased {U U' : R.Universe} {S S' : Slots Validator} {G R₀ d : ℕ} : Stack R U S U' S' G R₀ d → Rebased R U U' S S' G R₀ d | .nil => Rebased.refl | .step h st => h.trans st.rebased ``` **A stack is one mechanism.** #### `Stack.safe_and_live` *theorem, `Properties.Arcs.Stack.lean`* ```lean theorem Stack.safe_and_live (hb : Banded R) (ha : Agree R) (sp : Support R) (hloc : sp.Local) (st : Stack R U S U' S' G R₀ d) {V : R.View U} {V' : R.View U'} (hv : ViewAgreeAbove R V V' R₀) : (∀ (k : ℕ) (v : Option BlockId), R₀ ≤ S.slotRound (d + k) → (R.Decided S V (d + k) v ↔ R.Decided S' V' k v)) ∧ (∀ (W : R.View U') (k : ℕ) (w v : Option BlockId), R₀ ≤ S.slotRound (d + k) → R.Decided S' W k w → R.Decided S V (d + k) v → w = v) ∧ (∀ {rel : Reliability Validator} {T : Finset Validator} {lo K : ℕ}, sp.live rel S V T lo K → R₀ ≤ S.slotRound lo → d ≤ lo → lo < K → (∀ N, G ≤ N → CoversUpto R V N → CoversUpto R V' (N - G)) → sp.live rel S' V' T (lo - d) (K - d)) ``` **Every stack of mechanisms keeps safety and liveness**, for any rule with `Banded`, `Agree` and a support. Above the composite settling round: verdicts transport to the composite's numbering, any view of the composite agrees with the original, and the liveness precondition carries. Nothing is assumed about which mechanisms are stacked or in what order. #### `decided_of_rebased` *theorem, `Properties.Compose.lean`* ```lean theorem decided_of_rebased (h : Banded R) (hr : Rebased R U U' S S' G R₀ d) {V : R.View U} {V' : R.View U'} (hv : ViewAgreeAbove R V V' R₀) (k : ℕ) (hk : R₀ ≤ S.slotRound (d + k)) (v : Option BlockId) : R.Decided S V (d + k) v ↔ R.Decided S' V' k v ``` **A verdict above the settling round transports across any rebase**, to the rebased numbering, on views that agree above the settling round. An `↔`, as `LocalTruncate` is. #### `decided_agree_rebased` *theorem, `Properties.Compose.lean`* ```lean theorem decided_agree_rebased (ha : Agree R) (hb : Banded R) (hr : Rebased R U U' S S' G R₀ d) {V : R.View U} {V' : R.View U'} (hv : ViewAgreeAbove R V V' R₀) {W : R.View U'} {k : ℕ} (hk : R₀ ≤ S.slotRound (d + k)) {w v : Option BlockId} (hW : R.Decided S' W k w) (hV : R.Decided S V (d + k) v) : w = v ``` **Cross-rebase agreement**, from any view of the rebased universe. #### `agree` *theorem, `Properties.Derived.Bounded.lean`* ```lean theorem agree (ha : Agree R) {S : Slots Validator} {U : R.Universe} {V₁ V₂ : R.View U} {B₁ B₂ k : ℕ} {v₁ v₂ : Option BlockId} (h₁ : DecidedBelow R S B₁ V₁ k v₁) (h₂ : DecidedBelow R S B₂ V₂ k v₂) : v₁ = v₂ ``` Two bounded verdicts agree, at any bounds — `Agree` through the first component. #### `Descends.of_indirect` *theorem, `Properties.Derived.Descent.lean`* ```lean theorem Descends.of_indirect (hind : Indirect R Elig) {S : Slots Validator} {c : ℕ} (hc : 0 < c) (hspans : ∀ b i, i < b → Elig S.slotRound i (b + c - 1)) : Descends R S c ``` **`Descends` is a consequence, not an obligation.** The run is `c` consecutive commits from `b`; its top is `b + c - 1`, and the spanning hypothesis is the protocol's own condition on the round structure — that every slot below `b` has the run's top eligible. #### `Persist.of_banded` *theorem, `Properties.Derived.FromBand.lean`* ```lean theorem Persist.of_banded (h : Banded R) : Persist R ``` **Persistence falls out.** An extension carries every band and adds only blocks; a larger view holds everything the band names. #### `LocalTruncate.of_banded` *theorem, `Properties.Derived.Truncate.lean`* ```lean theorem LocalTruncate.of_banded (h : Banded R) : LocalTruncate R ``` **Truncation invariance falls out of the band** — `decided_of_rebased` at a cut, whose settling round is its horizon. #### `SelfParent.reaches_of_creator` *theorem, `Properties.Optional.SelfParent.lean`* ```lean theorem SelfParent.reaches_of_creator (hsp : SelfParent R) {rel : Reliability Validator} (hne : NoEquiv R rel) {U : R.Universe} {b c : BlockId} (hb : b ∈ R.ids U) (hc : c ∈ R.ids U) (hbc : (R.block U b).creator ∈ rel.correct) (hcc : (R.block U c).creator = (R.block U b).creator) (hle : (R.block U b).round ≤ (R.block U c).round) : ReachesFrom (R.block U) c b ``` **A reliable author's block reaches every earlier block of that author**: walk the self-parent chain down to the earlier block's round, where non-equivocation says the chain has arrived. #### `sustains_chop` *theorem, `Properties.Record.lean`* ```lean theorem sustains_chop (U : R.Universe) : Sustains R U (c.chop U G) G G where mem ``` **The cut sustains the carrier from its horizon.** #### `truncates_chop` *theorem, `Properties.Record.lean`* ```lean theorem truncates_chop (U : R.Universe) (hd : G ≤ S.slotRound d) : Truncates R U (c.chop U G) S (S.chop G d hd) G d ``` **The cut is a truncation of the carrier.** #### `extends_fill` *theorem, `Properties.Record.lean`* ```lean theorem extends_fill : Extends R U (c.fill U sk B hB hI) where subset ``` **The fill is an extension of the carrier.** #### `sustains_fill` *theorem, `Properties.Record.lean`* ```lean theorem sustains_fill : Sustains R U (c.fill U sk B hB hI) 0 (sk.r + 1) where mem ``` **And it sustains the carrier from the top of its gap.** #### `extends_copyFill` *theorem, `Properties.Record.lean`* ```lean theorem extends_copyFill (U : R.Universe) (sk : SkipData (c.toRec U).ids (c.toRec U).block) : Extends R U (c.copyFill U sk) ``` #### `extends_addGenesis` *theorem, `Properties.Record.lean`* ```lean theorem extends_addGenesis : Extends R U (c.addGenesis U v g p hg hsev) where subset ``` **Re-genesis is an extension of the carrier.** #### `sustains_addGenesis` *theorem, `Properties.Record.lean`* ```lean theorem sustains_addGenesis : Sustains R U (c.addGenesis U v g p hg hsev) 0 1 where mem ``` **And it sustains the carrier from round one.** #### `coreSupport_live_of_reactiveLive` *theorem, `Reactive.MysticetiProperties.lean`* ```lean theorem coreSupport_live_of_reactiveLive {S : Slots Validator} {U : BlockUniverse Validator BlockId Payload} {V : View Validator BlockId Payload U} {T : Finset Validator} {lo K : ℕ} (h : reactiveLive S (U := U) V T lo K) : (coreSupport (Validator := Validator) (BlockId := BlockId) (Payload := Payload)).live (coreReliability Validator) S (U := U) V T lo K ``` **The reactive discipline reaches the core's support precondition** (`Properties/Support.lean`): a reactive execution past GST is a quorum certifying every candidate of every reliably-led slot in the window, which is `Support.live` and the socket every mechanism reads. #### `committed_of_correct_block` *theorem, `Reactive.MysticetiProperties.lean`* ```lean theorem committed_of_correct_block (hT : T ⊆ (Correct : Finset Validator)) (hcard : quorumCard Validator ≤ T.card) (fair : FairToEach (S := S) T) {u : Validator} (hu : u ∈ T) (R m : ℕ) (hRm : R ≤ m) : ∃ k', m < S.slotRound k' ∧ R ≤ S.slotRound k' ∧ S.leader k' = u ∧ ∀ (U : BlockUniverse Validator BlockId Payload) (N : ℕ) (rm : ReactiveM U T N), rm.gst ≤ R → (∀ n, R ≤ n → 2 * rm.delay + rm.proc ≤ rm.timeout n) → S.slotRound k' + 2 ≤ N → ∀ b ∈ U.ids, (U.block b).creator = u → (U.block b).round = m → ∃ L, IsLeaderBlock U k' L ∧ Decided U (View.full U) k' (some L) ∧ Reaches U L b ∧ ∀ (g : ℕ → Option BlockId) (n : ℕ), g k' = some L → k' < n → b ∈ ledgerSet U g n ``` **RS5 — reactive inclusion.** The schedule fixes a `u`-led slot above any round `m` before an execution is named, and a sufficiently grown reactive execution commits it with a leader block whose cone contains `u`'s round-`m` block, so it lands in the agreed ledger. #### `SynchronisedOn.mono` *theorem, `Timed.Coverage.lean`* ```lean theorem SynchronisedOn.mono {U : R.Universe} {T : Finset Validator} {r r' : ℕ} (h : SynchronisedOn R U T r) (hr : r ≤ r') : SynchronisedOn R U T r' ``` Synchrony from a round is synchrony from any later one. #### `descent_of_support` *theorem, `Timed.Coverage.lean`* ```lean theorem descent_of_support (R : Properties.DagRule Validator BlockId Payload) (Good : R.Universe → ℕ → ℕ → Prop) (g : ℕ) (sp : Properties.Support R) {rel : Reliability Validator} (hcov : OfCoverage sp rel) (hlc : sp.Commits rel) (hind : Properties.Indirect R (fun sr i j => sr i + g ≤ sr j)) (hwave : sp.wave ≤ g) (hgood : ∀ U Rnd N, Good U Rnd N → Timed.Good R rel U Rnd N) : Properties.Descent R Good g rel.slack where goodLeaders ``` **The descent laws, from a support.** A rule whose support commits under coverage at a fault model, with the indirect rule at gap `g` and a goodness predicate that implies `Timed.Good`, has the descent laws at the model's slack. #### `not_synchronisedOn_of_extends` *theorem, `Timed.Extension.lean`* ```lean theorem not_synchronisedOn_of_extends {U U' : R.Universe} (he : Extends R U U') {T : Finset Validator} {R₀ k : ℕ} (hk : R₀ ≤ k) {f : BlockId} (hf : Novel R U U' f) (hfr : (R.block U' f).round = k) (hfc : (R.block U' f).creator ∈ T) {b : BlockId} (hb : b ∈ R.ids U) (hbr : (R.block U b).round = k + 1) (hbc : (R.block U b).creator ∈ T) : ¬ SynchronisedOn R U' T R₀ ``` **An extension does not restore coverage** for a set that counts a novel block's author reliable. An old reliable block at the round above the novel one references only old identifiers, and coverage would have it reference the novel one. #### `synchronisedOn_of_extends` *theorem, `Timed.Extension.lean`* ```lean theorem synchronisedOn_of_extends {U U' : R.Universe} (he : Extends R U U') {T : Finset Validator} {R₀ : ℕ} (hs : SynchronisedOn R U T R₀) (hnew : ∀ b, Novel R U U' b → (R.block U' b).creator ∉ T) : SynchronisedOn R U' T R₀ ``` **An extension preserves coverage** for a set holding no author of a novel block: every block the clause reaches is old, and old blocks are unchanged.