checkpointq_lib/
checkpoint_server.rs

1use crate::args::Network;
2use crate::client::CheckpointClient;
3use crate::errors::AppError;
4use crate::processor::DisplayableResult;
5use axum::extract::{Path, Query};
6use axum::response::Response;
7use axum::{extract::State, http::StatusCode, response::IntoResponse, Json, Router};
8use axum_macros::debug_handler;
9use serde::{Deserialize, Serialize};
10use std::{net::SocketAddr, sync::Arc};
11use tower_http::trace::TraceLayer;
12use tracing::info;
13use tracing::level_filters::LevelFilter;
14
15use tracing_subscriber::{fmt, EnvFilter};
16
17#[derive(Debug, Clone, Deserialize, Serialize)]
18pub struct QueryParams {
19    #[serde(default)]
20    pub verbose: bool,
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24pub struct ApiResponse {
25    block_root: String,
26    epoch: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    #[serde(flatten)]
29    payload: Option<DisplayableResult>,
30}
31
32impl IntoResponse for ApiResponse {
33    fn into_response(self) -> Response {
34        (StatusCode::OK, Json(self)).into_response()
35    }
36}
37
38impl IntoResponse for AppError {
39    fn into_response(self) -> Response {
40        (StatusCode::NOT_FOUND, self.to_string()).into_response()
41    }
42}
43
44#[derive(Debug)]
45pub struct CheckPointMiddleware {
46    checkpoint_client: CheckpointClient<reqwest::Client>,
47    port: u16,
48}
49
50impl CheckPointMiddleware {
51    pub fn new(checkpoint_client: CheckpointClient<reqwest::Client>, port: u16) -> Self {
52        Self {
53            checkpoint_client,
54            port,
55        }
56    }
57
58    pub async fn serve(self) {
59        fmt()
60            .with_env_filter(
61                EnvFilter::builder()
62                    .with_default_directive(LevelFilter::INFO.into())
63                    .from_env_lossy(),
64            )
65            .init();
66
67        info!("starting server on port {}", self.port);
68        let port = self.port;
69        let app = Router::new()
70            .route("/:network/finalized", axum::routing::get(finalized))
71            .layer(TraceLayer::new_for_http())
72            .with_state(Arc::new(self));
73
74        let addr = SocketAddr::from(([127, 0, 0, 1], port));
75        axum::Server::bind(&addr)
76            .serve(app.into_make_service())
77            .await
78            .unwrap();
79    }
80}
81
82#[debug_handler]
83async fn finalized(
84    State(middle_ware): State<Arc<CheckPointMiddleware>>,
85    Path(network): Path<Network>,
86    Query(query_params): Query<QueryParams>,
87) -> Result<Json<ApiResponse>, AppError> {
88    let displayable_result = middle_ware
89        .checkpoint_client
90        .fetch_finality_checkpoints(network)
91        .await?;
92
93    let block_not_found_msg = "Finalized block root not found";
94    let epoch_not_found_msg = "Epoch not found";
95    let (block_root, epoch) = match displayable_result.canonical.as_ref() {
96        Some(canonical) => {
97            let block_root = canonical
98                .keys()
99                .next()
100                .map(|s| s.to_string())
101                .unwrap_or(block_not_found_msg.to_string());
102            let epoch = canonical
103                .get(&block_root)
104                .and_then(|success_payloads| success_payloads.iter().next())
105                .map(|success_payload| success_payload.payload.data.finalized.epoch.to_string())
106                .unwrap_or(epoch_not_found_msg.to_string());
107            (block_root, epoch)
108        }
109        None => (
110            block_not_found_msg.to_string(),
111            epoch_not_found_msg.to_string(),
112        ),
113    };
114
115    let payload = if query_params.verbose {
116        Some(displayable_result)
117    } else {
118        None
119    };
120
121    let api_response = ApiResponse {
122        block_root,
123        epoch,
124        payload,
125    };
126
127    Ok(Json(api_response))
128}