chie_shared/
errors.rs

1//! Error types for CHIE Protocol.
2
3use crate::ValidationError;
4use thiserror::Error;
5
6/// Protocol-level errors.
7///
8/// # Examples
9///
10/// ```
11/// use chie_shared::ProtocolError;
12///
13/// // Network error example
14/// fn connect_to_peer(peer_id: &str) -> Result<(), ProtocolError> {
15///     if peer_id.is_empty() {
16///         return Err(ProtocolError::PeerNotFound("empty peer ID".to_string()));
17///     }
18///     Ok(())
19/// }
20///
21/// // Content lookup error
22/// fn get_content(cid: &str) -> Result<Vec<u8>, ProtocolError> {
23///     if cid.starts_with("Qm") {
24///         Ok(vec![1, 2, 3])
25///     } else {
26///         Err(ProtocolError::ContentNotFound(cid.to_string()))
27///     }
28/// }
29///
30/// // Rate limiting
31/// fn check_rate_limit(request_count: u32) -> Result<(), ProtocolError> {
32///     if request_count > 100 {
33///         Err(ProtocolError::RateLimitExceeded)
34///     } else {
35///         Ok(())
36///     }
37/// }
38///
39/// assert!(connect_to_peer("12D3Koo").is_ok());
40/// assert!(get_content("QmTest").is_ok());
41/// assert!(check_rate_limit(50).is_ok());
42/// assert!(check_rate_limit(150).is_err());
43/// ```
44#[derive(Debug, Error)]
45pub enum ProtocolError {
46    #[error("Invalid signature")]
47    InvalidSignature,
48
49    #[error("Nonce already used (replay attack detected)")]
50    NonceReused,
51
52    #[error("Timestamp out of valid range")]
53    TimestampOutdated,
54
55    #[error("Content not found: {0}")]
56    ContentNotFound(String),
57
58    #[error("Chunk not found: index {0}")]
59    ChunkNotFound(u64),
60
61    #[error("Encryption error: {0}")]
62    EncryptionError(String),
63
64    #[error("Decryption error: {0}")]
65    DecryptionError(String),
66
67    #[error("Network error: {0}")]
68    NetworkError(String),
69
70    #[error("Database error: {0}")]
71    DatabaseError(String),
72
73    #[error("Validation error: {0}")]
74    ValidationError(String),
75
76    #[error("Peer not found: {0}")]
77    PeerNotFound(String),
78
79    #[error("Insufficient storage space")]
80    InsufficientStorage,
81
82    #[error("Bandwidth limit exceeded")]
83    BandwidthLimitExceeded,
84
85    #[error("Rate limit exceeded")]
86    RateLimitExceeded,
87
88    #[error("Invalid protocol version: {0}")]
89    InvalidProtocolVersion(String),
90
91    #[error("Connection failed: {0}")]
92    ConnectionFailed(String),
93
94    #[error("Timeout: {0}")]
95    Timeout(String),
96
97    #[error("Unauthorized: {0}")]
98    Unauthorized(String),
99}
100
101impl From<ValidationError> for ProtocolError {
102    fn from(err: ValidationError) -> Self {
103        ProtocolError::ValidationError(err.to_string())
104    }
105}
106
107/// Verification errors.
108///
109/// # Examples
110///
111/// ```
112/// use chie_shared::VerificationError;
113///
114/// // Signature verification
115/// fn verify_proof_signature(signature: &[u8]) -> Result<(), VerificationError> {
116///     if signature.len() != 64 {
117///         return Err(VerificationError::InvalidProviderSignature);
118///     }
119///     if signature.iter().all(|&b| b == 0) {
120///         return Err(VerificationError::InvalidProviderSignature);
121///     }
122///     Ok(())
123/// }
124///
125/// // Nonce validation
126/// fn check_nonce(nonce: &str, used_nonces: &[&str]) -> Result<(), VerificationError> {
127///     if used_nonces.contains(&nonce) {
128///         Err(VerificationError::NonceReused)
129///     } else {
130///         Ok(())
131///     }
132/// }
133///
134/// // Latency check
135/// fn validate_latency(latency_ms: u32) -> Result<(), VerificationError> {
136///     if latency_ms > 5000 {
137///         Err(VerificationError::InvalidLatency(latency_ms))
138///     } else {
139///         Ok(())
140///     }
141/// }
142///
143/// assert!(verify_proof_signature(&[1u8; 64]).is_ok());
144/// assert!(verify_proof_signature(&[0u8; 64]).is_err());
145/// assert!(check_nonce("abc123", &[]).is_ok());
146/// assert!(check_nonce("abc123", &["abc123"]).is_err());
147/// assert!(validate_latency(100).is_ok());
148/// assert!(validate_latency(6000).is_err());
149/// ```
150#[derive(Debug, Error)]
151pub enum VerificationError {
152    #[error("Invalid provider signature")]
153    InvalidProviderSignature,
154
155    #[error("Invalid requester signature")]
156    InvalidRequesterSignature,
157
158    #[error("Nonce has already been used")]
159    NonceReused,
160
161    #[error("Timestamp is too old or in the future")]
162    TimestampOutOfRange,
163
164    #[error("Statistical anomaly detected: {0}")]
165    AnomalyDetected(String),
166
167    #[error("Peer is banned: {0}")]
168    PeerBanned(String),
169
170    #[error("Invalid proof structure: {0}")]
171    InvalidProofStructure(String),
172
173    #[error("Challenge nonce mismatch")]
174    ChallengeMismatch,
175
176    #[error("Chunk hash mismatch")]
177    ChunkHashMismatch,
178
179    #[error("Invalid latency: {0}ms")]
180    InvalidLatency(u32),
181
182    #[error("Blacklisted peer: {0}")]
183    BlacklistedPeer(String),
184}
185
186impl From<ValidationError> for VerificationError {
187    fn from(err: ValidationError) -> Self {
188        VerificationError::InvalidProofStructure(err.to_string())
189    }
190}
191
192/// Reward calculation errors.
193///
194/// # Examples
195///
196/// ```
197/// use chie_shared::RewardError;
198///
199/// // Content registration check
200/// fn calculate_reward(content_cid: &str, registered: bool) -> Result<u64, RewardError> {
201///     if !registered {
202///         return Err(RewardError::ContentNotRegistered(content_cid.to_string()));
203///     }
204///     Ok(100) // 100 points
205/// }
206///
207/// // Balance check
208/// fn withdraw_points(amount: u64, balance: u64) -> Result<u64, RewardError> {
209///     if amount > balance {
210///         Err(RewardError::InsufficientBalance)
211///     } else {
212///         Ok(balance - amount)
213///     }
214/// }
215///
216/// // Reward amount validation
217/// fn validate_reward(amount: u64) -> Result<u64, RewardError> {
218///     if amount > 1_000_000 {
219///         Err(RewardError::InvalidRewardAmount(amount))
220///     } else {
221///         Ok(amount)
222///     }
223/// }
224///
225/// assert_eq!(calculate_reward("QmTest", true).unwrap(), 100);
226/// assert!(calculate_reward("QmTest", false).is_err());
227/// assert_eq!(withdraw_points(50, 100).unwrap(), 50);
228/// assert!(withdraw_points(150, 100).is_err());
229/// assert!(validate_reward(500).is_ok());
230/// assert!(validate_reward(2_000_000).is_err());
231/// ```
232#[derive(Debug, Error)]
233pub enum RewardError {
234    #[error("Content not registered: {0}")]
235    ContentNotRegistered(String),
236
237    #[error("Invalid proof data")]
238    InvalidProof,
239
240    #[error("Database error: {0}")]
241    DatabaseError(String),
242
243    #[error("Calculation failed: {0}")]
244    CalculationFailed(String),
245
246    #[error("Insufficient points balance")]
247    InsufficientBalance,
248
249    #[error("Creator not found: {0}")]
250    CreatorNotFound(String),
251
252    #[error("Invalid reward amount: {0}")]
253    InvalidRewardAmount(u64),
254
255    #[error("Reward distribution failed: {0}")]
256    DistributionFailed(String),
257}
258
259/// Result type alias for protocol operations.
260///
261/// # Examples
262///
263/// ```
264/// use chie_shared::{ProtocolResult, ProtocolError};
265///
266/// fn fetch_content(cid: &str) -> ProtocolResult<Vec<u8>> {
267///     if cid.is_empty() {
268///         return Err(ProtocolError::ContentNotFound("empty CID".to_string()));
269///     }
270///     Ok(vec![1, 2, 3, 4])
271/// }
272///
273/// // Using the result
274/// match fetch_content("QmTest") {
275///     Ok(data) => assert_eq!(data.len(), 4),
276///     Err(e) => panic!("Unexpected error: {}", e),
277/// }
278///
279/// // Error case
280/// assert!(fetch_content("").is_err());
281/// ```
282pub type ProtocolResult<T> = Result<T, ProtocolError>;
283
284/// Result type alias for verification operations.
285///
286/// # Examples
287///
288/// ```
289/// use chie_shared::{VerificationResult, VerificationError};
290///
291/// fn verify_bandwidth_proof(latency_ms: u32) -> VerificationResult<()> {
292///     if latency_ms > 5000 {
293///         return Err(VerificationError::InvalidLatency(latency_ms));
294///     }
295///     Ok(())
296/// }
297///
298/// // Chain operations
299/// fn verify_and_process(latency: u32) -> VerificationResult<String> {
300///     verify_bandwidth_proof(latency)?;
301///     Ok("Proof verified".to_string())
302/// }
303///
304/// assert!(verify_and_process(100).is_ok());
305/// assert!(verify_and_process(6000).is_err());
306/// ```
307pub type VerificationResult<T> = Result<T, VerificationError>;
308
309/// Result type alias for reward operations.
310///
311/// # Examples
312///
313/// ```
314/// use chie_shared::{RewardResult, RewardError};
315///
316/// fn distribute_reward(provider_id: &str, amount: u64) -> RewardResult<u64> {
317///     if amount > 1_000_000 {
318///         return Err(RewardError::InvalidRewardAmount(amount));
319///     }
320///     Ok(amount)
321/// }
322///
323/// // Combine multiple operations
324/// fn process_rewards(providers: &[&str], amount: u64) -> RewardResult<u64> {
325///     let mut total = 0;
326///     for provider in providers {
327///         total += distribute_reward(provider, amount)?;
328///     }
329///     Ok(total)
330/// }
331///
332/// assert_eq!(distribute_reward("peer1", 100).unwrap(), 100);
333/// assert_eq!(process_rewards(&["peer1", "peer2"], 100).unwrap(), 200);
334/// assert!(distribute_reward("peer1", 2_000_000).is_err());
335/// ```
336pub type RewardResult<T> = Result<T, RewardError>;
337
338/// Content validation errors.
339///
340/// # Examples
341///
342/// ```
343/// use chie_shared::ContentValidationError;
344///
345/// // Content size validation
346/// fn validate_content_size(size: u64) -> Result<(), ContentValidationError> {
347///     const MAX_SIZE: u64 = 100 * 1024 * 1024; // 100 MB
348///     const MIN_SIZE: u64 = 1024; // 1 KB
349///
350///     if size > MAX_SIZE {
351///         return Err(ContentValidationError::ContentTooLarge {
352///             size,
353///             max: MAX_SIZE,
354///         });
355///     }
356///     if size < MIN_SIZE {
357///         return Err(ContentValidationError::ContentTooSmall {
358///             size,
359///             min: MIN_SIZE,
360///         });
361///     }
362///     Ok(())
363/// }
364///
365/// // Chunk index validation
366/// fn validate_chunk_index(index: u64, total_chunks: u64) -> Result<(), ContentValidationError> {
367///     if index >= total_chunks {
368///         Err(ContentValidationError::InvalidChunkIndex {
369///             index,
370///             total: total_chunks,
371///         })
372///     } else {
373///         Ok(())
374///     }
375/// }
376///
377/// // Signature length validation
378/// fn validate_signature(sig: &[u8]) -> Result<(), ContentValidationError> {
379///     if sig.len() != 64 {
380///         Err(ContentValidationError::InvalidSignatureLength {
381///             expected: 64,
382///             actual: sig.len(),
383///         })
384///     } else {
385///         Ok(())
386///     }
387/// }
388///
389/// assert!(validate_content_size(50 * 1024).is_ok());
390/// assert!(validate_content_size(500).is_err()); // Too small
391/// assert!(validate_content_size(200 * 1024 * 1024).is_err()); // Too large
392/// assert!(validate_chunk_index(5, 10).is_ok());
393/// assert!(validate_chunk_index(15, 10).is_err());
394/// assert!(validate_signature(&[0u8; 64]).is_ok());
395/// assert!(validate_signature(&[0u8; 32]).is_err());
396/// ```
397#[derive(Debug, Error)]
398pub enum ContentValidationError {
399    #[error("Invalid content size: {0} bytes")]
400    InvalidContentSize(u64),
401
402    #[error("Invalid chunk index: {index} out of {total}")]
403    InvalidChunkIndex { index: u64, total: u64 },
404
405    #[error("Invalid timestamp: {0}")]
406    InvalidTimestamp(String),
407
408    #[error("Invalid signature length: expected {expected}, got {actual}")]
409    InvalidSignatureLength { expected: usize, actual: usize },
410
411    #[error("Invalid public key length: expected {expected}, got {actual}")]
412    InvalidPublicKeyLength { expected: usize, actual: usize },
413
414    #[error("Invalid bandwidth value: {0}")]
415    InvalidBandwidth(String),
416
417    #[error("Content too large: {size} bytes exceeds max {max} bytes")]
418    ContentTooLarge { size: u64, max: u64 },
419
420    #[error("Content too small: {size} bytes is below min {min} bytes")]
421    ContentTooSmall { size: u64, min: u64 },
422
423    #[error("Invalid nonce length: expected {expected}, got {actual}")]
424    InvalidNonceLength { expected: usize, actual: usize },
425
426    #[error("Invalid hash length: expected {expected}, got {actual}")]
427    InvalidHashLength { expected: usize, actual: usize },
428}
429
430/// Result type alias for content validation operations.
431pub type ContentValidationResult<T> = Result<T, ContentValidationError>;
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_protocol_error_display() {
439        assert_eq!(
440            ProtocolError::InvalidSignature.to_string(),
441            "Invalid signature"
442        );
443
444        assert_eq!(
445            ProtocolError::NonceReused.to_string(),
446            "Nonce already used (replay attack detected)"
447        );
448
449        assert_eq!(
450            ProtocolError::TimestampOutdated.to_string(),
451            "Timestamp out of valid range"
452        );
453
454        assert_eq!(
455            ProtocolError::ContentNotFound("QmTest123".to_string()).to_string(),
456            "Content not found: QmTest123"
457        );
458
459        assert_eq!(
460            ProtocolError::ChunkNotFound(42).to_string(),
461            "Chunk not found: index 42"
462        );
463
464        assert_eq!(
465            ProtocolError::EncryptionError("Key error".to_string()).to_string(),
466            "Encryption error: Key error"
467        );
468
469        assert_eq!(
470            ProtocolError::DecryptionError("Invalid data".to_string()).to_string(),
471            "Decryption error: Invalid data"
472        );
473
474        assert_eq!(
475            ProtocolError::NetworkError("Connection timeout".to_string()).to_string(),
476            "Network error: Connection timeout"
477        );
478
479        assert_eq!(
480            ProtocolError::DatabaseError("Query failed".to_string()).to_string(),
481            "Database error: Query failed"
482        );
483    }
484
485    #[test]
486    fn test_verification_error_display() {
487        assert_eq!(
488            VerificationError::InvalidProviderSignature.to_string(),
489            "Invalid provider signature"
490        );
491
492        assert_eq!(
493            VerificationError::InvalidRequesterSignature.to_string(),
494            "Invalid requester signature"
495        );
496
497        assert_eq!(
498            VerificationError::NonceReused.to_string(),
499            "Nonce has already been used"
500        );
501
502        assert_eq!(
503            VerificationError::TimestampOutOfRange.to_string(),
504            "Timestamp is too old or in the future"
505        );
506
507        assert_eq!(
508            VerificationError::AnomalyDetected("Suspicious pattern".to_string()).to_string(),
509            "Statistical anomaly detected: Suspicious pattern"
510        );
511
512        assert_eq!(
513            VerificationError::PeerBanned("12D3Koo...".to_string()).to_string(),
514            "Peer is banned: 12D3Koo..."
515        );
516    }
517
518    #[test]
519    fn test_reward_error_display() {
520        assert_eq!(
521            RewardError::ContentNotRegistered("QmAbc".to_string()).to_string(),
522            "Content not registered: QmAbc"
523        );
524
525        assert_eq!(RewardError::InvalidProof.to_string(), "Invalid proof data");
526
527        assert_eq!(
528            RewardError::DatabaseError("Connection lost".to_string()).to_string(),
529            "Database error: Connection lost"
530        );
531
532        assert_eq!(
533            RewardError::CalculationFailed("Division by zero".to_string()).to_string(),
534            "Calculation failed: Division by zero"
535        );
536    }
537
538    #[test]
539    fn test_error_source() {
540        // Errors should be compatible with std::error::Error
541        let err = ProtocolError::InvalidSignature;
542        let _: &dyn std::error::Error = &err;
543
544        let err = VerificationError::NonceReused;
545        let _: &dyn std::error::Error = &err;
546
547        let err = RewardError::InvalidProof;
548        let _: &dyn std::error::Error = &err;
549    }
550
551    #[test]
552    fn test_new_protocol_errors() {
553        assert_eq!(
554            ProtocolError::PeerNotFound("peer123".to_string()).to_string(),
555            "Peer not found: peer123"
556        );
557        assert_eq!(
558            ProtocolError::InsufficientStorage.to_string(),
559            "Insufficient storage space"
560        );
561        assert_eq!(
562            ProtocolError::BandwidthLimitExceeded.to_string(),
563            "Bandwidth limit exceeded"
564        );
565        assert_eq!(
566            ProtocolError::RateLimitExceeded.to_string(),
567            "Rate limit exceeded"
568        );
569    }
570
571    #[test]
572    fn test_new_verification_errors() {
573        assert_eq!(
574            VerificationError::ChallengeMismatch.to_string(),
575            "Challenge nonce mismatch"
576        );
577        assert_eq!(
578            VerificationError::ChunkHashMismatch.to_string(),
579            "Chunk hash mismatch"
580        );
581        assert_eq!(
582            VerificationError::InvalidLatency(5000).to_string(),
583            "Invalid latency: 5000ms"
584        );
585    }
586
587    #[test]
588    fn test_new_reward_errors() {
589        assert_eq!(
590            RewardError::InsufficientBalance.to_string(),
591            "Insufficient points balance"
592        );
593        assert_eq!(
594            RewardError::CreatorNotFound("creator123".to_string()).to_string(),
595            "Creator not found: creator123"
596        );
597        assert_eq!(
598            RewardError::InvalidRewardAmount(9999).to_string(),
599            "Invalid reward amount: 9999"
600        );
601    }
602
603    #[test]
604    fn test_validation_error_conversion() {
605        use crate::ValidationError;
606
607        let val_err = ValidationError::EmptyCid;
608        let proto_err: ProtocolError = val_err.into();
609        assert!(
610            proto_err
611                .to_string()
612                .contains("Content CID cannot be empty")
613        );
614
615        let val_err2 = ValidationError::SelfTransfer;
616        let verif_err: VerificationError = val_err2.into();
617        assert!(
618            verif_err
619                .to_string()
620                .contains("Provider and requester cannot be the same")
621        );
622    }
623
624    #[test]
625    fn test_result_type_aliases() {
626        fn test_protocol_result() -> ProtocolResult<String> {
627            Ok("success".to_string())
628        }
629
630        fn test_verification_result() -> VerificationResult<i32> {
631            Err(VerificationError::NonceReused)
632        }
633
634        fn test_reward_result() -> RewardResult<u64> {
635            Ok(1000)
636        }
637
638        assert!(test_protocol_result().is_ok());
639        assert!(test_verification_result().is_err());
640        assert_eq!(test_reward_result().unwrap(), 1000);
641    }
642
643    #[test]
644    fn test_content_validation_error_display() {
645        assert_eq!(
646            ContentValidationError::InvalidContentSize(1000).to_string(),
647            "Invalid content size: 1000 bytes"
648        );
649
650        assert_eq!(
651            ContentValidationError::InvalidChunkIndex { index: 5, total: 3 }.to_string(),
652            "Invalid chunk index: 5 out of 3"
653        );
654
655        assert_eq!(
656            ContentValidationError::InvalidTimestamp("future timestamp".to_string()).to_string(),
657            "Invalid timestamp: future timestamp"
658        );
659
660        assert_eq!(
661            ContentValidationError::InvalidSignatureLength {
662                expected: 64,
663                actual: 32
664            }
665            .to_string(),
666            "Invalid signature length: expected 64, got 32"
667        );
668
669        assert_eq!(
670            ContentValidationError::ContentTooLarge {
671                size: 1_000_000_000,
672                max: 500_000_000
673            }
674            .to_string(),
675            "Content too large: 1000000000 bytes exceeds max 500000000 bytes"
676        );
677    }
678
679    #[test]
680    fn test_content_validation_result() {
681        fn validate_size(size: u64) -> ContentValidationResult<u64> {
682            if size > 1000 {
683                Err(ContentValidationError::ContentTooLarge { size, max: 1000 })
684            } else {
685                Ok(size)
686            }
687        }
688
689        assert!(validate_size(500).is_ok());
690        assert!(validate_size(2000).is_err());
691    }
692}