openmina_node_native/graphql/
constants.rs1use 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 }
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}