openmina_node_native/graphql/
constants.rs

1use juniper::GraphQLObject;
2use node::{
3    rpc::{
4        ConsensusTimeQuery, PeerConnectionStatus, RpcConsensusTimeGetResponse,
5        RpcNodeStatusNetworkInfo, RpcPeerInfo, RpcRequest,
6    },
7    BuildEnv,
8};
9use openmina_core::{
10    consensus::{ConsensusConstants, ConsensusTime},
11    constants::ConstraintConstants,
12};
13
14use super::{Context, ConversionError, Error};
15
16#[derive(Clone, Debug, Copy)]
17pub(crate) struct GraphQLDaemonStatus;
18
19#[juniper::graphql_object(context = Context)]
20impl GraphQLDaemonStatus {
21    async fn consensus_configuration(
22        &self,
23        context: &Context,
24    ) -> juniper::FieldResult<GraphQLConsensusConfiguration> {
25        let consensus_constants: ConsensusConstants = context
26            .rpc_sender
27            .oneshot_request(RpcRequest::ConsensusConstantsGet)
28            .await
29            .ok_or(Error::StateMachineEmptyResponse)?;
30        Ok(GraphQLConsensusConfiguration::from(consensus_constants))
31    }
32
33    async fn peers(&self, context: &Context) -> juniper::FieldResult<Vec<GraphQLRpcPeerInfo>> {
34        let peers: Vec<RpcPeerInfo> = context
35            .rpc_sender
36            .oneshot_request(RpcRequest::PeersGet)
37            .await
38            .ok_or(Error::StateMachineEmptyResponse)?;
39
40        let connected_peers = peers
41            .iter()
42            .filter(|peer| matches!(peer.connection_status, PeerConnectionStatus::Connected))
43            .map(GraphQLRpcPeerInfo::from)
44            .collect();
45
46        Ok(connected_peers)
47    }
48
49    async fn consensus_time_now(
50        &self,
51        context: &Context,
52    ) -> juniper::FieldResult<GraphQLConsensusTime> {
53        let consensus_time: RpcConsensusTimeGetResponse = context
54            .rpc_sender
55            .oneshot_request(RpcRequest::ConsensusTimeGet(ConsensusTimeQuery::Now))
56            .await
57            .ok_or(Error::StateMachineEmptyResponse)?;
58
59        match consensus_time {
60            Some(consensus_time) => Ok(GraphQLConsensusTime::from(consensus_time)),
61            None => Err(juniper::FieldError::new(
62                "No consensus time found",
63                juniper::Value::Null,
64            )),
65        }
66    }
67
68    async fn consensus_time_best_tip(
69        &self,
70        context: &Context,
71    ) -> juniper::FieldResult<GraphQLConsensusTime> {
72        let consensus_time_res: RpcConsensusTimeGetResponse = context
73            .rpc_sender
74            .oneshot_request(RpcRequest::ConsensusTimeGet(ConsensusTimeQuery::BestTip))
75            .await
76            .ok_or(Error::StateMachineEmptyResponse)?;
77
78        match consensus_time_res {
79            Some(consensus_time) => Ok(GraphQLConsensusTime::from(consensus_time)),
80            None => Err(juniper::FieldError::new(
81                "No consensus time found",
82                juniper::Value::Null,
83            )),
84        }
85    }
86
87    async fn consensus_mechanism(&self, _context: &Context) -> juniper::FieldResult<String> {
88        Ok("proof_of_stake".to_string())
89    }
90
91    async fn blockchain_length(&self, context: &Context) -> juniper::FieldResult<Option<i32>> {
92        let status = context.get_or_fetch_status().await;
93
94        Ok(status.and_then(|status| {
95            status
96                .transition_frontier
97                .best_tip
98                .map(|block_summary| block_summary.height as i32)
99        }))
100    }
101
102    async fn chain_id(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
103        let status = context.get_or_fetch_status().await;
104
105        Ok(status.and_then(|status| status.chain_id))
106    }
107
108    async fn commit_id(&self, _context: &Context) -> juniper::FieldResult<String> {
109        Ok(BuildEnv::get().git.commit_hash.to_string())
110    }
111
112    async fn global_slot_since_genesis_best_tip(
113        &self,
114        context: &Context,
115    ) -> juniper::FieldResult<Option<i32>> {
116        let best_tip = context.get_or_fetch_best_tip().await;
117        Ok(best_tip.and_then(|best_tip| {
118            println!("best_tip OK");
119            best_tip.global_slot_since_genesis().try_into().ok()
120        }))
121    }
122
123    async fn ledger_merkle_root(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
124        let best_tip = context.get_or_fetch_best_tip().await;
125
126        Ok(best_tip.map(|best_tip| best_tip.merkle_root_hash().to_string()))
127        // match best_tip {
128        //     Some(best_tip) => {
129        //         println!("best_tip_ledger_merkle_root {:?}", best_tip.merkle_root_hash());
130        //         let ledger_status = context
131        //             .get_or_fetch_ledger_status(best_tip.merkle_root_hash())
132        //             .await;
133        //         Ok(ledger_status
134        //             .map(|ledger_status| ledger_status.best_tip_staged_ledger_hash.to_string()))
135        //     }
136        //     None => Ok(None),
137        // }
138    }
139
140    async fn state_hash(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
141        let best_tip = context.get_or_fetch_best_tip().await;
142        Ok(best_tip.map(|best_tip| best_tip.hash().to_string()))
143    }
144
145    async fn num_accounts(&self, context: &Context) -> juniper::FieldResult<Option<i32>> {
146        let best_tip = context.get_or_fetch_best_tip().await;
147
148        match best_tip {
149            Some(best_tip) => {
150                let ledger_status = context
151                    .get_or_fetch_ledger_status(best_tip.merkle_root_hash())
152                    .await;
153                Ok(ledger_status.map(|ledger_status| ledger_status.num_accounts as i32))
154            }
155            None => Ok(None),
156        }
157    }
158
159    async fn highest_unvalidated_block_length_received(
160        &self,
161        context: &Context,
162    ) -> juniper::FieldResult<Option<i32>> {
163        let status = context.get_or_fetch_status().await;
164        Ok(status.and_then(|status| {
165            status
166                .transition_frontier
167                .best_tip
168                .map(|best_tip| best_tip.height as i32)
169                .or_else(|| {
170                    status
171                        .transition_frontier
172                        .sync
173                        .target
174                        .map(|target| target.height as i32)
175                })
176        }))
177    }
178
179    async fn highest_block_length_received(
180        &self,
181        context: &Context,
182    ) -> juniper::FieldResult<Option<i32>> {
183        let status = context.get_or_fetch_status().await;
184        Ok(status.and_then(|status| {
185            status
186                .transition_frontier
187                .best_tip
188                .map(|best_tip| best_tip.height as i32)
189                .or_else(|| {
190                    status
191                        .transition_frontier
192                        .sync
193                        .target
194                        .map(|target| target.height as i32)
195                })
196        }))
197    }
198
199    async fn addrs_and_ports(
200        &self,
201        context: &Context,
202    ) -> juniper::FieldResult<GraphQLAddrsAndPorts> {
203        let status = context.get_or_fetch_status().await;
204
205        match status {
206            Some(status) => Ok(GraphQLAddrsAndPorts::from(&status.network_info)),
207            None => Ok(Default::default()),
208        }
209    }
210
211    async fn block_production_keys(&self, context: &Context) -> juniper::FieldResult<Vec<String>> {
212        let status = context.get_or_fetch_status().await;
213        Ok(status.map_or(vec![], |status| {
214            status
215                .block_producer
216                .map_or(vec![], |key| vec![key.to_string()])
217        }))
218    }
219
220    async fn coinbase_receiver(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
221        let status = context.get_or_fetch_status().await;
222        Ok(status.and_then(|status| status.coinbase_receiver.map(|key| key.to_string())))
223    }
224}
225
226#[derive(GraphQLObject, Clone, Debug)]
227pub struct GraphQLAddrsAndPorts {
228    pub bind_ip: String,
229    pub external_ip: Option<String>,
230    pub client_port: Option<i32>,
231    pub libp2p_port: Option<i32>,
232}
233
234impl Default for GraphQLAddrsAndPorts {
235    fn default() -> Self {
236        Self {
237            bind_ip: "0.0.0.0".to_string(),
238            external_ip: None,
239            client_port: None,
240            libp2p_port: None,
241        }
242    }
243}
244
245impl From<&RpcNodeStatusNetworkInfo> for GraphQLAddrsAndPorts {
246    fn from(network_info: &RpcNodeStatusNetworkInfo) -> Self {
247        Self {
248            bind_ip: network_info.bind_ip.clone(),
249            external_ip: network_info.external_ip.clone(),
250            client_port: network_info.client_port.map(|port| port.into()),
251            libp2p_port: network_info.libp2p_port.map(|port| port.into()),
252        }
253    }
254}
255
256#[derive(GraphQLObject, Clone, Debug)]
257pub struct GraphQLRpcPeerInfo {
258    pub peer_id: String,
259    pub best_tip: Option<String>,
260    pub best_tip_height: Option<String>,
261    pub best_tip_global_slot: Option<String>,
262    pub best_tip_timestamp: Option<String>,
263    pub connection_status: String,
264    pub connecting_details: Option<String>,
265    pub address: Option<String>,
266    pub incoming: bool,
267    pub is_libp2p: bool,
268    pub time: String,
269}
270
271impl From<&RpcPeerInfo> for GraphQLRpcPeerInfo {
272    fn from(peer: &RpcPeerInfo) -> Self {
273        Self {
274            peer_id: peer.peer_id.to_string(),
275            best_tip: peer.best_tip.as_ref().map(|hash| hash.to_string()),
276            best_tip_height: peer.best_tip_height.map(|height| height.to_string()),
277            best_tip_global_slot: peer.best_tip_global_slot.map(|slot| slot.to_string()),
278            best_tip_timestamp: peer
279                .best_tip_timestamp
280                .map(|timestamp| timestamp.to_string()),
281            connection_status: peer.connection_status.to_string(),
282            connecting_details: peer.connecting_details.clone(),
283            address: peer.address.clone(),
284            incoming: peer.incoming,
285            is_libp2p: peer.is_libp2p,
286            time: peer.time.to_string(),
287        }
288    }
289}
290
291#[derive(GraphQLObject, Debug)]
292pub struct GraphQLGenesisConstants {
293    pub genesis_timestamp: String,
294    pub coinbase: String,
295    pub account_creation_fee: String,
296}
297
298impl GraphQLGenesisConstants {
299    pub fn try_new(
300        constrain_constants: ConstraintConstants,
301        consensus_constants: ConsensusConstants,
302    ) -> Result<Self, ConversionError> {
303        Ok(GraphQLGenesisConstants {
304            genesis_timestamp: consensus_constants
305                .human_readable_genesis_timestamp()
306                .map_err(|e| ConversionError::Custom(e.to_string()))?,
307            coinbase: constrain_constants.coinbase_amount.to_string(),
308            account_creation_fee: constrain_constants.account_creation_fee.to_string(),
309        })
310    }
311}
312
313#[derive(GraphQLObject, Debug)]
314pub struct GraphQLConsensusConfiguration {
315    pub epoch_duration: i32,
316    pub k: i32,
317    pub slot_duration: i32,
318    pub slots_per_epoch: i32,
319}
320
321impl From<ConsensusConstants> for GraphQLConsensusConfiguration {
322    fn from(consensus_constants: ConsensusConstants) -> Self {
323        GraphQLConsensusConfiguration {
324            epoch_duration: consensus_constants.epoch_duration as i32,
325            k: consensus_constants.k as i32,
326            slot_duration: consensus_constants.slot_duration_ms as i32,
327            slots_per_epoch: consensus_constants.slots_per_epoch as i32,
328        }
329    }
330}
331
332#[derive(GraphQLObject, Debug)]
333pub struct GraphQLConsensusTime {
334    pub start_time: String,
335    pub end_time: String,
336    pub epoch: String,
337    pub global_slot: String,
338    pub slot: String,
339}
340
341impl From<ConsensusTime> for GraphQLConsensusTime {
342    fn from(consensus_time: ConsensusTime) -> Self {
343        let start_time: u64 = consensus_time.start_time.into();
344        let end_time: u64 = consensus_time.end_time.into();
345
346        let start_time_ms = start_time.checked_div(1_000_000).expect("division by zero");
347        let end_time_ms = end_time.checked_div(1_000_000).expect("division by zero");
348
349        GraphQLConsensusTime {
350            start_time: start_time_ms.to_string(),
351            end_time: end_time_ms.to_string(),
352            epoch: consensus_time.epoch.to_string(),
353            global_slot: consensus_time.global_slot.to_string(),
354            slot: consensus_time.slot.to_string(),
355        }
356    }
357}