use super::vdf::VdfRejectReason;
use super::Severity;
use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum RecordRejectReason {
#[error("invalid signature")]
InvalidSignature,
#[error("VDF proof invalid")]
InvalidVdf,
#[error("registration has expired")]
Expired,
#[error("name already owned by a different key")]
AlreadyOwned,
#[error("insufficient VDF iterations to claim ownership")]
InsufficientIterations,
#[error("lost XOR tie-break to stronger record")]
TieBroken,
#[error("commitment mismatch")]
CommitmentMismatch,
#[error("drand_signature contains invalid hex")]
InvalidDrandHex,
#[error("public key bytes are malformed")]
InvalidPublicKey,
#[error("signature bytes are malformed")]
MalformedSignature,
}
#[derive(Error, Debug)]
pub enum ResolutionError {
#[error("Node is offline — no peers connected")]
Offline,
#[error("'{name}' not found after querying {peers_queried} peers")]
NotFound {
name: String,
peers_queried: usize,
},
#[error("'{name}' found but {count} record(s) failed VDF verification")]
VdfVerificationFailed {
name: String,
count: usize,
},
#[error("'{name}' registration has expired ({age} rounds old)")]
Expired {
name: String,
age: u64,
},
#[error("Resolution timed out after {elapsed_ms}ms ({peers_queried} peers queried)")]
Timeout {
name: String,
elapsed_ms: u64,
peers_queried: usize,
},
#[error("Internal error: {message}")]
Internal {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl ResolutionError {
pub fn code(&self) -> &'static str {
match self {
Self::Offline => "KIN-RES-001",
Self::NotFound { .. } => "KIN-RES-002",
Self::VdfVerificationFailed { .. } => "KIN-RES-003",
Self::Expired { .. } => "KIN-RES-004",
Self::Timeout { .. } => "KIN-RES-005",
Self::Internal { .. } => "KIN-RES-006",
}
}
pub fn error_type_uri(&self) -> String {
format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Offline | Self::Timeout { .. })
}
pub fn severity(&self) -> Severity {
match self {
Self::Offline => Severity::Warning,
Self::NotFound { .. } => Severity::Info,
Self::VdfVerificationFailed { .. } => Severity::Error,
Self::Expired { .. } => Severity::Info,
Self::Timeout { .. } => Severity::Warning,
Self::Internal { .. } => Severity::Error,
}
}
pub fn user_message(&self) -> String {
match self {
Self::Offline => {
"You appear to be offline. Check your internet connection.".to_string()
}
Self::NotFound { name, .. } => {
format!("'{}' is not registered on the Kinetic network.", name)
}
Self::VdfVerificationFailed { name, .. } => format!(
"'{}' has an invalid cryptographic proof. This record may have been tampered with.",
name
),
Self::Expired { name, .. } => format!(
"'{}' registration has expired. The owner needs to renew it.",
name
),
Self::Timeout { name, .. } => format!(
"The network took too long to respond for '{}'. Please try again.",
name
),
Self::Internal { .. } => {
"An internal network error occurred. Please try again.".to_string()
}
}
}
pub fn details(&self) -> serde_json::Value {
match self {
Self::NotFound { peers_queried, .. } => {
serde_json::json!({ "peers_queried": peers_queried })
}
Self::Timeout {
elapsed_ms,
peers_queried,
..
} => serde_json::json!({ "elapsed_ms": elapsed_ms, "peers_queried": peers_queried }),
Self::VdfVerificationFailed { count, .. } => {
serde_json::json!({ "failed_record_count": count })
}
Self::Expired { age, .. } => serde_json::json!({ "age_rounds": age }),
_ => serde_json::Value::Null,
}
}
}
#[derive(Error, Debug)]
pub enum PublishError {
#[error("Node is offline — cannot publish to the DHT")]
Offline,
#[error("VDF proof is invalid: {0}")]
InvalidProof(#[from] VdfRejectReason),
#[error("'{name}' is already owned by a different key")]
AlreadyOwned {
name: String,
},
#[error("All {count} DHT put operations failed")]
AllFailed {
count: usize,
},
#[error("Rejected by the network: {0}")]
Rejected(String),
#[error("Internal error: {message}")]
Internal {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl PublishError {
pub fn code(&self) -> &'static str {
match self {
Self::Offline => "KIN-PUB-001",
Self::InvalidProof(_) => "KIN-PUB-002",
Self::AlreadyOwned { .. } => "KIN-PUB-003",
Self::AllFailed { .. } => "KIN-PUB-004",
Self::Rejected(_) => "KIN-PUB-005",
Self::Internal { .. } => "KIN-PUB-006",
}
}
pub fn error_type_uri(&self) -> String {
format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Offline | Self::AllFailed { .. })
}
pub fn severity(&self) -> Severity {
match self {
Self::Offline => Severity::Warning,
Self::InvalidProof(_) => Severity::Error,
Self::AlreadyOwned { .. } => Severity::Info,
Self::AllFailed { .. } => Severity::Warning,
Self::Rejected(_) => Severity::Warning,
Self::Internal { .. } => Severity::Error,
}
}
pub fn user_message(&self) -> String {
match self {
Self::Offline => "You appear to be offline. Cannot publish to the network.".to_string(),
Self::InvalidProof(_) => "The VDF proof is invalid and was rejected.".to_string(),
Self::AlreadyOwned { name } => {
format!("'{}' is already registered under a different key.", name)
}
Self::AllFailed { .. } => {
"The network rejected all publish attempts. Please try again.".to_string()
}
Self::Rejected(reason) => format!("Publish rejected: {}", reason),
Self::Internal { .. } => "An internal error occurred during publishing.".to_string(),
}
}
pub fn details(&self) -> serde_json::Value {
match self {
Self::AllFailed { count } => serde_json::json!({ "failed_count": count }),
Self::InvalidProof(r) => serde_json::json!({ "reason": r.to_string() }),
_ => serde_json::Value::Null,
}
}
}
#[derive(Error, Debug)]
pub enum RegistrationError {
#[error("Name '{name}' contains invalid characters")]
InvalidName {
name: String,
},
#[error("VDF computation failed: {0}")]
VdfFailed(#[from] VdfRejectReason),
#[error("Commitment mismatch — reveal data does not match commitment")]
CommitmentMismatch,
#[error("'{name}' is already owned by a different key")]
AlreadyOwned {
name: String,
},
#[error("A VDF registration is already in progress for '{name}'")]
AlreadyInProgress {
name: String,
},
#[error("Registration rejected by the network: {reason}")]
NetworkRejected {
reason: RecordRejectReason,
},
#[error("Internal error: {message}")]
Internal {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl RegistrationError {
pub fn code(&self) -> &'static str {
match self {
Self::InvalidName { .. } => "KIN-REG-001",
Self::VdfFailed(_) => "KIN-REG-002",
Self::CommitmentMismatch => "KIN-REG-003",
Self::AlreadyOwned { .. } => "KIN-REG-004",
Self::AlreadyInProgress { .. } => "KIN-REG-005",
Self::NetworkRejected { .. } => "KIN-REG-006",
Self::Internal { .. } => "KIN-REG-007",
}
}
pub fn error_type_uri(&self) -> String {
format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::VdfFailed(_))
}
pub fn severity(&self) -> Severity {
match self {
Self::InvalidName { .. } => Severity::Info,
Self::VdfFailed(_) => Severity::Error,
Self::CommitmentMismatch => Severity::Error,
Self::AlreadyOwned { .. } => Severity::Info,
Self::AlreadyInProgress { .. } => Severity::Info,
Self::NetworkRejected { .. } => Severity::Warning,
Self::Internal { .. } => Severity::Error,
}
}
pub fn user_message(&self) -> String {
match self {
Self::InvalidName { name } => format!("'{}' contains invalid characters. Use only lowercase letters, digits, and hyphens.", name),
Self::VdfFailed(_) => "The VDF computation failed. Please try again.".to_string(),
Self::CommitmentMismatch => "The registration data is inconsistent. Please restart the registration process.".to_string(),
Self::AlreadyOwned { name } => format!("'{}' is already registered by someone else.", name),
Self::AlreadyInProgress { name } => format!("A registration is already in progress for '{}'.", name),
Self::NetworkRejected { reason } => format!("Registration was rejected: {}", reason),
Self::Internal { .. } => "An internal error occurred during registration.".to_string(),
}
}
pub fn details(&self) -> serde_json::Value {
match self {
Self::NetworkRejected { reason } => {
serde_json::json!({ "reject_reason": reason.to_string() })
}
_ => serde_json::Value::Null,
}
}
}
impl PartialEq for ResolutionError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Offline, Self::Offline) => true,
(
Self::NotFound {
name: a_n,
peers_queried: a_p,
},
Self::NotFound {
name: b_n,
peers_queried: b_p,
},
) => a_n == b_n && a_p == b_p,
(
Self::VdfVerificationFailed {
name: a_n,
count: a_c,
},
Self::VdfVerificationFailed {
name: b_n,
count: b_c,
},
) => a_n == b_n && a_c == b_c,
(
Self::Expired {
name: a_n,
age: a_a,
},
Self::Expired {
name: b_n,
age: b_a,
},
) => a_n == b_n && a_a == b_a,
(
Self::Timeout {
name: a_n,
elapsed_ms: a_e,
peers_queried: a_p,
},
Self::Timeout {
name: b_n,
elapsed_ms: b_e,
peers_queried: b_p,
},
) => a_n == b_n && a_e == b_e && a_p == b_p,
(Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
a_m == b_m
}
_ => false,
}
}
}
impl Eq for ResolutionError {}
impl PartialEq for PublishError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Offline, Self::Offline) => true,
(Self::InvalidProof(a), Self::InvalidProof(b)) => a == b,
(Self::AlreadyOwned { name: a_n }, Self::AlreadyOwned { name: b_n }) => a_n == b_n,
(Self::AllFailed { count: a_c }, Self::AllFailed { count: b_c }) => a_c == b_c,
(Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
a_m == b_m
}
_ => false,
}
}
}
impl Eq for PublishError {}
impl PartialEq for RegistrationError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::InvalidName { name: a_n }, Self::InvalidName { name: b_n }) => a_n == b_n,
(Self::VdfFailed(a), Self::VdfFailed(b)) => a == b,
(Self::CommitmentMismatch, Self::CommitmentMismatch) => true,
(Self::AlreadyOwned { name: a_n }, Self::AlreadyOwned { name: b_n }) => a_n == b_n,
(Self::AlreadyInProgress { name: a_n }, Self::AlreadyInProgress { name: b_n }) => {
a_n == b_n
}
(Self::NetworkRejected { reason: a_r }, Self::NetworkRejected { reason: b_r }) => {
a_r == b_r
}
(Self::Internal { message: a_m, .. }, Self::Internal { message: b_m, .. }) => {
a_m == b_m
}
_ => false,
}
}
}
impl Eq for RegistrationError {}