arb_rpc/
error.rs

1//! RPC error types for the arb-rpc handlers.
2//!
3//! Mirrors reth's `reth-engine-api/src/error.rs` pattern: every variant
4//! maps to a JSON-RPC error code via `From<RpcError> for ErrorObjectOwned`.
5//! Internal errors are redacted before reaching the wire so we never leak
6//! storage / database / consensus details to RPC callers.
7
8use jsonrpsee::types::{
9    ErrorObject, ErrorObjectOwned,
10    error::{INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG, INVALID_PARAMS_CODE},
11};
12
13use crate::BlockProducerError;
14
15/// Result alias for [`RpcError`].
16pub type RpcResult<T> = Result<T, RpcError>;
17
18/// Errors surfaced by the Arbitrum RPC handlers.
19///
20/// Variants split user-input failures (mapped to `-32602 invalid params`)
21/// from internal failures (mapped to `-32603 internal error`, with the
22/// source redacted before serialization).
23#[derive(Debug, thiserror::Error)]
24pub enum RpcError {
25    /// Failure originating in the wider arbreth error chain.
26    #[error(transparent)]
27    Arb(#[from] arb_errors::ArbError),
28
29    /// Block production failed downstream of the RPC entrypoint.
30    #[error(transparent)]
31    BlockProducer(#[from] BlockProducerError),
32
33    /// State-provider lookup failed.
34    #[error(transparent)]
35    Provider(#[from] reth_storage_errors::provider::ProviderError),
36
37    /// A required RPC parameter was missing or malformed.
38    #[error("invalid params: {0}")]
39    InvalidParams(String),
40
41    /// A base64-encoded byte field could not be decoded.
42    #[error("base64 decode failed: {0}")]
43    Base64Decode(String),
44
45    /// A requested resource (block, header) was not found.
46    #[error("resource not found: {0}")]
47    NotFound(String),
48
49    /// Catch-all for transient internal failures that do not yet have a
50    /// dedicated variant. Source is redacted on the wire.
51    #[error("internal error: {0}")]
52    Internal(String),
53}
54
55impl RpcError {
56    /// Construct an [`RpcError::InvalidParams`] from a displayable value.
57    pub fn invalid_params(msg: impl core::fmt::Display) -> Self {
58        Self::InvalidParams(msg.to_string())
59    }
60
61    /// Construct an [`RpcError::Base64Decode`] from a displayable value.
62    pub fn base64_decode(msg: impl core::fmt::Display) -> Self {
63        Self::Base64Decode(msg.to_string())
64    }
65
66    /// Construct an [`RpcError::NotFound`] from a displayable value.
67    pub fn not_found(msg: impl core::fmt::Display) -> Self {
68        Self::NotFound(msg.to_string())
69    }
70
71    /// Construct an [`RpcError::Internal`] from a displayable value.
72    pub fn internal(msg: impl core::fmt::Display) -> Self {
73        Self::Internal(msg.to_string())
74    }
75}
76
77impl From<RpcError> for ErrorObjectOwned {
78    fn from(err: RpcError) -> Self {
79        match err {
80            RpcError::InvalidParams(msg) => {
81                ErrorObject::owned(INVALID_PARAMS_CODE, msg, None::<()>)
82            }
83            RpcError::Base64Decode(msg) => ErrorObject::owned(
84                INVALID_PARAMS_CODE,
85                format!("base64 decode: {msg}"),
86                None::<()>,
87            ),
88            RpcError::NotFound(msg) => ErrorObject::owned(INVALID_PARAMS_CODE, msg, None::<()>),
89            RpcError::Arb(_)
90            | RpcError::BlockProducer(_)
91            | RpcError::Provider(_)
92            | RpcError::Internal(_) => {
93                tracing::warn!(target: "arb::rpc", error = %err, "RPC internal error");
94                ErrorObject::owned(INTERNAL_ERROR_CODE, INTERNAL_ERROR_MSG, None::<()>)
95            }
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use jsonrpsee::types::error::{INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE};
103
104    use super::*;
105
106    fn into_obj(err: RpcError) -> ErrorObjectOwned {
107        err.into()
108    }
109
110    #[test]
111    fn invalid_params_maps_to_invalid_params_code() {
112        let obj = into_obj(RpcError::invalid_params("bad msg"));
113        assert_eq!(obj.code(), INVALID_PARAMS_CODE);
114        assert_eq!(obj.message(), "bad msg");
115    }
116
117    #[test]
118    fn base64_decode_maps_to_invalid_params_code() {
119        let obj = into_obj(RpcError::base64_decode("bad char"));
120        assert_eq!(obj.code(), INVALID_PARAMS_CODE);
121        assert!(obj.message().contains("base64 decode"));
122    }
123
124    #[test]
125    fn not_found_maps_to_invalid_params_code() {
126        let obj = into_obj(RpcError::not_found("block 7"));
127        assert_eq!(obj.code(), INVALID_PARAMS_CODE);
128        assert_eq!(obj.message(), "block 7");
129    }
130
131    #[test]
132    fn internal_redacts_source() {
133        let obj = into_obj(RpcError::internal("db connection lost"));
134        assert_eq!(obj.code(), INTERNAL_ERROR_CODE);
135        assert!(!obj.message().contains("db connection lost"));
136    }
137
138    #[test]
139    fn provider_redacts_source() {
140        use reth_storage_errors::provider::ProviderError;
141        let obj = into_obj(RpcError::Provider(ProviderError::BestBlockNotFound));
142        assert_eq!(obj.code(), INTERNAL_ERROR_CODE);
143        assert!(!obj.message().contains("Best block"));
144    }
145}