1use super::vdf::VdfRejectReason;
15use super::Severity;
16use thiserror::Error;
17
18#[derive(Error, Debug, PartialEq, Eq)]
20pub enum RecordRejectReason {
21 #[error("invalid signature")]
23 InvalidSignature,
24 #[error("VDF proof invalid")]
26 InvalidVdf,
27 #[error("registration has expired")]
29 Expired,
30 #[error("name already owned by a different key")]
32 AlreadyOwned,
33
34 #[error("insufficient VDF iterations to claim ownership")]
36 InsufficientIterations,
37 #[error("lost XOR tie-break to stronger record")]
39 TieBroken,
40 #[error("commitment mismatch")]
42 CommitmentMismatch,
43 #[error("drand_signature contains invalid hex")]
45 InvalidDrandHex,
46 #[error("public key bytes are malformed")]
48 InvalidPublicKey,
49 #[error("signature bytes are malformed")]
51 MalformedSignature,
52}
53
54#[derive(Error, Debug)]
59pub enum ResolutionError {
60 #[error("Node is offline — no peers connected")]
62 Offline,
63 #[error("'{name}' not found after querying {peers_queried} peers")]
65 NotFound {
66 name: String,
68 peers_queried: usize,
70 },
71 #[error("'{name}' found but {count} record(s) failed VDF verification")]
73 VdfVerificationFailed {
74 name: String,
76 count: usize,
78 },
79 #[error("'{name}' registration has expired ({age} rounds old)")]
81 Expired {
82 name: String,
84 age: u64,
86 },
87 #[error("Resolution timed out after {elapsed_ms}ms ({peers_queried} peers queried)")]
89 Timeout {
90 name: String,
92 elapsed_ms: u64,
94 peers_queried: usize,
96 },
97 #[error("Internal error: {message}")]
99 Internal {
100 message: String,
102 #[source]
104 source: Option<Box<dyn std::error::Error + Send + Sync>>,
105 },
106}
107
108impl ResolutionError {
109 pub fn code(&self) -> &'static str {
111 match self {
112 Self::Offline => "KIN-RES-001",
113 Self::NotFound { .. } => "KIN-RES-002",
114 Self::VdfVerificationFailed { .. } => "KIN-RES-003",
115 Self::Expired { .. } => "KIN-RES-004",
116 Self::Timeout { .. } => "KIN-RES-005",
117 Self::Internal { .. } => "KIN-RES-006",
118 }
119 }
120
121 pub fn error_type_uri(&self) -> String {
123 format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
124 }
125
126 pub fn is_retryable(&self) -> bool {
128 matches!(self, Self::Offline | Self::Timeout { .. })
129 }
130
131 pub fn severity(&self) -> Severity {
133 match self {
134 Self::Offline => Severity::Warning,
135 Self::NotFound { .. } => Severity::Info,
136 Self::VdfVerificationFailed { .. } => Severity::Error,
137 Self::Expired { .. } => Severity::Info,
138 Self::Timeout { .. } => Severity::Warning,
139 Self::Internal { .. } => Severity::Error,
140 }
141 }
142
143 pub fn user_message(&self) -> String {
145 match self {
146 Self::Offline => {
147 "You appear to be offline. Check your internet connection.".to_string()
148 }
149 Self::NotFound { name, .. } => {
150 format!("'{}' is not registered on the Kinetic network.", name)
151 }
152 Self::VdfVerificationFailed { name, .. } => format!(
153 "'{}' has an invalid cryptographic proof. This record may have been tampered with.",
154 name
155 ),
156 Self::Expired { name, .. } => format!(
157 "'{}' registration has expired. The owner needs to renew it.",
158 name
159 ),
160 Self::Timeout { name, .. } => format!(
161 "The network took too long to respond for '{}'. Please try again.",
162 name
163 ),
164 Self::Internal { .. } => {
165 "An internal network error occurred. Please try again.".to_string()
166 }
167 }
168 }
169
170 pub fn details(&self) -> serde_json::Value {
172 match self {
173 Self::NotFound { peers_queried, .. } => {
174 serde_json::json!({ "peers_queried": peers_queried })
175 }
176 Self::Timeout {
177 elapsed_ms,
178 peers_queried,
179 ..
180 } => serde_json::json!({ "elapsed_ms": elapsed_ms, "peers_queried": peers_queried }),
181 Self::VdfVerificationFailed { count, .. } => {
182 serde_json::json!({ "failed_record_count": count })
183 }
184 Self::Expired { age, .. } => serde_json::json!({ "age_rounds": age }),
185 _ => serde_json::Value::Null,
186 }
187 }
188}
189
190#[derive(Error, Debug)]
194pub enum PublishError {
195 #[error("Node is offline — cannot publish to the DHT")]
197 Offline,
198 #[error("VDF proof is invalid: {0}")]
200 InvalidProof(#[from] VdfRejectReason),
201 #[error("'{name}' is already owned by a different key")]
203 AlreadyOwned {
204 name: String,
206 },
207 #[error("All {count} DHT put operations failed")]
209 AllFailed {
210 count: usize,
212 },
213 #[error("Rejected by the network: {0}")]
215 Rejected(String),
216 #[error("Internal error: {message}")]
218 Internal {
219 message: String,
221 #[source]
223 source: Option<Box<dyn std::error::Error + Send + Sync>>,
224 },
225}
226
227impl PublishError {
228 pub fn code(&self) -> &'static str {
230 match self {
231 Self::Offline => "KIN-PUB-001",
232 Self::InvalidProof(_) => "KIN-PUB-002",
233 Self::AlreadyOwned { .. } => "KIN-PUB-003",
234 Self::AllFailed { .. } => "KIN-PUB-004",
235 Self::Rejected(_) => "KIN-PUB-005",
236 Self::Internal { .. } => "KIN-PUB-006",
237 }
238 }
239
240 pub fn error_type_uri(&self) -> String {
242 format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
243 }
244
245 pub fn is_retryable(&self) -> bool {
247 matches!(self, Self::Offline | Self::AllFailed { .. })
248 }
249
250 pub fn severity(&self) -> Severity {
252 match self {
253 Self::Offline => Severity::Warning,
254 Self::InvalidProof(_) => Severity::Error,
255 Self::AlreadyOwned { .. } => Severity::Info,
256 Self::AllFailed { .. } => Severity::Warning,
257 Self::Rejected(_) => Severity::Warning,
258 Self::Internal { .. } => Severity::Error,
259 }
260 }
261
262 pub fn user_message(&self) -> String {
264 match self {
265 Self::Offline => "You appear to be offline. Cannot publish to the network.".to_string(),
266 Self::InvalidProof(_) => "The VDF proof is invalid and was rejected.".to_string(),
267 Self::AlreadyOwned { name } => {
268 format!("'{}' is already registered under a different key.", name)
269 }
270 Self::AllFailed { .. } => {
271 "The network rejected all publish attempts. Please try again.".to_string()
272 }
273 Self::Rejected(reason) => format!("Publish rejected: {}", reason),
274 Self::Internal { .. } => "An internal error occurred during publishing.".to_string(),
275 }
276 }
277
278 pub fn details(&self) -> serde_json::Value {
280 match self {
281 Self::AllFailed { count } => serde_json::json!({ "failed_count": count }),
282 Self::InvalidProof(r) => serde_json::json!({ "reason": r.to_string() }),
283 _ => serde_json::Value::Null,
284 }
285 }
286}
287
288#[derive(Error, Debug)]
292pub enum RegistrationError {
293 #[error("Name '{name}' contains invalid characters")]
295 InvalidName {
296 name: String,
298 },
299 #[error("VDF computation failed: {0}")]
301 VdfFailed(#[from] VdfRejectReason),
302 #[error("Commitment mismatch — reveal data does not match commitment")]
304 CommitmentMismatch,
305 #[error("'{name}' is already owned by a different key")]
307 AlreadyOwned {
308 name: String,
310 },
311 #[error("A VDF registration is already in progress for '{name}'")]
313 AlreadyInProgress {
314 name: String,
316 },
317 #[error("Registration rejected by the network: {reason}")]
319 NetworkRejected {
320 reason: RecordRejectReason,
322 },
323 #[error("Internal error: {message}")]
325 Internal {
326 message: String,
328 #[source]
330 source: Option<Box<dyn std::error::Error + Send + Sync>>,
331 },
332}
333
334impl RegistrationError {
335 pub fn code(&self) -> &'static str {
337 match self {
338 Self::InvalidName { .. } => "KIN-REG-001",
339 Self::VdfFailed(_) => "KIN-REG-002",
340 Self::CommitmentMismatch => "KIN-REG-003",
341 Self::AlreadyOwned { .. } => "KIN-REG-004",
342 Self::AlreadyInProgress { .. } => "KIN-REG-005",
343 Self::NetworkRejected { .. } => "KIN-REG-006",
344 Self::Internal { .. } => "KIN-REG-007",
345 }
346 }
347
348 pub fn error_type_uri(&self) -> String {
350 format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
351 }
352
353 pub fn is_retryable(&self) -> bool {
355 matches!(self, Self::VdfFailed(_))
356 }
357
358 pub fn severity(&self) -> Severity {
360 match self {
361 Self::InvalidName { .. } => Severity::Info,
362 Self::VdfFailed(_) => Severity::Error,
363 Self::CommitmentMismatch => Severity::Error,
364 Self::AlreadyOwned { .. } => Severity::Info,
365 Self::AlreadyInProgress { .. } => Severity::Info,
366 Self::NetworkRejected { .. } => Severity::Warning,
367 Self::Internal { .. } => Severity::Error,
368 }
369 }
370
371 pub fn user_message(&self) -> String {
373 match self {
374 Self::InvalidName { name } => format!("'{}' contains invalid characters. Use only lowercase letters, digits, and hyphens.", name),
375 Self::VdfFailed(_) => "The VDF computation failed. Please try again.".to_string(),
376 Self::CommitmentMismatch => "The registration data is inconsistent. Please restart the registration process.".to_string(),
377 Self::AlreadyOwned { name } => format!("'{}' is already registered by someone else.", name),
378 Self::AlreadyInProgress { name } => format!("A registration is already in progress for '{}'.", name),
379 Self::NetworkRejected { reason } => format!("Registration was rejected: {}", reason),
380 Self::Internal { .. } => "An internal error occurred during registration.".to_string(),
381 }
382 }
383
384 pub fn details(&self) -> serde_json::Value {
386 match self {
387 Self::NetworkRejected { reason } => {
388 serde_json::json!({ "reject_reason": reason.to_string() })
389 }
390 _ => serde_json::Value::Null,
391 }
392 }
393}
394
395impl PartialEq for ResolutionError {
396 fn eq(&self, other: &Self) -> bool {
397 match (self, other) {
398 (Self::Offline, Self::Offline) => true,
399 (
400 Self::NotFound {
401 name: a_n,
402 peers_queried: a_p,
403 },
404 Self::NotFound {
405 name: b_n,
406 peers_queried: b_p,
407 },
408 ) => a_n == b_n && a_p == b_p,
409 (
410 Self::VdfVerificationFailed {
411 name: a_n,
412 count: a_c,
413 },
414 Self::VdfVerificationFailed {
415 name: b_n,
416 count: b_c,
417 },
418 ) => a_n == b_n && a_c == b_c,
419 (
420 Self::Expired {
421 name: a_n,
422 age: a_a,
423 },
424 Self::Expired {
425 name: b_n,
426 age: b_a,
427 },
428 ) => a_n == b_n && a_a == b_a,
429 (
430 Self::Timeout {
431 name: a_n,
432 elapsed_ms: a_e,
433 peers_queried: a_p,
434 },
435 Self::Timeout {
436 name: b_n,
437 elapsed_ms: b_e,
438 peers_queried: b_p,
439 },
440 ) => a_n == b_n && a_e == b_e && a_p == b_p,
441 (Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
442 a_m == b_m
443 }
444 _ => false,
445 }
446 }
447}
448impl Eq for ResolutionError {}
449
450impl PartialEq for PublishError {
451 fn eq(&self, other: &Self) -> bool {
452 match (self, other) {
453 (Self::Offline, Self::Offline) => true,
454 (Self::InvalidProof(a), Self::InvalidProof(b)) => a == b,
455 (Self::AlreadyOwned { name: a_n }, Self::AlreadyOwned { name: b_n }) => a_n == b_n,
456 (Self::AllFailed { count: a_c }, Self::AllFailed { count: b_c }) => a_c == b_c,
457 (Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
458 a_m == b_m
459 }
460 _ => false,
461 }
462 }
463}
464impl Eq for PublishError {}
465
466impl PartialEq for RegistrationError {
467 fn eq(&self, other: &Self) -> bool {
468 match (self, other) {
469 (Self::InvalidName { name: a_n }, Self::InvalidName { name: b_n }) => a_n == b_n,
470 (Self::VdfFailed(a), Self::VdfFailed(b)) => a == b,
471 (Self::CommitmentMismatch, Self::CommitmentMismatch) => true,
472 (Self::AlreadyOwned { name: a_n }, Self::AlreadyOwned { name: b_n }) => a_n == b_n,
473 (Self::AlreadyInProgress { name: a_n }, Self::AlreadyInProgress { name: b_n }) => {
474 a_n == b_n
475 }
476 (Self::NetworkRejected { reason: a_r }, Self::NetworkRejected { reason: b_r }) => {
477 a_r == b_r
478 }
479 (Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
480 a_m == b_m
481 }
482 _ => false,
483 }
484 }
485}
486impl Eq for RegistrationError {}