linera_chain/justification/proof.rs
1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Accountability: convicting the validators responsible when agreement fails.
5//!
6//! [`CommitAgreement`] holds only while [`MaxByzantineWeight`] does. This module proves what
7//! happens when it does not: a violation leaves *self-contained evidence* naming validators of at
8//! least [`Committee::validity_threshold`] weight — more than the fault bound permits, so the
9//! conviction set is itself a proof that the assumption was broken.
10//!
11//! Two properties, deliberately independent:
12//!
13//! * **Soundness** ([`ProofSoundness`]) — a proof that [`EquivocationProof::check`] accepts names
14//! a genuinely faulty validator. No correct validator is ever convictable.
15//! * **Completeness** ([`ConflictCompleteness`]) — two conflicting confirmed certificates yield
16//! enough accepted proofs, from the certificates alone.
17//!
18//! **Neither depends on [`MaxByzantineWeight`]**, which is the point: both must hold precisely in
19//! the regime where the fault bound has failed. Soundness is per-validator and rests only on
20//! [`UnforgeableSignatures`]; completeness needs only [`Intersection`], which in turn needs only
21//! [`ThresholdArithmetic`]. Accountability therefore sits on a strictly weaker assumption base
22//! than the safety theorem it backstops.
23//!
24//! [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
25//! [`CommitAgreement`]: crate::manager::proof::safety::CommitAgreement
26//! [`MaxByzantineWeight`]: crate::manager::proof::model::MaxByzantineWeight
27//! [`UnforgeableSignatures`]: crate::manager::proof::model::UnforgeableSignatures
28//! [`Intersection`]: crate::data_types::proof::quorum::Intersection
29//! [`ThresholdArithmetic`]: crate::data_types::proof::quorum::ThresholdArithmetic
30//! [`Committee::validity_threshold`]: linera_execution::committee::Committee::validity_threshold
31
32use crate::{
33 data_types::proof::quorum::{
34 CertificateEmbedsQuorum, CertificateSignaturesVerify, Intersection, ThresholdArithmetic,
35 },
36 manager::proof::{
37 commit::{
38 CertifiedBlockWasExecuted, CommitRestsOnValidation, IncomingBundlesMatchTheLocalInbox,
39 },
40 locking::{
41 ConfirmedVoteRoundMonotone, OneConfirmationVotePerRound, OneValidationVotePerRound,
42 SafetyStateRecovery,
43 },
44 model::{ConsensusInstance, DeterministicExecution, UnforgeableSignatures},
45 rounds::{CurrentRoundMonotone, RoundFloor, VoteRoundBelowCurrentRound},
46 safety::CommitAgreement,
47 timeouts::LeaderEligibility,
48 voting::{
49 ConfirmationOnlyInCurrentRound, FastConfirmationNeedsEmptyLock,
50 UnlockingRequiresHigherCertificate, VoteConstructionSites,
51 },
52 },
53};
54
55/// **Definition (Proof of misbehaviour).** A *proof of misbehaviour* against validator `v` is an
56/// [`EquivocationProof`] naming `v` for which [`EquivocationProof::check`] returns `Ok` against a
57/// committee. There are four shapes, and each exhibits a pair of `v`'s own signatures — or, for
58/// [`InvalidJustification`], a single signature plus the opening it commits to.
59///
60/// **What `check` establishes, and what it does not.** Signatures are verified against the
61/// [`ValidatorPublicKey`] carried *in the proof*, not against anything looked up in the committee.
62/// Of the four arms, only [`InvalidJustification`] consults the `committee` argument at all — to
63/// judge whether the opening was a quorum of it. So [`LockViolation`], [`DoubleVote`] and
64/// [`FirstRoundViolation`] are committee-independent: their verdicts hold whatever committee is
65/// supplied, and indeed whether or not the named validator belongs to one.
66///
67/// For [`InvalidJustification`] the committee does matter, and a vote is honest only relative to
68/// the committee of the epoch it was cast in; that epoch is not carried in the proof, so an
69/// auditor supplying a different epoch's committee could convict a correct validator. Throughout
70/// this module such a proof is understood to be adjudicated against the right one.
71///
72/// No arm checks committee membership or weight. An accepted proof therefore says "this key
73/// equivocated", not "this committee member equivocated": turning a set of proofs into a weight
74/// is the consumer's job, and [`ConflictCompleteness`] states its threshold about the set
75/// [`extract_equivocations`] returns, whose members are committee signers by construction.
76///
77/// [`EquivocationProof`]: crate::justification::EquivocationProof
78/// [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
79/// [`InvalidJustification`]: crate::justification::EquivocationProof::InvalidJustification
80/// [`LockViolation`]: crate::justification::EquivocationProof::LockViolation
81/// [`DoubleVote`]: crate::justification::EquivocationProof::DoubleVote
82/// [`FirstRoundViolation`]: crate::justification::EquivocationProof::FirstRoundViolation
83/// [`ValidatorPublicKey`]: linera_base::crypto::ValidatorPublicKey
84/// [`extract_equivocations`]: crate::justification::extract_equivocations
85/// [`Committee`]: linera_execution::committee::Committee
86pub trait MisbehaviourProof {}
87
88/// **Lemma (Double votes are never honest).** No correct validator is named by an accepted
89/// [`DoubleVote`] proof.
90///
91/// *Proof.* An accepted proof exhibits two signatures by `v` over [`VoteValue`]s that agree on
92/// round and kind, whose headers share a chain and height, and whose hashes differ; by
93/// [`UnforgeableSignatures`] only `v` could have produced them, so a correct `v` cast both votes.
94/// Sharing a chain and height means both votes belong to the same [`ConsensusInstance`] — a reset
95/// changes the height, and [`SafetyStateRecovery`] shows the one path that recreates an instance
96/// at an unchanged height preserves the votes rather than forgetting them. Then:
97///
98/// * `kind = Validated` contradicts [`OneValidationVotePerRound`];
99/// * `kind = Confirmed` contradicts [`OneConfirmationVotePerRound`];
100/// * `kind = Timeout` is not realizable at all: the proof carries [`BlockHeader`]s and checks the
101/// signature over `CryptoHash::new(header)`, whereas a timeout vote signs the hash of a
102/// [`Timeout`] value, so an accepted timeout-kind proof would require a block header colliding
103/// with a `Timeout` — excluded by [`UnforgeableSignatures`]. ∎
104///
105/// [`DoubleVote`]: crate::justification::EquivocationProof::DoubleVote
106/// [`VoteValue`]: crate::data_types::VoteValue
107/// [`BlockHeader`]: crate::block::BlockHeader
108/// [`Timeout`]: crate::block::Timeout
109pub trait DoubleVoteSoundness:
110 UnforgeableSignatures
111 + OneValidationVotePerRound
112 + OneConfirmationVotePerRound
113 + ConsensusInstance
114 + SafetyStateRecovery
115{
116}
117
118/// **Lemma (First-round attestations are never honestly contradicted).** No correct validator is
119/// named by an accepted [`FirstRoundViolation`] proof.
120///
121/// *Proof.* An accepted proof exhibits `v`'s confirmation vote at round `a` carrying the
122/// attestation, and `v`'s confirmation vote at a round `b < a` on the same chain and height; by
123/// [`UnforgeableSignatures`] a correct `v` cast both, in the same instance.
124///
125/// Both sites that set the attestation — the fast branch of [`ChainManager::create_vote`] and
126/// [`ChainManager::create_final_vote`] ([`VoteConstructionSites`]) — compute it as
127/// `round == self.ownership.get().first_round()`. The `ownership` register has exactly one
128/// writer, [`ChainManager::reset`], which by [`ConsensusInstance`] begins the instance, so
129/// `first_round()` is a constant `φ` throughout. The attestation at `a` therefore gives `a = φ`.
130///
131/// Now consider the vote at `b < a = φ`. By [`VoteConstructionSites`] it is either:
132///
133/// * [`ChainManager::create_final_vote`], which by [`ConfirmationOnlyInCurrentRound`] requires
134/// `current_round == b`. But [`RoundFloor`] makes `current_round ≥ φ = a > b` at all times.
135/// Contradiction.
136/// * the fast branch, so `b = Round::Fast`. Since `φ > b`, `φ` is not `Round::Fast`, which by
137/// [`ChainOwnership::first_round`] means `super_owners` is empty. But a fast-round proposal is
138/// rejected with `WorkerError::InvalidOwner` unless its proposer is a super owner
139/// ([`LeaderEligibility`]: `can_propose` returns `false` for `Round::Fast` for everyone else),
140/// so `v` never reaches the fast branch. Contradiction. ∎
141///
142/// [`FirstRoundViolation`]: crate::justification::EquivocationProof::FirstRoundViolation
143/// [`ChainManager::create_vote`]: crate::manager::ChainManager::create_vote
144/// [`ChainManager::create_final_vote`]: crate::manager::ChainManager::create_final_vote
145/// [`ChainManager::reset`]: crate::manager::ChainManager::reset
146/// [`ChainOwnership::first_round`]: linera_base::ownership::ChainOwnership::first_round
147pub trait FirstRoundSoundness:
148 UnforgeableSignatures
149 + VoteConstructionSites
150 + ConfirmationOnlyInCurrentRound
151 + RoundFloor
152 + LeaderEligibility
153 + ConsensusInstance
154{
155}
156
157/// **Lemma (Lock violations are never honest).** No correct validator is named by an accepted
158/// [`LockViolation`] proof — subject to the residual obligation below.
159///
160/// *Proof.* An accepted proof exhibits `v`'s confirmation vote for `X` at round `r` and `v`'s
161/// validation vote for `Y` at round `s`, with `hash(X) ≠ hash(Y)`, the same chain and height,
162/// `r < s`, and a signed unlocking round `u` with `u ≤ r` (or `u = None`). Suppose `v` correct.
163///
164/// *The confirmation came first.* Otherwise, after the validation at `s`,
165/// [`VoteRoundBelowCurrentRound`] and [`CurrentRoundMonotone`] pin `v`'s current round at `≥ s`.
166/// A later confirmation at `r < s` is then impossible: via [`ChainManager::create_final_vote`] it
167/// would need `current_round == r` ([`ConfirmationOnlyInCurrentRound`]); via the fast branch it
168/// would need `r = Round::Fast` and, by [`FastConfirmationNeedsEmptyLock`], an empty lock and no
169/// validation vote — contradicting the floor `≥ s` that the validation at `s` established.
170///
171/// *So let `(X', p)` be `v`'s stored confirmation vote just before the validation at `s`.* It is
172/// present, and by [`ConfirmedVoteRoundMonotone`], `p ≥ r`. Apply
173/// [`UnlockingRequiresHigherCertificate`] to the validation vote, by the shape of its proposal:
174///
175/// * *Fresh proposal.* Rejected outright — `v` would not have voted.
176/// * *Regular retry with certificate `c`, and `X'` not matching `Y`'s proposal.* The guard is
177/// `p < c.round`, and `c.round` is exactly the signed `u`. With `u ≤ r ≤ p` this gives
178/// `p < u ≤ p`. Contradiction.
179/// * *Regular retry with `X'` matching `Y`'s proposal.* The guard is `p ≤ u`, so
180/// `p ≤ u ≤ r ≤ p` forces `p = u = r`.
181/// * *Fast retry.* The signed `u` is `None`, and the guard forces `p = Round::Fast` and `X'`
182/// matching `Y`'s proposal; with `Round::Fast` minimal and `p ≥ r`, again `p = r`.
183///
184/// The last two cases coincide: `v` confirmed `X` at round `r` and its stored confirmation at the
185/// same round `r = p` is `X'`, so [`OneConfirmationVotePerRound`] gives `X = X'`. Hence `X`
186/// matches `Y`'s [`ProposedBlock`] while `hash(X) ≠ hash(Y)` — the two blocks share a proposal and
187/// differ only in [`BlockExecutionOutcome`]. Sharing a proposal means sharing a
188/// `previous_block_hash`, so by [`UnforgeableSignatures`] (collision resistance) they have the
189/// same parent and hence the same ancestry and the same pre-state, and
190/// [`DeterministicExecution`] makes the outcome a function of that pre-state and the proposal.
191/// So `X = Y`, contradicting `hash(X) ≠ hash(Y)`. ∎
192///
193/// **Residual obligation.** Only the first two cases are unconditional; the last two are closed by
194/// [`DeterministicExecution`], the same hinge as [`FastRetryPreservesBlock`]. The ancestry
195/// argument avoids circularity — it follows `previous_block_hash` down rather than appealing to
196/// [`UniqueChain`] — but an execution engine whose outcome depends on the round without recording
197/// an `OracleResponse::Round` would make a correct validator convictable here.
198///
199/// [`LockViolation`]: crate::justification::EquivocationProof::LockViolation
200/// [`ChainManager::create_final_vote`]: crate::manager::ChainManager::create_final_vote
201/// [`ProposedBlock`]: crate::data_types::ProposedBlock
202/// [`BlockExecutionOutcome`]: crate::data_types::BlockExecutionOutcome
203/// [`FastRetryPreservesBlock`]: crate::manager::proof::safety::FastRetryPreservesBlock
204/// [`UniqueChain`]: crate::manager::proof::safety::UniqueChain
205pub trait LockViolationSoundness:
206 UnforgeableSignatures
207 + UnlockingRequiresHigherCertificate
208 + ConfirmedVoteRoundMonotone
209 + ConfirmationOnlyInCurrentRound
210 + FastConfirmationNeedsEmptyLock
211 + OneConfirmationVotePerRound
212 + VoteRoundBelowCurrentRound
213 + CurrentRoundMonotone
214 + DeterministicExecution
215{
216}
217
218/// **Lemma (Attested justifications are never honestly invalid).** No correct validator is named
219/// by an accepted [`InvalidJustification`] proof, when the proof is adjudicated against the
220/// committee of the vote's own epoch ([`MisbehaviourProof`]).
221///
222/// *Proof.* An accepted proof exhibits `v`'s signature over a [`VoteValue`] whose justification
223/// commitment is `opening.commitment()`, together with an `opening` on which `check_cited_quorum`
224/// fails. By [`UnforgeableSignatures`] a correct `v` produced that signature, so it is one of the
225/// five sites of [`VoteConstructionSites`]. Take them in turn.
226///
227/// * [`ChainManager::create_timeout_vote`], [`ChainManager::vote_fallback`], and the fast branch
228/// of [`ChainManager::create_vote`] all sign a commitment of `None`. A proof requires
229/// `Some(opening.commitment())`, so its signature check fails and it is not accepted.
230/// * *The non-fast branch of [`ChainManager::create_vote`], on a regular retry.* It signs
231/// `unlocking_round = Some(c.round)` and `Some(c.full_justification_commitment())`, whose
232/// opening is `c`'s own quorum. `check_cited_quorum` then asks exactly the four things that
233/// verifying `c` already established: that the opening's `value_hash` is the voted block's hash
234/// (given by [`BlockProposal::check_invariants`], which binds `c` to the proposed block); that
235/// `unlocking_round == Some(opening.round)` and `opening.round < round` (the first by
236/// construction, the second by `check_invariants`' `content.round > certificate.round`); that
237/// the opening's own unlocking round and previous commitment are both present or both absent
238/// (from [`LiteCertificate::check`], where a `Validated` certificate's `unlocking_round` equals
239/// its chain's top and its commitment is `None` exactly when the chain is empty); and that the
240/// opening's signatures form a quorum over the reconstructed `Validated` payload — which is
241/// verbatim the check [`LiteCertificate::check`] performed on `c`, the `first_round` component
242/// being `false` for every [`ValidatedBlockCertificate`]. A fresh or fast-retry proposal signs
243/// `None` and is covered by the first case.
244/// * *[`ChainManager::create_final_vote`].* It signs `unlocking_round = None`,
245/// `Some(validated.full_justification_commitment())` — or `None` in the chain's first round,
246/// again covered above — in the round `validated.round`. For `kind = Confirmed`
247/// `check_cited_quorum` requires `opening.round == round`, which holds since the vote's round
248/// *is* `validated.round`; the remaining conditions are as in the previous case, `validated`
249/// having been verified by the caller ([`ConfirmationNeedsValidatedCertificate`]). ∎
250///
251/// [`InvalidJustification`]: crate::justification::EquivocationProof::InvalidJustification
252/// [`VoteValue`]: crate::data_types::VoteValue
253/// [`ChainManager::create_timeout_vote`]: crate::manager::ChainManager::create_timeout_vote
254/// [`ChainManager::vote_fallback`]: crate::manager::ChainManager::vote_fallback
255/// [`ChainManager::create_vote`]: crate::manager::ChainManager::create_vote
256/// [`ChainManager::create_final_vote`]: crate::manager::ChainManager::create_final_vote
257/// [`BlockProposal::check_invariants`]: crate::data_types::BlockProposal::check_invariants
258/// [`LiteCertificate::check`]: crate::types::LiteCertificate::check
259/// [`ValidatedBlockCertificate`]: crate::types::ValidatedBlockCertificate
260/// [`ConfirmationNeedsValidatedCertificate`]: crate::manager::proof::voting::ConfirmationNeedsValidatedCertificate
261pub trait InvalidJustificationSoundness:
262 UnforgeableSignatures + VoteConstructionSites + MisbehaviourProof
263{
264}
265
266/// **Theorem (Soundness — no correct validator is convictable).** If
267/// [`EquivocationProof::check`] accepts a proof naming `v` against the committee of the epoch its
268/// votes were cast in, then `v` is faulty in the sense of [`CorrectValidator`].
269///
270/// *Proof.* By cases on the four variants: [`DoubleVoteSoundness`], [`FirstRoundSoundness`],
271/// [`LockViolationSoundness`] and [`InvalidJustificationSoundness`]. ∎
272///
273/// Note what this does *not* assume: no [`MaxByzantineWeight`], no synchrony, no bound on how many
274/// other validators misbehaved. Soundness is a statement about one validator's own signatures, so
275/// it survives arbitrary corruption of everyone else — which is what makes a conviction meaningful
276/// in the regime where accountability is invoked. For three of the four variants it does not even
277/// depend on the committee ([`MisbehaviourProof`]); only the [`InvalidJustification`] case needs
278/// the right one.
279///
280/// [`InvalidJustification`]: crate::justification::EquivocationProof::InvalidJustification
281///
282/// [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
283/// [`CorrectValidator`]: crate::manager::proof::model::CorrectValidator
284/// [`MaxByzantineWeight`]: crate::manager::proof::model::MaxByzantineWeight
285pub trait ProofSoundness:
286 DoubleVoteSoundness + FirstRoundSoundness + LockViolationSoundness + InvalidJustificationSoundness
287{
288}
289
290/// **Definition (Sound justification chain).** A [`JustificationChain`] carried by a confirmed
291/// certificate for block `B` is *sound* when every link's signatures form a quorum over the
292/// `Validated` payload reconstructed for that link — the payload with `B`'s hash, the link's
293/// round, the previous link's round as unlocking round, and the previous link's commitment.
294///
295/// [`audit_confirmation`] returns an empty list exactly when the chain is sound: it reconstructs
296/// each link's payload in order and calls `check_signatures` on it, returning at the first
297/// failure. Soundness is *not* implied by the certificate verifying:
298/// [`LiteCertificate::check`] deliberately skips the links, relying instead on the attestation
299/// carried by the quorum above them, which is what makes [`ChainAuditability`] the fallback.
300///
301/// [`JustificationChain`]: crate::justification::JustificationChain
302/// [`audit_confirmation`]: crate::justification::audit_confirmation
303/// [`LiteCertificate::check`]: crate::types::LiteCertificate::check
304pub trait SoundChain {}
305
306/// **Lemma (A sound chain tiles every round below the confirmation).** Let a valid
307/// [`ConfirmedBlockCertificate`] in round `s` carry a non-empty chain with link rounds
308/// `ρ₀ < ρ₁ < … < ρₖ`. Then `ρₖ = s`, link `i` was cast under unlocking round `ρᵢ₋₁` (and link `0`
309/// under `None`), and the half-open windows
310///
311/// ```text
312/// [⊥, ρ₀), [ρ₀, ρ₁), …, [ρₖ₋₁, ρₖ)
313/// ```
314///
315/// partition the rounds strictly below `s`. In particular every round `r < s` lies in exactly one
316/// link's window.
317///
318/// *Proof.* [`JustificationChain::verify`] rejects unless the rounds strictly increase, and
319/// [`LiteCertificate::check`] on a `Confirmed` certificate with a non-empty chain requires
320/// `top == self.round`, i.e. `ρₖ = s`. [`JustificationChain::commitment`] folds the chain from the
321/// bottom, setting each link's `unlocking_round` to the round of the link below and `None` for the
322/// first — so the reconstructed payload of link `i` carries unlocking round `ρᵢ₋₁`, which is the
323/// window's lower bound; the upper bound `ρᵢ` is where the link's own votes were cast. Consecutive
324/// windows abut and the first is unbounded below, so their union is `[⊥, ρₖ) = [⊥, s)`. ∎
325///
326/// This is what makes the chain walk in [`extract_equivocations`] exhaustive rather than
327/// best-effort: a lower confirmation cannot slip between two links.
328///
329/// [`ConfirmedBlockCertificate`]: crate::types::ConfirmedBlockCertificate
330/// [`JustificationChain::verify`]: crate::justification::JustificationChain::verify
331/// [`JustificationChain::commitment`]: crate::justification::JustificationChain::commitment
332/// [`LiteCertificate::check`]: crate::types::LiteCertificate::check
333/// [`extract_equivocations`]: crate::justification::extract_equivocations
334pub trait ChainTilesRounds {}
335
336/// **Theorem (Completeness — a conflict convicts a validity threshold).** Let two valid
337/// [`ConfirmedBlockCertificate`]s for conflicting blocks at the same chain and height, valid for
338/// the *same* committee and carrying sound chains ([`SoundChain`]), be certified in rounds
339/// `r ≤ s`. Then [`extract_equivocations`] applied to their [`JustifiedConfirmation`]s returns
340/// proofs that [`EquivocationProof::check`] accepts, naming validators of total weight at least
341/// [`Committee::validity_threshold`].
342///
343/// *Proof.* By [`CertificateEmbedsQuorum`] each certificate's confirmation signatures form a
344/// quorum, and by [`ChainTilesRounds`] so does each link of a sound chain. Three cases, which are
345/// exactly the three the implementation tries.
346///
347/// * **`r = s`.** `double_confirm` walks the intersection of the two confirmation quorums, which
348/// by [`Intersection`] has weight at least `f⁺`, emitting a [`DoubleVote`] for each member. Each
349/// is accepted: the blocks differ, the chain and height agree, and by
350/// [`CertificateSignaturesVerify`] both extracted signatures verify individually — which is
351/// what [`EquivocationProof::check`] re-checks.
352/// * **`r < s` and the higher certificate carries a chain.** By [`ChainTilesRounds`] some link's
353/// window contains `r`, i.e. `link.round > r` and its unlocking round is `≤ r` — precisely
354/// `walk_chain`'s guard. That link is a quorum, so its intersection with the lower confirmation
355/// quorum has weight at least `f⁺` by [`Intersection`], and each member gets a
356/// [`LockViolation`]. Each is accepted: `check` re-derives the same window condition
357/// `confirmed_round < validated_round` and `validated_unlocking_round ≤ confirmed_round`.
358/// * **`r < s` and the higher certificate carries no chain.** Then [`LiteCertificate::check`]
359/// accepted it only because its `first_round` attestation is set. `first_round_violation` walks
360/// the intersection of the two confirmation quorums — weight at least `f⁺` — emitting a
361/// [`FirstRoundViolation`] for each, accepted since `earlier_round = r < s = attested_round`.
362///
363/// In every case the blamed set is a full quorum intersection — which is where the weight claim
364/// comes from: its members are signers of a verified certificate, hence committee members of
365/// nonzero weight by [`CertificateEmbedsQuorum`]. [`EquivocationProof::check`] itself establishes
366/// no membership or weight ([`MisbehaviourProof`]), so a consumer tallying the threshold must read
367/// the weights from the committee. ∎
368///
369/// *Depends on the shared-committee hypothesis.* [`Intersection`] compares quorums of one
370/// committee; [`extract_equivocations`] checks only the chain and height, not the epoch, so two
371/// certificates declaring different epochs could yield no intersection at all. An auditor must
372/// establish that both certificates are valid for the same committee before drawing the
373/// conclusion.
374///
375/// [`ConfirmedBlockCertificate`]: crate::types::ConfirmedBlockCertificate
376/// [`extract_equivocations`]: crate::justification::extract_equivocations
377/// [`JustifiedConfirmation`]: crate::justification::JustifiedConfirmation
378/// [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
379/// [`Committee::validity_threshold`]: linera_execution::committee::Committee::validity_threshold
380/// [`DoubleVote`]: crate::justification::EquivocationProof::DoubleVote
381/// [`LockViolation`]: crate::justification::EquivocationProof::LockViolation
382/// [`FirstRoundViolation`]: crate::justification::EquivocationProof::FirstRoundViolation
383/// [`LiteCertificate::check`]: crate::types::LiteCertificate::check
384/// [`CertificateSignaturesVerify`]: crate::data_types::proof::quorum::CertificateSignaturesVerify
385pub trait ConflictCompleteness:
386 ChainTilesRounds
387 + SoundChain
388 + Intersection
389 + CertificateEmbedsQuorum
390 + CertificateSignaturesVerify
391 + ThresholdArithmetic
392{
393}
394
395/// **Lemma (An unsound chain convicts its attesters).** If a confirmed certificate's chain is not
396/// sound ([`SoundChain`]), [`audit_confirmation`] returns a non-empty list of proofs that
397/// [`EquivocationProof::check`] accepts.
398///
399/// *Proof.* [`audit_confirmation`] walks the links upward and stops at the lowest one whose
400/// reconstructed payload fails `check_signatures`. Every validator at the level immediately above
401/// — the next link, or the confirmation quorum if the bad link is the top one — signed a payload
402/// whose justification commitment is that link's `CommittedQuorum` hash, so each receives an
403/// [`InvalidJustification`] carrying that signature and that opening. Each is accepted:
404/// [`EquivocationProof::check`] verifies the signature against the reconstructed payload and then
405/// requires `check_cited_quorum` to fail, which it does, the opening's signatures not forming a
406/// quorum. ∎
407///
408/// **Weaker than [`ConflictCompleteness`], deliberately.** The blamed set is a quorum only when
409/// the bad link is the top one, where the accusers are the certificate's own — verified —
410/// confirmation quorum. Lower down, the accusers are the next link, whose own signatures the audit
411/// has not yet reached, so they may be fewer than a quorum. Each individual proof is still
412/// accepted, and an unsound level above is itself auditable one step further up; what is not
413/// guaranteed is a `f⁺`-weight blame set from a single pass. Repairing that would mean verifying
414/// links during certificate checking, which is the cost the attestation scheme exists to avoid.
415///
416/// [`audit_confirmation`]: crate::justification::audit_confirmation
417/// [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
418/// [`InvalidJustification`]: crate::justification::EquivocationProof::InvalidJustification
419pub trait ChainAuditability: SoundChain + CertificateEmbedsQuorum {}
420
421/// **Lemma (Two validated blocks in one round convict a validity threshold).** If two valid
422/// [`ValidatedBlockCertificate`]s for conflicting blocks are certified in the *same* round and are
423/// valid for the same committee, [`extract_double_validations`] returns accepted [`DoubleVote`]
424/// proofs naming validators of total weight at least [`Committee::validity_threshold`].
425///
426/// *Proof.* By [`CertificateEmbedsQuorum`] both signature sets are quorums; by [`Intersection`]
427/// their intersection has weight at least `f⁺`; `double_vote` emits a proof for each member, with
428/// `kind = Validated` and the round both share. Each is accepted: the blocks differ, the chain and
429/// height agree, and by [`CertificateSignaturesVerify`] the extracted signatures verify
430/// individually. ∎
431///
432/// This is the accountability counterpart of
433/// [`UniqueValidatedBlockPerRound`](crate::manager::proof::locking::UniqueValidatedBlockPerRound):
434/// that lemma says the situation cannot arise below the fault bound, this one says it is
435/// attributable if it does. Note the round equality is required — validating conflicting blocks in
436/// *different* rounds is legitimate, which is exactly what locks exist to regulate.
437///
438/// [`ValidatedBlockCertificate`]: crate::types::ValidatedBlockCertificate
439/// [`extract_double_validations`]: crate::justification::extract_double_validations
440/// [`DoubleVote`]: crate::justification::EquivocationProof::DoubleVote
441/// [`Committee::validity_threshold`]: linera_execution::committee::Committee::validity_threshold
442/// [`CertificateSignaturesVerify`]: crate::data_types::proof::quorum::CertificateSignaturesVerify
443pub trait DoubleValidationCompleteness:
444 Intersection + CertificateEmbedsQuorum + CertificateSignaturesVerify + ThresholdArithmetic
445{
446}
447
448/// **Theorem (Accountable safety).** For every chain and height, one of the following holds:
449///
450/// 1. at most one block is committed there ([`CommitAgreement`]); or
451/// 2. two conflicting confirmed certificates exist, and then — from those certificates alone,
452/// with no further observation of the network — validators of total weight at least
453/// [`Committee::validity_threshold`] are convictable by proofs that
454/// [`EquivocationProof::check`] accepts, every one of them genuinely faulty
455/// ([`ProofSoundness`]); or
456/// 3. a certificate's justification chain is unsound, and then its attesters are convictable
457/// ([`ChainAuditability`]) with no conflict required at all.
458///
459/// *Proof.* Case 1 is [`CommitAgreement`], which holds whenever
460/// [`MaxByzantineWeight`](crate::manager::proof::model::MaxByzantineWeight) does. If it fails,
461/// there are conflicting confirmed certificates; if both carry sound chains, case 2 is
462/// [`ConflictCompleteness`] together with [`ProofSoundness`], and otherwise case 3 is
463/// [`ChainAuditability`]. ∎
464///
465/// Case 2 convicts strictly more weight than the fault bound permits — `f⁺` against a permitted
466/// `f⁺ − 1` — so a conviction set is itself evidence that the assumption underpinning
467/// [`CommitAgreement`] was violated, rather than merely that some validator misbehaved.
468///
469/// [`CommitAgreement`]: crate::manager::proof::safety::CommitAgreement
470/// [`EquivocationProof::check`]: crate::justification::EquivocationProof::check
471/// [`Committee::validity_threshold`]: linera_execution::committee::Committee::validity_threshold
472pub trait AccountableSafety:
473 ProofSoundness + ConflictCompleteness + ChainAuditability + CommitAgreement
474{
475}
476
477/// **Remark (What accountability does not cover).** Four exclusions, the third being the
478/// substantive one.
479///
480/// * **There is no adjudicator.** [`EquivocationProof`] is constructed and verified nowhere
481/// outside this module and its tests: no slashing operation consumes one, and no chain records
482/// one. [`AccountableSafety`] establishes *convictability* — that the evidence exists, is
483/// self-contained and verifies — not any protocol consequence. Wiring an adjudicator in would
484/// need a place to submit proofs and a stake to forfeit, neither of which exists today.
485///
486/// * **Only equivocation is attributable, not silence.** A validator that simply stops voting, or
487/// answers some clients and not others, produces no contradictory signature and is unconvictable.
488/// Liveness faults are outside the scheme by construction, which is why
489/// `linera_core::proof::assumptions::CorrectValidatorAvailability` is an assumption rather than
490/// something enforced.
491///
492/// * **Incorrect execution is not attributable, and its effects are not confined to one chain.**
493/// Nothing in [`EquivocationProof`] relates a block's [`ProposedBlock`] to its
494/// [`BlockExecutionOutcome`]. A validator that votes for exactly one block per round, with a
495/// sound chain, but whose block carries a fabricated outcome, yields no proof at all.
496///
497/// This is worse than it first looks, because the outcome is eight separately committed
498/// components ([`CertifiedBlockWasExecuted`]) and they differ sharply in reach. A wrong
499/// `state_hash` stays on the chain. A wrong `messages` or `events` field *leaves* it: the
500/// bundles are delivered into other chains' inboxes and consumed by their blocks, and the events
501/// are read across chains through `OracleResponse::Event`. Those downstream blocks are then
502/// themselves properly certified and are evidence of nobody's fault. So even a hypothetical
503/// fraud proof would convict one block's executors while leaving a transitively corrupted
504/// subgraph standing — and since Linera finalizes on confirmation rather than after a challenge
505/// window, none of it can be reverted. Of the eight, only `blobs` are self-verifying, being
506/// content-addressed.
507///
508/// What the implementation has instead is *local detection*:
509/// `ChainWorkerState::execute_contiguous_block` re-executes the block and rejects a mismatch
510/// with [`ChainError::CorruptedChainState`]. That is unilateral — the detecting node holds
511/// nothing transferable, and any peer must redo the work — and it is incomplete in three ways:
512/// the certificate's `oracle_responses` are *replayed* into the re-execution rather than
513/// re-derived, so a fabricated oracle answer reproduces the same state hash and is never caught;
514/// `preprocess_certified_block` does not execute at all, taking `messages` and `events` from the
515/// certificate; and the `execution_state_cache` hit path skips re-execution.
516///
517/// The properties that do protect against a bad outcome are [`CertifiedBlockWasExecuted`] and,
518/// for the cross-chain component, [`IncomingBundlesMatchTheLocalInbox`]. Unlike everything else in
519/// this module both need
520/// [`MaxByzantineWeight`](crate::manager::proof::model::MaxByzantineWeight): validity degrades
521/// above the fault bound with no forensic residue, whereas agreement degrades with one.
522///
523/// * **The blame set is a threshold, not a census.** [`ConflictCompleteness`] names *a* quorum
524/// intersection; other validators may have equivocated without appearing in it, and running
525/// [`extract_equivocations`] on further certificate pairs may name more.
526///
527/// [`EquivocationProof`]: crate::justification::EquivocationProof
528/// [`ProposedBlock`]: crate::data_types::ProposedBlock
529/// [`BlockExecutionOutcome`]: crate::data_types::BlockExecutionOutcome
530/// [`ChainError::CorruptedChainState`]: crate::ChainError::CorruptedChainState
531/// [`extract_equivocations`]: crate::justification::extract_equivocations
532/// [`CertifiedBlockWasExecuted`]: crate::manager::proof::commit::CertifiedBlockWasExecuted
533/// [`IncomingBundlesMatchTheLocalInbox`]: crate::manager::proof::commit::IncomingBundlesMatchTheLocalInbox
534pub trait AccountabilityScope:
535 AccountableSafety
536 + DoubleValidationCompleteness
537 + CertifiedBlockWasExecuted
538 + IncomingBundlesMatchTheLocalInbox
539 + CommitRestsOnValidation
540{
541}