reth_rpc_engine_api/
error.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use alloy_primitives::{B256, U256};
use jsonrpsee_types::error::{
    INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG,
};
use reth_beacon_consensus::BeaconForkChoiceUpdateError;
use reth_engine_primitives::BeaconOnNewPayloadError;
use reth_payload_builder_primitives::PayloadBuilderError;
use reth_payload_primitives::EngineObjectValidationError;
use thiserror::Error;

/// The Engine API result type
pub type EngineApiResult<Ok> = Result<Ok, EngineApiError>;

/// Invalid payload attributes code.
pub const INVALID_PAYLOAD_ATTRIBUTES: i32 = -38003;
/// Payload unsupported fork code.
pub const UNSUPPORTED_FORK_CODE: i32 = -38005;
/// Payload unknown error code.
pub const UNKNOWN_PAYLOAD_CODE: i32 = -38001;
/// Request too large error code.
pub const REQUEST_TOO_LARGE_CODE: i32 = -38004;

/// Error message for the request too large error.
const REQUEST_TOO_LARGE_MESSAGE: &str = "Too large request";

/// Error message for the request too large error.
const INVALID_PAYLOAD_ATTRIBUTES_MSG: &str = "Invalid payload attributes";

/// Error returned by [`EngineApi`][crate::EngineApi]
///
/// Note: This is a high-fidelity error type which can be converted to an RPC error that adheres to
/// the [Engine API spec](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors).
#[derive(Error, Debug)]
pub enum EngineApiError {
    // **IMPORTANT**: keep error messages in sync with the Engine API spec linked above.
    /// Payload does not exist / is not available.
    #[error("Unknown payload")]
    UnknownPayload,
    /// The payload body request length is too large.
    #[error("requested count too large: {len}")]
    PayloadRequestTooLarge {
        /// The length that was requested.
        len: u64,
    },
    /// Too many requested versioned hashes for blobs request
    #[error("requested blob count too large: {len}")]
    BlobRequestTooLarge {
        /// The length that was requested.
        len: usize,
    },
    /// Thrown if `engine_getPayloadBodiesByRangeV1` contains an invalid range
    #[error("invalid start ({start}) or count ({count})")]
    InvalidBodiesRange {
        /// Start of the range
        start: u64,
        /// Requested number of items
        count: u64,
    },
    /// Terminal total difficulty mismatch during transition configuration exchange.
    #[error(
        "invalid transition terminal total difficulty: \
         execution: {execution}, consensus: {consensus}"
    )]
    TerminalTD {
        /// Execution terminal total difficulty value.
        execution: U256,
        /// Consensus terminal total difficulty value.
        consensus: U256,
    },
    /// Terminal block hash mismatch during transition configuration exchange.
    #[error(
        "invalid transition terminal block hash: \
         execution: {execution:?}, consensus: {consensus}"
    )]
    TerminalBlockHash {
        /// Execution terminal block hash. `None` if block number is not found in the database.
        execution: Option<B256>,
        /// Consensus terminal block hash.
        consensus: B256,
    },
    /// An error occurred while processing the fork choice update in the beacon consensus engine.
    #[error(transparent)]
    ForkChoiceUpdate(#[from] BeaconForkChoiceUpdateError),
    /// An error occurred while processing a new payload in the beacon consensus engine.
    #[error(transparent)]
    NewPayload(#[from] BeaconOnNewPayloadError),
    /// Encountered an internal error.
    #[error(transparent)]
    Internal(#[from] Box<dyn core::error::Error + Send + Sync>),
    /// Fetching the payload failed
    #[error(transparent)]
    GetPayloadError(#[from] PayloadBuilderError),
    /// The payload or attributes are known to be malformed before processing.
    #[error(transparent)]
    EngineObjectValidationError(#[from] EngineObjectValidationError),
    /// Any other rpc error
    #[error("{0}")]
    Other(jsonrpsee_types::ErrorObject<'static>),
}

impl EngineApiError {
    /// Crates a new [`EngineApiError::Other`] variant.
    pub const fn other(err: jsonrpsee_types::ErrorObject<'static>) -> Self {
        Self::Other(err)
    }
}

/// Helper type to represent the `error` field in the error response:
/// <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors>
#[derive(serde::Serialize)]
struct ErrorData {
    err: String,
}

impl ErrorData {
    #[inline]
    fn new(err: impl std::fmt::Display) -> Self {
        Self { err: err.to_string() }
    }
}

impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
    fn from(error: EngineApiError) -> Self {
        match error {
            EngineApiError::InvalidBodiesRange { .. } |
            EngineApiError::EngineObjectValidationError(
                EngineObjectValidationError::Payload(_) |
                EngineObjectValidationError::InvalidParams(_),
            ) => {
                // Note: the data field is not required by the spec, but is also included by other
                // clients
                jsonrpsee_types::error::ErrorObject::owned(
                    INVALID_PARAMS_CODE,
                    INVALID_PARAMS_MSG,
                    Some(ErrorData::new(error)),
                )
            }
            EngineApiError::EngineObjectValidationError(
                EngineObjectValidationError::PayloadAttributes(_),
            ) => {
                // Note: the data field is not required by the spec, but is also included by other
                // clients
                jsonrpsee_types::error::ErrorObject::owned(
                    INVALID_PAYLOAD_ATTRIBUTES,
                    INVALID_PAYLOAD_ATTRIBUTES_MSG,
                    Some(ErrorData::new(error)),
                )
            }
            EngineApiError::UnknownPayload => jsonrpsee_types::error::ErrorObject::owned(
                UNKNOWN_PAYLOAD_CODE,
                error.to_string(),
                None::<()>,
            ),
            EngineApiError::PayloadRequestTooLarge { .. } |
            EngineApiError::BlobRequestTooLarge { .. } => {
                jsonrpsee_types::error::ErrorObject::owned(
                    REQUEST_TOO_LARGE_CODE,
                    REQUEST_TOO_LARGE_MESSAGE,
                    Some(ErrorData::new(error)),
                )
            }
            EngineApiError::EngineObjectValidationError(
                EngineObjectValidationError::UnsupportedFork,
            ) => jsonrpsee_types::error::ErrorObject::owned(
                UNSUPPORTED_FORK_CODE,
                error.to_string(),
                None::<()>,
            ),
            // Error responses from the consensus engine
            EngineApiError::ForkChoiceUpdate(ref err) => match err {
                BeaconForkChoiceUpdateError::ForkchoiceUpdateError(err) => (*err).into(),
                BeaconForkChoiceUpdateError::EngineUnavailable |
                BeaconForkChoiceUpdateError::Internal(_) => {
                    jsonrpsee_types::error::ErrorObject::owned(
                        INTERNAL_ERROR_CODE,
                        SERVER_ERROR_MSG,
                        Some(ErrorData::new(error)),
                    )
                }
            },
            // Any other server error
            EngineApiError::TerminalTD { .. } |
            EngineApiError::TerminalBlockHash { .. } |
            EngineApiError::NewPayload(_) |
            EngineApiError::Internal(_) |
            EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned(
                INTERNAL_ERROR_CODE,
                SERVER_ERROR_MSG,
                Some(ErrorData::new(error)),
            ),
            EngineApiError::Other(err) => err,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloy_rpc_types_engine::ForkchoiceUpdateError;

    #[track_caller]
    fn ensure_engine_rpc_error(
        code: i32,
        message: &str,
        err: impl Into<jsonrpsee_types::error::ErrorObject<'static>>,
    ) {
        let err = err.into();
        assert_eq!(err.code(), code);
        assert_eq!(err.message(), message);
    }

    // Tests that engine errors are formatted correctly according to the engine API spec
    // <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors>
    #[test]
    fn engine_error_rpc_error_test() {
        ensure_engine_rpc_error(
            UNSUPPORTED_FORK_CODE,
            "Unsupported fork",
            EngineApiError::EngineObjectValidationError(
                EngineObjectValidationError::UnsupportedFork,
            ),
        );

        ensure_engine_rpc_error(
            REQUEST_TOO_LARGE_CODE,
            "Too large request",
            EngineApiError::PayloadRequestTooLarge { len: 0 },
        );

        ensure_engine_rpc_error(
            -38002,
            "Invalid forkchoice state",
            EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
                ForkchoiceUpdateError::InvalidState,
            )),
        );

        ensure_engine_rpc_error(
            -38003,
            "Invalid payload attributes",
            EngineApiError::ForkChoiceUpdate(BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
                ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes,
            )),
        );

        ensure_engine_rpc_error(
            UNKNOWN_PAYLOAD_CODE,
            "Unknown payload",
            EngineApiError::UnknownPayload,
        );
    }
}