Skip to main content

csv_adapter_aptos/
error.rs

1//! Aptos adapter error types
2//!
3//! This module provides a comprehensive error taxonomy for the Aptos adapter,
4//! with chain-specific error variants and recovery guidance.
5
6use thiserror::Error;
7
8/// Comprehensive error types for the Aptos adapter.
9///
10/// Each variant includes context for debugging and recovery guidance.
11#[derive(Error, Debug)]
12pub enum AptosError {
13    /// Error during RPC communication with Aptos node.
14    /// Recovery: Retry with backoff, switch to fallback RPC endpoint.
15    #[error("RPC error: {0}")]
16    RpcError(String),
17
18    /// Seal resource has already been consumed.
19    /// Recovery: This is a fatal error for seal replay attempts. Check seal state.
20    #[error("Resource already used: {0}")]
21    ResourceUsed(String),
22
23    /// State proof verification failed (Merkle proof against accumulator root).
24    /// Recovery: Re-fetch proof from different RPC endpoint, check for reorg.
25    #[error("State proof verification failed: {0}")]
26    StateProofFailed(String),
27
28    /// Event proof verification failed (event emission verification).
29    /// Recovery: Re-verify transaction, check event index and data.
30    #[error("Event proof verification failed: {0}")]
31    EventProofFailed(String),
32
33    /// Checkpoint certification verification failed.
34    /// Recovery: Check validator signatures, verify epoch boundaries.
35    #[error("Checkpoint certification failed: {0}")]
36    CheckpointFailed(String),
37
38    /// Transaction submission or execution failed.
39    /// Recovery: Check transaction simulation error, adjust gas parameters.
40    #[error("Transaction failed: {0}")]
41    TransactionFailed(String),
42
43    /// Error during serialization/deserialization.
44    /// Recovery: This is a programming error. Check data format compatibility.
45    #[error("Serialization error: {0}")]
46    SerializationError(String),
47
48    /// Timeout while waiting for transaction confirmation.
49    /// Recovery: Resubmit transaction with higher gas, check mempool status.
50    #[error("Transaction confirmation timeout after {timeout_ms}ms: {tx_hash}")]
51    ConfirmationTimeout { tx_hash: String, timeout_ms: u64 },
52
53    /// Chain reorg detected affecting anchor validity.
54    /// Recovery: Re-publish commitment at new chain tip.
55    #[error("Chain reorg detected at version {version}: anchor may be invalid")]
56    ReorgDetected { version: u64 },
57
58    /// Network mismatch (e.g., mainnet seal on testnet).
59    /// Recovery: Ensure network configuration matches chain ID.
60    #[error("Network mismatch: expected chain_id {expected}, got {actual}")]
61    NetworkMismatch { expected: u64, actual: u64 },
62
63    /// Core adapter error from csv-adapter-core.
64    #[error(transparent)]
65    CoreError(#[from] csv_adapter_core::AdapterError),
66}
67
68impl AptosError {
69    /// Returns true if this error is potentially transient and should be retried.
70    pub fn is_transient(&self) -> bool {
71        match self {
72            AptosError::RpcError(_)
73            | AptosError::ConfirmationTimeout { .. }
74            | AptosError::TransactionFailed(_) => true,
75            AptosError::ResourceUsed(_)
76            | AptosError::StateProofFailed(_)
77            | AptosError::EventProofFailed(_)
78            | AptosError::CheckpointFailed(_)
79            | AptosError::SerializationError(_)
80            | AptosError::ReorgDetected { .. }
81            | AptosError::NetworkMismatch { .. }
82            | AptosError::CoreError(_) => false,
83        }
84    }
85
86    /// Construct an error for transaction timeout
87    pub fn timeout(tx_hash: &str, timeout_ms: u64) -> Self {
88        AptosError::ConfirmationTimeout {
89            tx_hash: tx_hash.to_string(),
90            timeout_ms,
91        }
92    }
93
94    /// Construct an error for chain reorg
95    pub fn reorg(version: u64) -> Self {
96        AptosError::ReorgDetected { version }
97    }
98}
99
100impl From<Box<dyn std::error::Error + Send + Sync>> for AptosError {
101    fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
102        AptosError::RpcError(err.to_string())
103    }
104}
105
106impl From<AptosError> for csv_adapter_core::AdapterError {
107    fn from(err: AptosError) -> Self {
108        match err {
109            AptosError::CoreError(e) => e,
110            AptosError::RpcError(msg) | AptosError::TransactionFailed(msg) => {
111                csv_adapter_core::AdapterError::NetworkError(msg)
112            }
113            AptosError::ResourceUsed(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
114            AptosError::StateProofFailed(msg) | AptosError::EventProofFailed(msg) => {
115                csv_adapter_core::AdapterError::InclusionProofFailed(msg)
116            }
117            AptosError::CheckpointFailed(msg) => csv_adapter_core::AdapterError::NetworkError(msg),
118            AptosError::SerializationError(msg) => csv_adapter_core::AdapterError::InvalidSeal(msg),
119            AptosError::ConfirmationTimeout {
120                tx_hash,
121                timeout_ms,
122            } => csv_adapter_core::AdapterError::NetworkError(format!(
123                "Timeout waiting for tx {} after {}ms",
124                tx_hash, timeout_ms
125            )),
126            AptosError::ReorgDetected { version } => csv_adapter_core::AdapterError::ReorgInvalid(
127                format!("Reorg at version {}", version),
128            ),
129            aptos_err => csv_adapter_core::AdapterError::NetworkError(format!("{}", aptos_err)),
130        }
131    }
132}
133
134pub type AptosResult<T> = Result<T, AptosError>;
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_transient_errors() {
142        assert!(AptosError::RpcError("connection refused".to_string()).is_transient());
143        assert!(AptosError::ConfirmationTimeout {
144            tx_hash: "abc".to_string(),
145            timeout_ms: 30000
146        }
147        .is_transient());
148        assert!(AptosError::TransactionFailed("out of gas".to_string()).is_transient());
149    }
150
151    #[test]
152    fn test_non_transient_errors() {
153        assert!(!AptosError::ResourceUsed("seal consumed".to_string()).is_transient());
154        assert!(!AptosError::StateProofFailed("invalid merkle".to_string()).is_transient());
155        assert!(!AptosError::ReorgDetected { version: 100 }.is_transient());
156    }
157
158    #[test]
159    fn test_error_conversion() {
160        let aptos_err = AptosError::StateProofFailed("bad proof".to_string());
161        let core_err: csv_adapter_core::AdapterError = aptos_err.into();
162        assert!(matches!(
163            core_err,
164            csv_adapter_core::AdapterError::InclusionProofFailed(_)
165        ));
166    }
167}