use thiserror::Error;
#[derive(Error, Debug)]
pub enum AptosError {
#[error("RPC error: {0}")]
RpcError(String),
#[error("Resource already used: {0}")]
ResourceUsed(String),
#[error("State proof verification failed: {0}")]
StateProofFailed(String),
#[error("Event proof verification failed: {0}")]
EventProofFailed(String),
#[error("Checkpoint certification failed: {0}")]
CheckpointFailed(String),
#[error("Transaction failed: {0}")]
TransactionFailed(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Transaction confirmation timeout after {timeout_ms}ms: {tx_hash}")]
ConfirmationTimeout { tx_hash: String, timeout_ms: u64 },
#[error("Chain reorg detected at version {version}: anchor may be invalid")]
ReorgDetected { version: u64 },
#[error("Network mismatch: expected chain_id {expected}, got {actual}")]
NetworkMismatch { expected: u64, actual: u64 },
#[error(transparent)]
CoreError(#[from] csv_adapter_core::AdapterError),
}
impl AptosError {
pub fn is_transient(&self) -> bool {
match self {
AptosError::RpcError(_)
| AptosError::ConfirmationTimeout { .. }
| AptosError::TransactionFailed(_) => true,
AptosError::ResourceUsed(_)
| AptosError::StateProofFailed(_)
| AptosError::EventProofFailed(_)
| AptosError::CheckpointFailed(_)
| AptosError::SerializationError(_)
| AptosError::ReorgDetected { .. }
| AptosError::NetworkMismatch { .. }
| AptosError::CoreError(_) => false,
}
}
pub fn timeout(tx_hash: &str, timeout_ms: u64) -> Self {
AptosError::ConfirmationTimeout {
tx_hash: tx_hash.to_string(),
timeout_ms,
}
}
pub fn reorg(version: u64) -> Self {
AptosError::ReorgDetected { version }
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for AptosError {
fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
AptosError::RpcError(err.to_string())
}
}
impl From<AptosError> for csv_adapter_core::AdapterError {
fn from(err: AptosError) -> Self {
match err {
AptosError::CoreError(e) => e,
AptosError::RpcError(msg) | AptosError::TransactionFailed(msg) => {
csv_adapter_core::AdapterError::NetworkError(msg)
}
AptosError::ResourceUsed(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
AptosError::StateProofFailed(msg) | AptosError::EventProofFailed(msg) => {
csv_adapter_core::AdapterError::InclusionProofFailed(msg)
}
AptosError::CheckpointFailed(msg) => csv_adapter_core::AdapterError::NetworkError(msg),
AptosError::SerializationError(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
AptosError::ConfirmationTimeout {
tx_hash,
timeout_ms,
} => csv_adapter_core::AdapterError::NetworkError(format!(
"Timeout waiting for tx {} after {}ms",
tx_hash, timeout_ms
)),
AptosError::ReorgDetected { version } => csv_adapter_core::AdapterError::ReorgInvalid(
format!("Reorg at version {}", version),
),
aptos_err => csv_adapter_core::AdapterError::NetworkError(format!("{}", aptos_err)),
}
}
}
pub type AptosResult<T> = Result<T, AptosError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transient_errors() {
assert!(AptosError::RpcError("connection refused".to_string()).is_transient());
assert!(AptosError::ConfirmationTimeout {
tx_hash: "abc".to_string(),
timeout_ms: 30000
}
.is_transient());
assert!(AptosError::TransactionFailed("out of gas".to_string()).is_transient());
}
#[test]
fn test_non_transient_errors() {
assert!(!AptosError::ResourceUsed("seal consumed".to_string()).is_transient());
assert!(!AptosError::StateProofFailed("invalid merkle".to_string()).is_transient());
assert!(!AptosError::ReorgDetected { version: 100 }.is_transient());
}
#[test]
fn test_error_conversion() {
let aptos_err = AptosError::StateProofFailed("bad proof".to_string());
let core_err: csv_adapter_core::AdapterError = aptos_err.into();
assert!(matches!(
core_err,
csv_adapter_core::AdapterError::InclusionProofFailed(_)
));
}
}