forest/rpc/methods/eth/
errors.rs1use crate::rpc::error::RpcErrorData;
5use crate::shim::clock::ChainEpoch;
6use crate::shim::error::ExitCode;
7use crate::utils::encoding::hex;
8use serde::Serialize;
9use std::fmt::Debug;
10use thiserror::Error;
11
12pub const EXECUTION_REVERTED_CODE: i32 = 3;
15pub const LIMIT_EXCEEDED_CODE: i32 = -32005;
18pub const NULL_ROUND_CODE: i32 = 12;
20
21#[derive(Clone, Debug, Error, Serialize)]
22pub enum EthErrors {
23 #[error("{message}")]
24 ExecutionReverted { message: String, data: String },
25 #[error("{message}")]
26 BlockRangeExceeded {
27 max: i64,
28 given: i64,
29 message: String,
30 },
31 #[error("events for the requested block are not yet available")]
32 EventsNotYetAvailable,
33 #[error("requested epoch was a null round ({epoch})")]
34 NullRound { epoch: ChainEpoch },
35}
36
37impl EthErrors {
38 pub fn execution_reverted(exit_code: ExitCode, reason: &str, error: &str, data: &[u8]) -> Self {
40 let revert_reason = if reason.is_empty() {
41 String::new()
42 } else {
43 format!(", revert reason=[{reason}]")
44 };
45
46 Self::ExecutionReverted {
47 message: format!(
48 "message execution failed (exit=[{exit_code}]{revert_reason}, vm error=[{error}])"
49 ),
50 data: hex::encode_prefixed(data),
51 }
52 }
53
54 #[must_use]
57 pub fn with_message_prefix(mut self, prefix: &str) -> Self {
58 match &mut self {
59 Self::ExecutionReverted { message, .. } | Self::BlockRangeExceeded { message, .. } => {
60 *message = format!("{prefix}: {message}");
61 }
62 Self::EventsNotYetAvailable | Self::NullRound { .. } => {}
63 }
64 self
65 }
66
67 pub fn limit_exceeded(max_block_range: i64, given: i64) -> Self {
68 Self::BlockRangeExceeded {
69 max: max_block_range,
70 given,
71 message: format!("block range exceeds maximum of {max_block_range} (got {given})"),
72 }
73 }
74
75 pub fn null_round(epoch: ChainEpoch) -> Self {
77 Self::NullRound { epoch }
78 }
79}
80
81impl RpcErrorData for EthErrors {
82 fn error_code(&self) -> Option<i32> {
83 match self {
84 EthErrors::ExecutionReverted { .. } => Some(EXECUTION_REVERTED_CODE),
85 EthErrors::BlockRangeExceeded { .. } => Some(LIMIT_EXCEEDED_CODE),
86 EthErrors::EventsNotYetAvailable => None,
87 EthErrors::NullRound { .. } => Some(NULL_ROUND_CODE),
88 }
89 }
90
91 fn error_message(&self) -> Option<String> {
92 match self {
93 EthErrors::ExecutionReverted { message, .. } => Some(message.clone()),
94 EthErrors::BlockRangeExceeded { message, .. } => Some(message.clone()),
95 EthErrors::EventsNotYetAvailable => Some(self.to_string()),
96 EthErrors::NullRound { .. } => Some(self.to_string()),
97 }
98 }
99
100 fn error_data(&self) -> Option<serde_json::Value> {
101 match self {
102 EthErrors::ExecutionReverted { data, .. } => {
103 Some(serde_json::Value::String(data.clone()))
104 }
105 EthErrors::BlockRangeExceeded { .. } => None,
106 EthErrors::EventsNotYetAvailable => None,
107 EthErrors::NullRound { epoch } => Some(serde_json::Value::from(*epoch)),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::rpc::error::ServerError;
117
118 #[test]
119 fn test_block_range_exceeded_converts_to_server_error_with_correct_code() {
120 let err = EthErrors::limit_exceeded(100, 500);
121 let server_err: ServerError = err.into();
122
123 assert_eq!(server_err.code(), LIMIT_EXCEEDED_CODE);
124 assert_eq!(
125 server_err.message(),
126 "block range exceeds maximum of 100 (got 500)"
127 );
128 }
129
130 #[test]
131 fn test_block_range_exceeded_via_anyhow_preserves_code() {
132 let eth_err = EthErrors::limit_exceeded(2880, 5000);
133 let anyhow_err: anyhow::Error = eth_err.into();
134 let server_err: ServerError = anyhow_err.into();
135
136 assert_eq!(server_err.code(), LIMIT_EXCEEDED_CODE);
137 assert_eq!(
138 server_err.message(),
139 "block range exceeds maximum of 2880 (got 5000)"
140 );
141 }
142
143 #[test]
144 fn test_null_round_converts_to_server_error_matching_lotus() {
145 let err = EthErrors::null_round(3847253);
146 let server_err: ServerError = err.into();
147
148 assert_eq!(server_err.code(), NULL_ROUND_CODE);
150 assert_eq!(
151 server_err.message(),
152 "requested epoch was a null round (3847253)"
153 );
154 assert_eq!(
155 server_err.data().map(|d| d.to_string()),
156 Some("3847253".to_string())
157 );
158 }
159
160 #[test]
161 fn test_null_round_via_anyhow_preserves_code() {
162 let anyhow_err: anyhow::Error = EthErrors::null_round(42).into();
163 let server_err: ServerError = anyhow_err.into();
164
165 assert_eq!(server_err.code(), NULL_ROUND_CODE);
166 assert_eq!(
167 server_err.message(),
168 "requested epoch was a null round (42)"
169 );
170 }
171
172 #[test]
173 fn test_events_not_yet_available_converts_to_server_error() {
174 let err = EthErrors::EventsNotYetAvailable;
175 let server_err: ServerError = err.into();
176
177 assert_eq!(
179 server_err.message(),
180 "events for the requested block are not yet available"
181 );
182 }
183
184 #[test]
185 fn test_with_message_prefix_prepends_and_preserves_code_and_data() {
186 let err = EthErrors::execution_reverted(
187 ExitCode::from(33u32),
188 "boom",
189 "backtrace",
190 &[0xde, 0xad],
191 )
192 .with_message_prefix("gas search failed");
193 let server_err: ServerError = err.into();
194
195 assert_eq!(server_err.code(), EXECUTION_REVERTED_CODE);
196 assert_eq!(
197 server_err.message(),
198 "gas search failed: message execution failed (exit=[33], revert reason=[boom], vm error=[backtrace])"
199 );
200 assert_eq!(
201 server_err.data().map(|d| d.to_string()),
202 Some("\"0xdead\"".to_string())
203 );
204 }
205
206 #[test]
207 fn test_with_message_prefix_is_a_noop_for_messageless_variants() {
208 let err = EthErrors::null_round(7).with_message_prefix("ignored");
209 assert_eq!(err.to_string(), "requested epoch was a null round (7)");
210 }
211}