@@ -80,9 +80,13 @@ mod stream;
8080pub mod variant;
8181pub use variant:: { Buffer , Variant } ;
8282
83- use commonware_consensus:: Block ;
8483use commonware_consensus:: simplex:: scheme:: Scheme ;
85- use commonware_consensus:: simplex:: types:: Finalization ;
84+ use commonware_consensus:: simplex:: types:: {
85+ Attributable as _, ConflictingFinalize , ConflictingNotarize , Finalization , NullifyFinalize ,
86+ } ;
87+ use commonware_consensus:: types:: { Epoch , Participant , View } ;
88+ use commonware_consensus:: { Block , Epochable as _, Viewable as _} ;
89+ use commonware_cryptography:: Digest ;
8690use commonware_utils:: { Acknowledgement , acknowledgement:: Exact } ;
8791
8892/// An update reported to the application: finalized tips, finalized blocks, or notarized blocks.
@@ -111,6 +115,102 @@ pub enum Update<B: Block, S: Scheme<B::Digest>, A: Acknowledgement = Exact> {
111115 /// blocks without waiting for finalization. For a given block, this update is reported before
112116 /// its [`Self::FinalizedBlock`] update.
113117 NotarizedBlock ( B ) ,
118+ /// Locally observed evidence of Byzantine behavior (equivocation) by a validator.
119+ ///
120+ /// Reported by the consensus batcher when a committee member signs conflicting votes.
121+ /// Delivery is best-effort and NOT deterministic: only nodes that received both
122+ /// conflicting votes observe the fault, and different nodes may observe it at
123+ /// different times (or not at all). Consumers must not apply state transitions
124+ /// based on this update alone.
125+ Fault ( FaultEvidence < B :: Digest , S > ) ,
126+ }
127+
128+ /// The kind of Byzantine fault observed.
129+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
130+ pub enum FaultKind {
131+ /// The validator signed notarize votes for two different proposals in the same view.
132+ ConflictingNotarize ,
133+ /// The validator signed finalize votes for two different proposals in the same view.
134+ ConflictingFinalize ,
135+ /// The validator signed both a nullify and a finalize for the same view.
136+ NullifyFinalize ,
137+ }
138+
139+ impl FaultKind {
140+ /// Stable label for metrics.
141+ pub const fn as_reason ( self ) -> & ' static str {
142+ match self {
143+ Self :: ConflictingNotarize => "equivocation_notarize" ,
144+ Self :: ConflictingFinalize => "equivocation_finalize" ,
145+ Self :: NullifyFinalize => "nullify_finalize" ,
146+ }
147+ }
148+ }
149+
150+ /// Cryptographic evidence of a Byzantine fault, self-contained and verifiable
151+ /// against the epoch's committee.
152+ #[ derive( Clone , Debug ) ]
153+ pub enum FaultProof < D : Digest , S : Scheme < D > > {
154+ /// Two conflicting signed notarize votes.
155+ ConflictingNotarize ( ConflictingNotarize < S , D > ) ,
156+ /// Two conflicting signed finalize votes.
157+ ConflictingFinalize ( ConflictingFinalize < S , D > ) ,
158+ /// A signed nullify and a signed finalize for the same view.
159+ NullifyFinalize ( NullifyFinalize < S , D > ) ,
160+ }
161+
162+ /// Locally observed Byzantine fault evidence with its consensus coordinates.
163+ #[ derive( Clone , Debug ) ]
164+ pub struct FaultEvidence < D : Digest , S : Scheme < D > > {
165+ /// The epoch in which the fault occurred.
166+ pub epoch : Epoch ,
167+ /// The view in which the fault occurred.
168+ pub view : View ,
169+ /// The committee index of the faulting validator (per the epoch's committee order).
170+ pub signer : Participant ,
171+ /// The signed evidence.
172+ pub proof : FaultProof < D , S > ,
173+ }
174+
175+ impl < D : Digest , S : Scheme < D > > FaultEvidence < D , S > {
176+ /// Builds evidence from a [`ConflictingNotarize`] activity.
177+ pub fn conflicting_notarize ( evidence : ConflictingNotarize < S , D > ) -> Self {
178+ Self {
179+ epoch : evidence. epoch ( ) ,
180+ view : evidence. view ( ) ,
181+ signer : evidence. signer ( ) ,
182+ proof : FaultProof :: ConflictingNotarize ( evidence) ,
183+ }
184+ }
185+
186+ /// Builds evidence from a [`ConflictingFinalize`] activity.
187+ pub fn conflicting_finalize ( evidence : ConflictingFinalize < S , D > ) -> Self {
188+ Self {
189+ epoch : evidence. epoch ( ) ,
190+ view : evidence. view ( ) ,
191+ signer : evidence. signer ( ) ,
192+ proof : FaultProof :: ConflictingFinalize ( evidence) ,
193+ }
194+ }
195+
196+ /// Builds evidence from a [`NullifyFinalize`] activity.
197+ pub fn nullify_finalize ( evidence : NullifyFinalize < S , D > ) -> Self {
198+ Self {
199+ epoch : evidence. epoch ( ) ,
200+ view : evidence. view ( ) ,
201+ signer : evidence. signer ( ) ,
202+ proof : FaultProof :: NullifyFinalize ( evidence) ,
203+ }
204+ }
205+
206+ /// The kind of fault this evidence proves.
207+ pub const fn kind ( & self ) -> FaultKind {
208+ match self . proof {
209+ FaultProof :: ConflictingNotarize ( _) => FaultKind :: ConflictingNotarize ,
210+ FaultProof :: ConflictingFinalize ( _) => FaultKind :: ConflictingFinalize ,
211+ FaultProof :: NullifyFinalize ( _) => FaultKind :: NullifyFinalize ,
212+ }
213+ }
114214}
115215
116216#[ cfg( test) ]
0 commit comments