mina_node_native/graphql/
constants.rs

1use juniper::GraphQLObject;
2use mina_core::{
3    consensus::{ConsensusConstants, ConsensusTime},
4    constants::ConstraintConstants,
5};
6use node::{
7    rpc::{
8        ConsensusTimeQuery, PeerConnectionStatus, RpcConsensusTimeGetResponse,
9        RpcNodeStatusNetworkInfo, RpcPeerInfo, RpcRequest,
10    },
11    BuildEnv,
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    }
128
129    async fn state_hash(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
130        let best_tip = context.get_or_fetch_best_tip().await;
131        Ok(best_tip.map(|best_tip| best_tip.hash().to_string()))
132    }
133
134    async fn num_accounts(&self, context: &Context) -> juniper::FieldResult<Option<i32>> {
135        let best_tip = context.get_or_fetch_best_tip().await;
136
137        match best_tip {
138            Some(best_tip) => {
139                let ledger_status = context
140                    .get_or_fetch_ledger_status(best_tip.merkle_root_hash())
141                    .await;
142                Ok(ledger_status.map(|ledger_status| ledger_status.num_accounts as i32))
143            }
144            None => Ok(None),
145        }
146    }
147
148    async fn highest_unvalidated_block_length_received(
149        &self,
150        context: &Context,
151    ) -> juniper::FieldResult<Option<i32>> {
152        let status = context.get_or_fetch_status().await;
153        Ok(status.and_then(|status| {
154            status
155                .transition_frontier
156                .best_tip
157                .map(|best_tip| best_tip.height as i32)
158                .or_else(|| {
159                    status
160                        .transition_frontier
161                        .sync
162                        .target
163                        .map(|target| target.height as i32)
164                })
165        }))
166    }
167
168    async fn highest_block_length_received(
169        &self,
170        context: &Context,
171    ) -> juniper::FieldResult<Option<i32>> {
172        let status = context.get_or_fetch_status().await;
173        Ok(status.and_then(|status| {
174            status
175                .transition_frontier
176                .best_tip
177                .map(|best_tip| best_tip.height as i32)
178                .or_else(|| {
179                    status
180                        .transition_frontier
181                        .sync
182                        .target
183                        .map(|target| target.height as i32)
184                })
185        }))
186    }
187
188    async fn addrs_and_ports(
189        &self,
190        context: &Context,
191    ) -> juniper::FieldResult<GraphQLAddrsAndPorts> {
192        let status = context.get_or_fetch_status().await;
193
194        match status {
195            Some(status) => Ok(GraphQLAddrsAndPorts::from(&status.network_info)),
196            None => Ok(Default::default()),
197        }
198    }
199
200    async fn block_production_keys(&self, context: &Context) -> juniper::FieldResult<Vec<String>> {
201        let status = context.get_or_fetch_status().await;
202        Ok(status.map_or(vec![], |status| {
203            status
204                .block_producer
205                .map_or(vec![], |key| vec![key.to_string()])
206        }))
207    }
208
209    async fn coinbase_receiver(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
210        let status = context.get_or_fetch_status().await;
211        Ok(status.and_then(|status| status.coinbase_receiver.map(|key| key.to_string())))
212    }
213}
214
215#[derive(GraphQLObject, Clone, Debug)]
216pub struct GraphQLAddrsAndPorts {
217    pub bind_ip: String,
218    pub external_ip: Option<String>,
219    pub client_port: Option<i32>,
220    pub libp2p_port: Option<i32>,
221}
222
223impl Default for GraphQLAddrsAndPorts {
224    fn default() -> Self {
225        Self {
226            bind_ip: "0.0.0.0".to_string(),
227            external_ip: None,
228            client_port: None,
229            libp2p_port: None,
230        }
231    }
232}
233
234impl From<&RpcNodeStatusNetworkInfo> for GraphQLAddrsAndPorts {
235    fn from(network_info: &RpcNodeStatusNetworkInfo) -> Self {
236        Self {
237            bind_ip: network_info.bind_ip.clone(),
238            external_ip: network_info.external_ip.clone(),
239            client_port: network_info.client_port.map(|port| port.into()),
240            libp2p_port: network_info.libp2p_port.map(|port| port.into()),
241        }
242    }
243}
244
245#[derive(GraphQLObject, Clone, Debug)]
246pub struct GraphQLRpcPeerInfo {
247    pub peer_id: String,
248    pub best_tip: Option<String>,
249    pub best_tip_height: Option<String>,
250    pub best_tip_global_slot: Option<String>,
251    pub best_tip_timestamp: Option<String>,
252    pub connection_status: String,
253    pub connecting_details: Option<String>,
254    pub address: Option<String>,
255    pub incoming: bool,
256    pub is_libp2p: bool,
257    pub time: String,
258}
259
260impl From<&RpcPeerInfo> for GraphQLRpcPeerInfo {
261    fn from(peer: &RpcPeerInfo) -> Self {
262        Self {
263            peer_id: peer.peer_id.to_string(),
264            best_tip: peer.best_tip.as_ref().map(|hash| hash.to_string()),
265            best_tip_height: peer.best_tip_height.map(|height| height.to_string()),
266            best_tip_global_slot: peer.best_tip_global_slot.map(|slot| slot.to_string()),
267            best_tip_timestamp: peer
268                .best_tip_timestamp
269                .map(|timestamp| timestamp.to_string()),
270            connection_status: peer.connection_status.to_string(),
271            connecting_details: peer.connecting_details.clone(),
272            address: peer.address.clone(),
273            incoming: peer.incoming,
274            is_libp2p: peer.is_libp2p,
275            time: peer.time.to_string(),
276        }
277    }
278}
279
280#[derive(GraphQLObject, Debug)]
281pub struct GraphQLGenesisConstants {
282    pub genesis_timestamp: String,
283    pub coinbase: String,
284    pub account_creation_fee: String,
285}
286
287impl GraphQLGenesisConstants {
288    pub fn try_new(
289        constrain_constants: ConstraintConstants,
290        consensus_constants: ConsensusConstants,
291    ) -> Result<Self, ConversionError> {
292        Ok(GraphQLGenesisConstants {
293            genesis_timestamp: consensus_constants
294                .human_readable_genesis_timestamp()
295                .map_err(|e| ConversionError::Custom(e.to_string()))?,
296            coinbase: constrain_constants.coinbase_amount.to_string(),
297            account_creation_fee: constrain_constants.account_creation_fee.to_string(),
298        })
299    }
300}
301
302#[derive(GraphQLObject, Debug)]
303pub struct GraphQLConsensusConfiguration {
304    pub epoch_duration: i32,
305    pub k: i32,
306    pub slot_duration: i32,
307    pub slots_per_epoch: i32,
308}
309
310impl From<ConsensusConstants> for GraphQLConsensusConfiguration {
311    fn from(consensus_constants: ConsensusConstants) -> Self {
312        GraphQLConsensusConfiguration {
313            epoch_duration: consensus_constants.epoch_duration as i32,
314            k: consensus_constants.k as i32,
315            slot_duration: consensus_constants.slot_duration_ms as i32,
316            slots_per_epoch: consensus_constants.slots_per_epoch as i32,
317        }
318    }
319}
320
321#[derive(GraphQLObject, Debug)]
322pub struct GraphQLConsensusTime {
323    pub start_time: String,
324    pub end_time: String,
325    pub epoch: String,
326    pub global_slot: String,
327    pub slot: String,
328}
329
330impl From<ConsensusTime> for GraphQLConsensusTime {
331    fn from(consensus_time: ConsensusTime) -> Self {
332        let start_time: u64 = consensus_time.start_time.into();
333        let end_time: u64 = consensus_time.end_time.into();
334
335        let start_time_ms = start_time.checked_div(1_000_000).expect("division by zero");
336        let end_time_ms = end_time.checked_div(1_000_000).expect("division by zero");
337
338        GraphQLConsensusTime {
339            start_time: start_time_ms.to_string(),
340            end_time: end_time_ms.to_string(),
341            epoch: consensus_time.epoch.to_string(),
342            global_slot: consensus_time.global_slot.to_string(),
343            slot: consensus_time.slot.to_string(),
344        }
345    }
346}