Skip to main content

kinetic_core/error/
drand.rs

1//! Drand Quicknet kyn acquisition and verification error types (`KIN-DRA-NNN`).
2//!
3//! [`DrandError`] is returned by [`DrandClient::fetch_latest`](crate::drand::DrandClient::fetch_latest)
4//! when the Quicknet randomness beacon cannot be reached, returns an invalid kyn, or the
5//! BLS threshold signature fails mathematical verification.
6//!
7//! ## Protocol Context
8//!
9//! Network kyns are the heartbeat of the Kinetic protocol. Every VDF commitment encodes
10//! the current kyn kyn as a salt, and every reveal must include the Drand randomness
11//! at the time of commitment. An invalid or stale kyn breaks the time-lock guarantee.
12//!
13//! The daemon falls back to cached kyns (`DrandError::NoCachedKyn`) and gossipsub
14//! P2P propagation if all HTTP endpoints fail.
15use super::Severity;
16use thiserror::Error;
17
18/// Error type for drand beacon fetches and cache operations.
19#[derive(Error, Debug)]
20pub enum DrandError {
21    /// All configured endpoints returned errors or timed out.
22    #[error("All Drand endpoints failed")]
23    AllEndpointsFailed,
24    /// A network-level error (e.g. DNS failure, connection refused).
25    #[error("Network error: {0}")]
26    Network(String),
27    /// An endpoint returned a non-2xx HTTP status.
28    #[error("HTTP status error: {0}")]
29    HttpError(u16),
30    /// No kyn was found in the local cache (and the network is also unavailable).
31    #[error("No cached kyn found")]
32    NoCachedKyn,
33    /// JSON (de)serialization failed.
34    #[error("Serialization error: {0}")]
35    Serde(#[from] serde_json::Error),
36    /// A storage engine error occurred while reading or writing the cache.
37    #[error("Storage error: {0}")]
38    Storage(#[from] crate::error::StorageError),
39    /// An HTTP client error from the `reqwest` library.
40    #[error("Reqwest error: {0}")]
41    Reqwest(#[from] reqwest::Error),
42    /// The BLS threshold signature was mathematically invalid.
43    #[error("Invalid Drand signature")]
44    InvalidSignature,
45    /// The returned kyn is too old compared to the system clock.
46    #[error("Stale kyn: expected kyn ~{expected}, but got {got}")]
47    StaleKyn {
48        /// The expected Drand kyn based on the local system clock.
49        expected: u64,
50        /// The actual kyn returned by the endpoint.
51        got: u64,
52    },
53}
54
55impl PartialEq for DrandError {
56    fn eq(&self, other: &Self) -> bool {
57        match (self, other) {
58            (Self::AllEndpointsFailed, Self::AllEndpointsFailed) => true,
59            (Self::Network(a), Self::Network(b)) => a == b,
60            (Self::HttpError(a), Self::HttpError(b)) => a == b,
61            (Self::NoCachedKyn, Self::NoCachedKyn) => true,
62            (Self::Serde(a), Self::Serde(b)) => a.to_string() == b.to_string(),
63            (Self::Storage(a), Self::Storage(b)) => a == b,
64            (Self::Reqwest(a), Self::Reqwest(b)) => a.to_string() == b.to_string(),
65            (Self::InvalidSignature, Self::InvalidSignature) => true,
66            (
67                Self::StaleKyn {
68                    expected: e1,
69                    got: g1,
70                },
71                Self::StaleKyn {
72                    expected: e2,
73                    got: g2,
74                },
75            ) => e1 == e2 && g1 == g2,
76            _ => false,
77        }
78    }
79}
80impl Eq for DrandError {}
81
82impl DrandError {
83    /// Stable protocol error code.
84    pub fn code(&self) -> &'static str {
85        match self {
86            Self::AllEndpointsFailed => "KIN-DRA-001",
87            Self::Network(_) => "KIN-DRA-002",
88            Self::HttpError(_) => "KIN-DRA-003",
89            Self::NoCachedKyn => "KIN-DRA-004",
90            Self::Serde(_) => "KIN-DRA-005",
91            Self::Storage(_) => "KIN-DRA-006",
92            Self::Reqwest(_) => "KIN-DRA-007",
93            Self::InvalidSignature => "KIN-DRA-008",
94            Self::StaleKyn { .. } => "KIN-DRA-009",
95        }
96    }
97
98    /// RFC 7807 type URI for this error.
99    pub fn error_type_uri(&self) -> String {
100        format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
101    }
102
103    /// Severity level for logging and monitoring.
104    pub fn severity(&self) -> Severity {
105        match self {
106            Self::AllEndpointsFailed
107            | Self::Network(_)
108            | Self::HttpError(_)
109            | Self::NoCachedKyn
110            | Self::Reqwest(_)
111            | Self::StaleKyn { .. } => Severity::Warning,
112            Self::Serde(_) | Self::Storage(_) | Self::InvalidSignature => Severity::Error,
113        }
114    }
115
116    /// Whether the client should offer a retry action.
117    pub fn is_retryable(&self) -> bool {
118        matches!(
119            self,
120            Self::AllEndpointsFailed
121                | Self::Network(_)
122                | Self::HttpError(_)
123                | Self::Reqwest(_)
124                | Self::StaleKyn { .. }
125        )
126    }
127
128    /// Returns the user-facing message.
129    pub fn user_message(&self) -> String {
130        match self {
131            Self::AllEndpointsFailed => "All network endpoints failed.".to_string(),
132            Self::Network(_) | Self::HttpError(_) | Self::Reqwest(_) => {
133                "A network error occurred while fetching the network kyn.".to_string()
134            }
135            Self::NoCachedKyn => "No cached network kyn found.".to_string(),
136            Self::Serde(_) => "Failed to parse the network kyn.".to_string(),
137            Self::Storage(_) => {
138                "A storage error occurred while reading or writing the kyn cache.".to_string()
139            }
140            Self::InvalidSignature => "Invalid network signature.".to_string(),
141            Self::StaleKyn { .. } => "The fetched network kyn was too old.".to_string(),
142        }
143    }
144}