Skip to content

Commit 5d19b9a

Browse files
authored
Catching panics in Core API and returning HTTP 500 (#554)
A continuation of #550 but for Core API. The implementation: use the `tower-http`'s `CatchPanic` layer to transform panics into error responses.
2 parents a0ed650 + 271502f commit 5d19b9a

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

core-rust/Cargo.lock

Lines changed: 40 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core-rust/core-api-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ chrono = { version = "0.4.23", default-features = false, features = ["std"] }
3232
hex = { version = "0.4.3", default-features = false }
3333
futures = { version = "0.3" }
3434
axum = { version = "0.6.6", features = ["http1", "json"] }
35+
tower-http = { version = "0.4.0", features = ["catch-panic"] }
3536
hyper = { version = "0.14.20", features = ["server", "http1"] }
3637
paste = { version = "1.0.12", default-features = false }

core-rust/core-api-server/src/core_api/errors.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use axum::body::BoxBody;
12
use axum::{
23
response::{IntoResponse, Response},
34
Json,
45
};
6+
use std::any::Any;
57

68
use hyper::StatusCode;
79
use radix_engine_interface::network::NetworkDefinition;
10+
use tower_http::catch_panic::ResponseForPanic;
811

912
use super::models;
1013
use models::{
@@ -69,6 +72,23 @@ impl ErrorDetails for LtsTransactionSubmitErrorDetails {
6972
}
7073
}
7174

75+
#[derive(Debug, Clone)]
76+
pub(crate) struct InternalServerErrorResponseForPanic;
77+
78+
impl ResponseForPanic for InternalServerErrorResponseForPanic {
79+
type ResponseBody = BoxBody;
80+
81+
fn response_for_panic(
82+
&mut self,
83+
_panic_payload: Box<dyn Any + Send + 'static>,
84+
) -> Response<Self::ResponseBody> {
85+
// Please note that we deliberately do *not*:
86+
// - log the panic payload (since the default panic handler already does this);
87+
// - include the panic payload in the response (it may contain sensitive details).
88+
server_error::<()>("Unexpected server error").into_response()
89+
}
90+
}
91+
7292
#[derive(Debug, Clone)]
7393
pub(crate) struct ResponseError<E: ErrorDetails> {
7494
status_code: StatusCode,

core-rust/core-api-server/src/core_api/server.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,15 @@ use axum::{
7676
use parking_lot::RwLock;
7777
use radix_engine::types::{Categorize, Decode, Encode};
7878
use radix_engine_common::network::NetworkDefinition;
79+
use tower_http::catch_panic::CatchPanicLayer;
7980
use tracing::{debug, error, info, trace, warn, Level};
8081

8182
use state_manager::jni::state_manager::ActualStateManager;
8283

8384
use super::{constants::LARGE_REQUEST_MAX_BYTES, handlers::*, not_found_error, ResponseError};
8485

8586
use crate::core_api::models::ErrorResponse;
87+
use crate::core_api::InternalServerErrorResponseForPanic;
8688
use handle_status_network_configuration as handle_provide_info_at_root_path;
8789
use state_manager::mempool::priority_mempool::PriorityMempool;
8890
use state_manager::mempool_manager::MempoolManager;
@@ -189,6 +191,7 @@ where
189191
let prefixed_router = Router::new()
190192
.nest("/core", router)
191193
.route("/", get(handle_no_core_path))
194+
.layer(CatchPanicLayer::custom(InternalServerErrorResponseForPanic))
192195
.layer(map_response(emit_error_response_event));
193196

194197
let bind_addr = bind_addr.parse().expect("Failed to parse bind address");
@@ -215,7 +218,7 @@ async fn emit_error_response_event(uri: Uri, response: Response) -> Response {
215218
let error_response = response.extensions().get::<ErrorResponse>();
216219
if let Some(error_response) = error_response {
217220
let level = resolve_level(response.status());
218-
// the `event!(level, ...)` macro does not accept non-costant levels, hence we unroll:
221+
// the `event!(level, ...)` macro does not accept non-constant levels, hence we unroll:
219222
match level {
220223
Level::TRACE => trace!(path = uri.path(), error = debug(error_response)),
221224
Level::DEBUG => debug!(path = uri.path(), error = debug(error_response)),

0 commit comments

Comments
 (0)