reth_optimism_txpool/supervisor/
errors.rs
1use core::error;
20use op_alloy_consensus::interop::SafetyLevel;
21
22const UNKNOWN_CHAIN_MSG: &str = "unknown chain: ";
25const MINIMUM_SAFETY_MSG: &str = "does not meet the minimum safety";
28
29#[derive(thiserror::Error, Debug)]
31pub enum InvalidInboxEntry {
32 #[error("message does not meet min safety level, got: {got}, expected: {expected}")]
34 MinimumSafety {
35 got: SafetyLevel,
37 expected: SafetyLevel,
39 },
40 #[error("unsupported chain id: {0}")]
42 UnknownChain(u64),
43}
44
45impl InvalidInboxEntry {
46 pub fn msg_safety_level(&self) -> Option<SafetyLevel> {
49 match self {
50 Self::MinimumSafety { got, .. } => Some(*got),
51 Self::UnknownChain(_) => None,
52 }
53 }
54
55 pub fn is_msg_invalid(&self) -> bool {
57 matches!(self, Self::MinimumSafety { got: SafetyLevel::Invalid, .. })
58 }
59
60 pub fn is_msg_unsafe(&self) -> bool {
62 matches!(self, Self::MinimumSafety { got: SafetyLevel::Unsafe, .. })
63 }
64
65 pub fn is_msg_cross_unsafe(&self) -> bool {
67 matches!(self, Self::MinimumSafety { got: SafetyLevel::CrossUnsafe, .. })
68 }
69
70 pub fn is_msg_local_safe(&self) -> bool {
72 matches!(self, Self::MinimumSafety { got: SafetyLevel::LocalSafe, .. })
73 }
74
75 pub fn is_msg_safe(&self) -> bool {
77 matches!(self, Self::MinimumSafety { got: SafetyLevel::Safe, .. })
78 }
79
80 pub fn is_msg_at_least_cross_unsafe(&self) -> bool {
82 self.is_msg_cross_unsafe() || self.is_msg_local_safe() || self.is_msg_safe()
83 }
84
85 pub fn parse_err_msg(err_msg: &str) -> Option<Self> {
88 if err_msg.contains(UNKNOWN_CHAIN_MSG) {
91 if let Ok(chain_id) =
92 err_msg.split(' ').next_back().expect("message contains chain id").parse::<u64>()
93 {
94 return Some(Self::UnknownChain(chain_id))
95 }
96 } else if err_msg.contains(MINIMUM_SAFETY_MSG) {
100 let message_safety = if err_msg.contains("safety level: safe") {
101 SafetyLevel::Safe
102 } else if err_msg.contains("safety level: local-safe") {
103 SafetyLevel::LocalSafe
104 } else if err_msg.contains("safety level: cross-unsafe") {
105 SafetyLevel::CrossUnsafe
106 } else if err_msg.contains("safety level: unsafe") {
107 SafetyLevel::Unsafe
108 } else if err_msg.contains("safety level: invalid") {
109 SafetyLevel::Invalid
110 } else {
111 return None
113 };
114 let expected_safety = if err_msg.contains("safety finalized") {
115 SafetyLevel::Finalized
116 } else if err_msg.contains("safety safe") {
117 SafetyLevel::Safe
118 } else if err_msg.contains("safety local-safe") {
119 SafetyLevel::LocalSafe
120 } else if err_msg.contains("safety cross-unsafe") {
121 SafetyLevel::CrossUnsafe
122 } else if err_msg.contains("safety unsafe") {
123 SafetyLevel::Unsafe
124 } else {
125 return None
127 };
128
129 return Some(Self::MinimumSafety { expected: expected_safety, got: message_safety })
130 }
131
132 None
133 }
134}
135
136#[derive(thiserror::Error, Debug)]
138pub enum InteropTxValidatorError {
139 #[error(transparent)]
141 InvalidInboxEntry(#[from] InvalidInboxEntry),
142
143 #[error("supervisor rpc client failure: {0}")]
145 RpcClientError(Box<dyn error::Error + Send + Sync>),
146
147 #[error("message validation timed out, timeout: {0} secs")]
149 ValidationTimeout(u64),
150
151 #[error("unexpected error from supervisor: {0}")]
153 SupervisorServerError(Box<dyn error::Error + Send + Sync>),
154}
155
156impl InteropTxValidatorError {
157 pub fn client<E>(err: alloy_json_rpc::RpcError<E>) -> Self
159 where
160 E: error::Error + Send + Sync + 'static,
161 {
162 let err_msg = err.to_string();
163
164 if let Some(invalid_entry) = InvalidInboxEntry::parse_err_msg(&err_msg) {
166 return Self::InvalidInboxEntry(invalid_entry);
167 }
168
169 Self::RpcClientError(Box::new(err))
170 }
171
172 pub fn server_unexpected(err: impl error::Error + Send + Sync + 'static) -> Self {
174 Self::SupervisorServerError(Box::new(err))
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use alloy_json_rpc::{ErrorPayload, RpcError};
182
183 const MIN_SAFETY_CROSS_UNSAFE_ERROR: &str = "message {0x4200000000000000000000000000000000000023 4 1 1728507701 901} (safety level: unsafe) does not meet the minimum safety cross-unsafe";
184 const MIN_SAFETY_UNSAFE_ERROR: &str = "message {0x4200000000000000000000000000000000000023 1091637521 4369 0 901} (safety level: invalid) does not meet the minimum safety unsafe";
185 const MIN_SAFETY_FINALIZED_ERROR: &str = "message {0x4200000000000000000000000000000000000023 1091600001 215 1170 901} (safety level: safe) does not meet the minimum safety finalized";
186 const INVALID_CHAIN: &str =
187 "failed to check message: failed to check log: unknown chain: 14417";
188 const RANDOM_ERROR: &str = "gibberish error";
189
190 #[test]
191 fn test_op_supervisor_error_parsing() {
192 assert!(matches!(
193 InvalidInboxEntry::parse_err_msg(MIN_SAFETY_CROSS_UNSAFE_ERROR).unwrap(),
194 InvalidInboxEntry::MinimumSafety {
195 expected: SafetyLevel::CrossUnsafe,
196 got: SafetyLevel::Unsafe
197 }
198 ));
199
200 assert!(matches!(
201 InvalidInboxEntry::parse_err_msg(MIN_SAFETY_UNSAFE_ERROR).unwrap(),
202 InvalidInboxEntry::MinimumSafety {
203 expected: SafetyLevel::Unsafe,
204 got: SafetyLevel::Invalid
205 }
206 ));
207
208 assert!(matches!(
209 InvalidInboxEntry::parse_err_msg(MIN_SAFETY_FINALIZED_ERROR).unwrap(),
210 InvalidInboxEntry::MinimumSafety {
211 expected: SafetyLevel::Finalized,
212 got: SafetyLevel::Safe,
213 }
214 ));
215
216 assert!(matches!(
217 InvalidInboxEntry::parse_err_msg(INVALID_CHAIN).unwrap(),
218 InvalidInboxEntry::UnknownChain(14417)
219 ));
220
221 assert!(InvalidInboxEntry::parse_err_msg(RANDOM_ERROR).is_none());
222 }
223
224 #[test]
225 fn test_client_error_parsing() {
226 let err =
227 ErrorPayload { code: 0, message: MIN_SAFETY_CROSS_UNSAFE_ERROR.into(), data: None };
228 let rpc_err = RpcError::<InvalidInboxEntry>::ErrorResp(err);
229 let error = InteropTxValidatorError::client(rpc_err);
230
231 assert!(matches!(
232 error,
233 InteropTxValidatorError::InvalidInboxEntry(InvalidInboxEntry::MinimumSafety {
234 expected: SafetyLevel::CrossUnsafe,
235 got: SafetyLevel::Unsafe
236 })
237 ));
238
239 let err = ErrorPayload { code: 0, message: "unknown error".into(), data: None };
241 let rpc_err = RpcError::<InvalidInboxEntry>::ErrorResp(err);
242 let error = InteropTxValidatorError::client(rpc_err);
243 assert!(matches!(error, InteropTxValidatorError::RpcClientError(_)));
244 }
245}