checkpointq_lib/
checkpoint_server.rs1use 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}