@@ -3,9 +3,9 @@ use core::fmt::{Display, Formatter};
33#[ cfg( feature = "bincode" ) ]
44use bincode:: { Decode , Encode } ;
55
6- use crate :: BlockHash ;
76use crate :: prelude:: CoreBlockHeight ;
87use crate :: sml:: quorum_validation_error:: QuorumValidationError ;
8+ use crate :: { BlockHash , QuorumHash } ;
99
1010#[ derive( Clone , Ord , PartialOrd , PartialEq , Eq , Hash , Debug ) ]
1111#[ cfg_attr( feature = "bincode" , derive( Encode , Decode ) ) ]
@@ -14,6 +14,29 @@ pub enum LLMQEntryVerificationSkipStatus {
1414 NotMarkedForVerification ,
1515 MissedList ( CoreBlockHeight ) ,
1616 UnknownBlock ( BlockHash ) ,
17+ /// The snapshot required to validate this quorum entry was not provided
18+ /// by the caller. Distinct from `UnknownBlock` so retry/back-off logic
19+ /// can target snapshot fetches separately from block fetches.
20+ MissingSnapshot ( BlockHash ) ,
21+ /// The chain lock at the given height/block hash was not provided by the
22+ /// caller. The block itself may be known; the chain-lock signature for
23+ /// it just hasn't been fetched yet. Distinct from `UnknownBlock` so
24+ /// retry logic can dispatch to a chain-lock fetch instead of a block
25+ /// fetch.
26+ MissingChainLock ( CoreBlockHeight , BlockHash ) ,
27+ /// The quorum entry came through without an attached
28+ /// `VerifyingChainLockSignaturesType::Rotating`. Typically happens when
29+ /// a QRInfo's historical diff covers a block range in which no rotating
30+ /// DKG successfully committed, so `apply_diff` extracts no
31+ /// `rotation_sig` and `feed_qr_info` can't populate the 4-sig tuple for
32+ /// the quorums in `lastCommitmentPerIndex`.
33+ MissingRotationChainLockSigs ( QuorumHash ) ,
34+ /// A specific rotation chain-lock signature at offset `h - n` was not
35+ /// present for the masternode diff at the given block hash. The first
36+ /// field is the rotation offset, the second is the diff block hash.
37+ /// Distinct from `MissingRotationChainLockSigs`, which covers the case
38+ /// where the entire 4-sig tuple is absent.
39+ MissingRotationChainLockSig ( u8 , BlockHash ) ,
1740 OtherContext ( String ) ,
1841}
1942
@@ -30,6 +53,21 @@ impl Display for LLMQEntryVerificationSkipStatus {
3053 LLMQEntryVerificationSkipStatus :: UnknownBlock ( block_hash) => {
3154 format ! ( "UnknownBlock({})" , block_hash)
3255 }
56+ LLMQEntryVerificationSkipStatus :: MissingSnapshot ( block_hash) => {
57+ format ! ( "MissingSnapshot({})" , block_hash)
58+ }
59+ LLMQEntryVerificationSkipStatus :: MissingChainLock ( height, block_hash) => {
60+ format ! ( "MissingChainLock({}, {})" , height, block_hash)
61+ }
62+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSigs ( quorum_hash) => {
63+ format ! ( "MissingRotationChainLockSigs({})" , quorum_hash)
64+ }
65+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSig (
66+ offset,
67+ block_hash,
68+ ) => {
69+ format ! ( "MissingRotationChainLockSig(h - {}, {})" , offset, block_hash)
70+ }
3371 LLMQEntryVerificationSkipStatus :: OtherContext ( message) => {
3472 format ! ( "OtherContext({message})" )
3573 }
@@ -49,6 +87,46 @@ pub enum LLMQEntryVerificationStatus {
4987 Skipped ( LLMQEntryVerificationSkipStatus ) ,
5088 Invalid ( QuorumValidationError ) ,
5189}
90+ impl From < QuorumValidationError > for LLMQEntryVerificationStatus {
91+ /// Classify a validation error as either `Skipped` (missing infrastructure
92+ /// data that the caller should have provided) or `Invalid` (the quorum
93+ /// data itself is genuinely bad).
94+ fn from ( error : QuorumValidationError ) -> Self {
95+ match error {
96+ QuorumValidationError :: RequiredBlockNotPresent ( block_hash, _) => {
97+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: UnknownBlock ( block_hash) )
98+ }
99+ // `VerifyingMasternodeListNotPresent` is grouped here because the
100+ // verifying masternode list at the validation height is caller-
101+ // supplied infrastructure, not quorum data. Treating it as
102+ // `Skipped` mirrors the sibling `RequiredMasternodeListNotPresent`
103+ // case and lets the caller refetch instead of rejecting the quorum.
104+ QuorumValidationError :: RequiredMasternodeListNotPresent ( height)
105+ | QuorumValidationError :: RequiredBlockHeightNotPresent ( height)
106+ | QuorumValidationError :: VerifyingMasternodeListNotPresent ( height) => {
107+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: MissedList ( height) )
108+ }
109+ QuorumValidationError :: RequiredSnapshotNotPresent ( hash) => {
110+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: MissingSnapshot ( hash) )
111+ }
112+ QuorumValidationError :: RequiredChainLockNotPresent ( height, block_hash) => {
113+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: MissingChainLock ( height, block_hash) )
114+ }
115+ QuorumValidationError :: RequiredRotatedChainLockSigsNotPresent ( quorum_hash) => {
116+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSigs (
117+ quorum_hash,
118+ ) )
119+ }
120+ QuorumValidationError :: RequiredRotatedChainLockSigNotPresent ( offset, block_hash) => {
121+ Self :: Skipped ( LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSig (
122+ offset, block_hash,
123+ ) )
124+ }
125+ other => Self :: Invalid ( other) ,
126+ }
127+ }
128+ }
129+
52130impl Display for LLMQEntryVerificationStatus {
53131 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
54132 f. write_str (
@@ -62,3 +140,107 @@ impl Display for LLMQEntryVerificationStatus {
62140 )
63141 }
64142}
143+
144+ #[ cfg( test) ]
145+ mod tests {
146+ use hashes:: Hash ;
147+
148+ use super :: * ;
149+
150+ fn dummy_hash ( byte : u8 ) -> BlockHash {
151+ BlockHash :: from_byte_array ( [ byte; 32 ] )
152+ }
153+
154+ #[ test]
155+ fn from_quorum_validation_error_classifies_each_arm ( ) {
156+ let h1 = dummy_hash ( 1 ) ;
157+ let h2 = dummy_hash ( 2 ) ;
158+ let h3 = dummy_hash ( 3 ) ;
159+ let h4 = dummy_hash ( 4 ) ;
160+ let h5 = dummy_hash ( 5 ) ;
161+
162+ let cases: Vec < ( QuorumValidationError , LLMQEntryVerificationStatus ) > = vec ! [
163+ (
164+ QuorumValidationError :: RequiredBlockNotPresent ( h1, "ctx" . to_string( ) ) ,
165+ LLMQEntryVerificationStatus :: Skipped (
166+ LLMQEntryVerificationSkipStatus :: UnknownBlock ( h1) ,
167+ ) ,
168+ ) ,
169+ (
170+ QuorumValidationError :: RequiredMasternodeListNotPresent ( 42 ) ,
171+ LLMQEntryVerificationStatus :: Skipped ( LLMQEntryVerificationSkipStatus :: MissedList (
172+ 42 ,
173+ ) ) ,
174+ ) ,
175+ (
176+ QuorumValidationError :: RequiredBlockHeightNotPresent ( 99 ) ,
177+ LLMQEntryVerificationStatus :: Skipped ( LLMQEntryVerificationSkipStatus :: MissedList (
178+ 99 ,
179+ ) ) ,
180+ ) ,
181+ (
182+ QuorumValidationError :: VerifyingMasternodeListNotPresent ( 123 ) ,
183+ LLMQEntryVerificationStatus :: Skipped ( LLMQEntryVerificationSkipStatus :: MissedList (
184+ 123 ,
185+ ) ) ,
186+ ) ,
187+ (
188+ QuorumValidationError :: RequiredSnapshotNotPresent ( h2) ,
189+ LLMQEntryVerificationStatus :: Skipped (
190+ LLMQEntryVerificationSkipStatus :: MissingSnapshot ( h2) ,
191+ ) ,
192+ ) ,
193+ (
194+ QuorumValidationError :: RequiredChainLockNotPresent ( 7 , h5) ,
195+ LLMQEntryVerificationStatus :: Skipped (
196+ LLMQEntryVerificationSkipStatus :: MissingChainLock ( 7 , h5) ,
197+ ) ,
198+ ) ,
199+ (
200+ QuorumValidationError :: RequiredRotatedChainLockSigsNotPresent ( h3) ,
201+ LLMQEntryVerificationStatus :: Skipped (
202+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSigs ( h3) ,
203+ ) ,
204+ ) ,
205+ (
206+ QuorumValidationError :: RequiredRotatedChainLockSigNotPresent ( 2 , h4) ,
207+ LLMQEntryVerificationStatus :: Skipped (
208+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSig ( 2 , h4) ,
209+ ) ,
210+ ) ,
211+ (
212+ QuorumValidationError :: InvalidQuorumPublicKey ,
213+ LLMQEntryVerificationStatus :: Invalid ( QuorumValidationError :: InvalidQuorumPublicKey ) ,
214+ ) ,
215+ ] ;
216+
217+ for ( error, expected) in cases {
218+ let actual: LLMQEntryVerificationStatus = error. clone ( ) . into ( ) ;
219+ assert_eq ! ( actual, expected, "case: {error:?}" ) ;
220+ }
221+ }
222+
223+ #[ test]
224+ fn skip_status_display_formats_new_variants ( ) {
225+ let h = dummy_hash ( 1 ) ;
226+ let cases: Vec < ( LLMQEntryVerificationSkipStatus , String ) > = vec ! [
227+ ( LLMQEntryVerificationSkipStatus :: MissingSnapshot ( h) , format!( "MissingSnapshot({h})" ) ) ,
228+ (
229+ LLMQEntryVerificationSkipStatus :: MissingChainLock ( 42 , h) ,
230+ format!( "MissingChainLock(42, {h})" ) ,
231+ ) ,
232+ (
233+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSigs ( h) ,
234+ format!( "MissingRotationChainLockSigs({h})" ) ,
235+ ) ,
236+ (
237+ LLMQEntryVerificationSkipStatus :: MissingRotationChainLockSig ( 2 , h) ,
238+ format!( "MissingRotationChainLockSig(h - 2, {h})" ) ,
239+ ) ,
240+ ] ;
241+
242+ for ( status, expected) in cases {
243+ assert_eq ! ( status. to_string( ) , expected, "case: {status:?}" ) ;
244+ }
245+ }
246+ }
0 commit comments