Skip to main content

forest/rpc/
error.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::fmt::{self, Display};
5
6use crate::rpc::eth::errors::EthErrors;
7use jsonrpsee::{
8    core::ClientError,
9    types::error::{self, ErrorCode, ErrorObjectOwned},
10};
11
12/// Trait for errors that can provide additional RPC error data
13pub trait RpcErrorData {
14    /// Return the error code to use in RPC responses
15    fn error_code(&self) -> Option<i32> {
16        None
17    }
18
19    /// Return the error message to use in RPC responses
20    fn error_message(&self) -> Option<String> {
21        None
22    }
23
24    /// Return additional data to include in the RPC error response
25    fn error_data(&self) -> Option<serde_json::Value> {
26        None
27    }
28}
29
30/// An error returned _by the remote server_, not due to e.g serialization errors,
31/// protocol errors, or the connection failing.
32#[derive(derive_more::From, derive_more::Into, derive_more::Deref, Debug, PartialEq)]
33pub struct ServerError {
34    inner: ErrorObjectOwned,
35}
36
37/// According to the [JSON-RPC 2.0 spec](https://www.jsonrpc.org/specification#response_object),
38/// the error codes from -32000 to -32099 are reserved for implementation-defined server-errors.
39/// We define them here.
40pub(crate) mod implementation_defined_errors {
41    /// This error indicates that the method is not supported by the current version of the Forest
42    /// node. Note that it's not the same as not found, as we are explicitly not supporting it,
43    /// e.g., because it's deprecated or Lotus is doing the same.
44    pub(crate) const UNSUPPORTED_METHOD: i32 = -32001;
45    /// EIP-1474 "resource unavailable": explicit call targets a block whose state cannot be
46    /// served without running an expensive migration on demand.
47    pub(crate) const EXPENSIVE_FORK_CODE: i32 = -32002;
48    /// The token authenticated, but its permissions do not allow the requested method. (Token
49    /// verification failures are rejected earlier with an HTTP `401`, not this code.)
50    pub(crate) const INSUFFICIENT_PERMISSIONS: i32 = -32003;
51}
52
53impl ServerError {
54    pub fn new(
55        code: i32,
56        message: impl Display,
57        data: impl Into<Option<serde_json::Value>>,
58    ) -> Self {
59        Self {
60            inner: ErrorObjectOwned::owned(code, message.to_string(), data.into()),
61        }
62    }
63
64    pub fn known_code(&self) -> ErrorCode {
65        self.inner.code().into()
66    }
67
68    /// We are only including this method to get the JSON schemas for our OpenRPC
69    /// machinery
70    pub fn stubbed_for_openrpc() -> Self {
71        Self::new(
72            4528,
73            "unimplemented",
74            Some(
75                "This method is stubbed as part of https://github.com/ChainSafe/forest/issues/4528"
76                    .into(),
77            ),
78        )
79    }
80
81    pub fn unsupported_method() -> Self {
82        Self::new(
83            implementation_defined_errors::UNSUPPORTED_METHOD,
84            "unsupported method",
85            Some("This method is not supported by the current version of the Forest node".into()),
86        )
87    }
88}
89
90impl<E: std::error::Error + RpcErrorData + 'static> From<E> for ServerError {
91    fn from(error: E) -> Self {
92        let code = error.error_code().unwrap_or(error::INTERNAL_ERROR_CODE);
93        let message = error.error_message().unwrap_or_else(|| error.to_string());
94        let data = error.error_data();
95
96        Self::new(code, message, data)
97    }
98}
99
100// Default implementation for anyhow::Error to handle downcasting once
101impl From<anyhow::Error> for ServerError {
102    fn from(error: anyhow::Error) -> Self {
103        // Try to downcast to known RpcErrorData implementations
104        if let Some(eth_error) = error.downcast_ref::<EthErrors>() {
105            return eth_error.clone().into();
106        }
107        if let Some(sm_error @ crate::state_manager::Error::ExpensiveFork { epoch }) =
108            error.downcast_ref::<crate::state_manager::Error>()
109        {
110            return Self::new(
111                implementation_defined_errors::EXPENSIVE_FORK_CODE,
112                sm_error.to_string(),
113                Some(serde_json::Value::from(*epoch)),
114            );
115        }
116
117        // Default fallback, not using `format!("{e:#}")` here to match Lotus error
118        Self::internal_error(error.to_string(), None)
119    }
120}
121
122impl Display for ServerError {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.write_str("JSON-RPC error:\n")?;
125        f.write_fmt(format_args!("\tcode: {}\n", self.inner.code()))?;
126        f.write_fmt(format_args!("\tmessage: {}\n", self.inner.message()))?;
127        if let Some(data) = self.inner.data() {
128            f.write_fmt(format_args!("\tdata: {data}\n"))?
129        }
130        Ok(())
131    }
132}
133
134impl std::error::Error for ServerError {}
135
136macro_rules! ctor {
137    ($($ctor:ident { $code:expr })*) => {
138        $(
139            impl ServerError {
140                pub fn $ctor(message: impl Display, data: impl Into<Option<serde_json::Value>>) -> Self {
141                    Self::new($code, message, data)
142                }
143            }
144        )*
145    }
146}
147
148ctor! {
149    parse_error { error::PARSE_ERROR_CODE }
150    internal_error { error::INTERNAL_ERROR_CODE }
151    invalid_params { error::INVALID_PARAMS_CODE }
152    method_not_found { error::METHOD_NOT_FOUND_CODE }
153}
154
155macro_rules! from2internal {
156    ($($ty:ty),* $(,)?) => {
157        $(
158            impl From<$ty> for ServerError {
159                fn from(it: $ty) -> Self {
160                    Self::internal_error(it, None)
161                }
162            }
163        )*
164    };
165}
166
167from2internal! {
168    String,
169    base64::DecodeError,
170    cid::multibase::Error,
171    crate::chain::store::Error,
172    crate::chain_sync::TipsetValidationError,
173    crate::key_management::Error,
174    crate::libp2p::ParseError,
175    crate::message_pool::Error,
176    crate::state_manager::Error,
177    fil_actors_shared::fvm_ipld_amt::Error,
178    futures::channel::oneshot::Canceled,
179    fvm_ipld_encoding::Error,
180    fvm_shared4::address::Error,
181    jsonwebtoken::errors::Error,
182    std::io::Error,
183    std::time::SystemTimeError,
184    tokio::task::JoinError,
185    fil_actors_shared::fvm_ipld_hamt::Error,
186    flume::RecvError,
187    fil_actors_shared::v12::ActorError,
188    fil_actors_shared::v13::ActorError,
189    fil_actors_shared::v14::ActorError,
190    fil_actors_shared::v15::ActorError,
191    fil_actors_shared::v16::ActorError,
192    serde_json::Error,
193    jsonrpsee::core::client::error::Error,
194}
195
196impl From<ServerError> for ClientError {
197    fn from(value: ServerError) -> Self {
198        Self::Call(value.inner)
199    }
200}
201
202impl<T> From<flume::SendError<T>> for ServerError {
203    fn from(e: flume::SendError<T>) -> Self {
204        Self::internal_error(e, None)
205    }
206}
207
208impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ServerError {
209    fn from(e: tokio::sync::mpsc::error::SendError<T>) -> Self {
210        Self::internal_error(e, None)
211    }
212}