checkpointq_lib/
client.rs1use crate::errors::AppError;
2use crate::processor::{process_to_displayable_format, DisplayableResult};
3use async_trait::async_trait;
4use futures::future::join_all;
5
6use reqwest::Response;
7
8use crate::args::Network;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::fmt;
12use std::fmt::{Debug, Formatter};
13
14#[derive(Debug)]
15pub struct ResponsePayloadWithEndpointInfo {
16 pub payload: Result<SuccessEndpointPayload, AppError>,
17 pub endpoint: String,
18}
19
20#[derive(Serialize, Deserialize, Debug, Clone)]
21pub struct SuccessEndpointPayload {
22 pub data: Data,
23}
24
25#[derive(Serialize, Deserialize, Debug, Clone)]
26pub struct BlockInfo {
27 pub epoch: String,
28 pub root: String,
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone)]
32pub struct Data {
33 pub finalized: BlockInfo,
34 pub current_justified: BlockInfo,
35 pub previous_justified: BlockInfo,
36}
37
38#[derive(Debug, Clone)]
39pub struct CheckpointClient<C: HttpClient> {
40 client: C,
41 endpoints_config: EndpointsConfig,
42 state_id: StateId,
43}
44
45#[derive(Debug, Clone)]
46pub enum StateId {
47 Finalized,
48 Slot(u128), }
50
51impl fmt::Display for StateId {
52 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53 match self {
54 StateId::Finalized => write!(f, "finalized"),
55 StateId::Slot(slot) => write!(f, "{:?}", slot.to_string()),
56 }
57 }
58}
59
60#[derive(Debug, Deserialize, Clone)]
61pub struct EndpointsConfig {
62 pub endpoints: HashMap<String, Vec<String>>,
63}
64
65#[async_trait]
66pub trait HttpClient {
67 async fn send_request(&self, path: String) -> Result<Response, AppError>;
68}
69
70#[async_trait]
71impl HttpClient for reqwest::Client {
72 async fn send_request(&self, path: String) -> Result<Response, AppError> {
73 self.get(path)
74 .send()
75 .await
76 .map_err(|e| AppError::EndpointResponseError(e.to_string()))
77 }
78}
79
80impl<C: HttpClient> CheckpointClient<C> {
81 pub fn new(client: C, state_id: StateId, endpoints: EndpointsConfig) -> Self {
82 Self {
83 client,
84 endpoints_config: endpoints,
85 state_id,
86 }
87 }
88 pub async fn fetch_finality_checkpoints(
89 &self,
90 network: Network,
91 ) -> Result<DisplayableResult, AppError> {
92 let endpoints_config = &self.endpoints_config;
93 let endpoints: &Vec<String> = endpoints_config.endpoints.get(&network.to_string().to_lowercase()).ok_or(
94 AppError::EndpointsNotFound(format!(r#"Endpoint not found for {network} network. Ensure it is present in the config file and the network name is specified in lowercase."#)),
95 )?;
96
97 let results = join_all(endpoints.iter().map(|endpoint| async {
98 let raw_response = async {
99 let path = format!(
100 "{}/eth/v1/beacon/states/{}/finality_checkpoints",
101 endpoint.clone(),
102 self.state_id
103 );
104 let result = self.client.send_request(path);
105 match result.await {
106 Ok(res) => {
107 if res.status().is_success() {
108 ResponsePayloadWithEndpointInfo {
109 payload: res
110 .json::<SuccessEndpointPayload>()
111 .await
112 .map_err(|e| AppError::EndpointResponseError(e.to_string())),
113 endpoint: endpoint.clone(),
114 }
115 } else {
116 ResponsePayloadWithEndpointInfo {
117 payload: Err(AppError::EndpointResponseError(format!(
118 "Error with calling {} status code {}",
119 endpoint.clone(),
120 res.status().to_string()
121 ))),
122 endpoint: endpoint.clone(),
123 }
124 }
125 }
126 Err(e) => ResponsePayloadWithEndpointInfo {
127 payload: Err(e),
128 endpoint: endpoint.clone(),
129 },
130 }
131 }
132 .await;
133 raw_response
134 }))
135 .await;
136
137 Ok(process_to_displayable_format(results))
138 }
139}