openmina_node_native/graphql/
block.rs

1use crate::graphql::{
2    account::GraphQLAccount,
3    zkapp::{GraphQLFailureReason, GraphQLFeePayer, GraphQLZkappCommand},
4};
5use juniper::{graphql_object, FieldResult, GraphQLEnum, GraphQLObject};
6use ledger::AccountId;
7use mina_p2p_messages::v2::{
8    MinaBaseSignedCommandPayloadBodyStableV2, MinaBaseSignedCommandStableV2,
9    MinaBaseStakeDelegationStableV2, TransactionSnarkWorkTStableV2,
10};
11use mina_signer::CompressedPubKey;
12use node::account::AccountPublicKey;
13use openmina_core::block::AppliedBlock;
14
15use super::{zkapp::GraphQLZkapp, Context, ConversionError};
16
17#[derive(Debug)]
18/// Location [src/lib/mina_graphql/types.ml:2095](https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_graphql/types.ml#L2095-L2151)
19pub(crate) struct GraphQLBlock {
20    creator: String,
21    creator_account_key: CompressedPubKey,
22    winner_account_key: CompressedPubKey,
23    state_hash: String,
24    /// Experimental: Bigint field-element representation of stateHash
25    state_hash_field: String,
26    protocol_state: GraphQLProtocolState,
27    /// Public key of account that produced this block
28    /// use creatorAccount field instead
29    transactions: GraphQLTransactions,
30    /// Base58Check-encoded hash of the state after this block
31    /// Count of user command transactions in the block
32    command_transaction_count: i32,
33    snark_jobs: Vec<GraphQLSnarkJob>,
34}
35
36#[graphql_object(context = Context)]
37#[graphql(description = "A Mina block")]
38impl GraphQLBlock {
39    fn creator(&self) -> &str {
40        &self.creator
41    }
42
43    async fn creator_account(&self, context: &Context) -> FieldResult<Box<GraphQLAccount>> {
44        let account_id = AccountId::new_with_default_token(self.creator_account_key.clone());
45        if let Some(account) = context.load_account(account_id).await {
46            Ok(Box::new(account))
47        } else {
48            Err(juniper::FieldError::new(
49                "Failed to load creator account".to_string(),
50                juniper::Value::null(),
51            ))
52        }
53    }
54    async fn winner_account(&self, context: &Context) -> FieldResult<Box<GraphQLAccount>> {
55        let account_id = AccountId::new_with_default_token(self.winner_account_key.clone());
56        if let Some(account) = context.load_account(account_id).await {
57            Ok(Box::new(account))
58        } else {
59            Err(juniper::FieldError::new(
60                "Failed to load winner account".to_string(),
61                juniper::Value::null(),
62            ))
63        }
64    }
65
66    async fn state_hash(&self) -> &str {
67        &self.state_hash
68    }
69
70    /// Experimental: Bigint field-element representation of stateHash
71    async fn state_hash_field(&self) -> &str {
72        &self.state_hash_field
73    }
74
75    async fn protocol_state(&self) -> &GraphQLProtocolState {
76        &self.protocol_state
77    }
78
79    async fn transactions(&self) -> &GraphQLTransactions {
80        &self.transactions
81    }
82
83    async fn command_transaction_count(&self) -> i32 {
84        self.command_transaction_count
85    }
86
87    async fn snark_jobs(&self) -> &Vec<GraphQLSnarkJob> {
88        &self.snark_jobs
89    }
90}
91
92#[derive(GraphQLObject, Debug)]
93pub struct GraphQLSnarkJob {
94    pub fee: String,
95    pub prover: String,
96}
97
98#[derive(GraphQLObject, Debug)]
99pub struct GraphQLTransactions {
100    pub zkapp_commands: Vec<GraphQLZkapp>,
101    pub user_commands: Vec<GraphQLUserCommands>,
102}
103
104#[derive(GraphQLObject, Debug)]
105pub struct GraphQLUserCommands {
106    pub amount: Option<String>,
107    pub failure_reason: Option<String>,
108    pub fee: String,
109    pub fee_token: String,
110    pub from: String,
111    pub hash: String,
112    pub id: String,
113    pub is_delegation: bool,
114    pub kind: GraphQLUserCommandsKind,
115    pub memo: String,
116    pub nonce: i32,
117    pub to: String,
118    pub token: String,
119    pub valid_until: String,
120}
121
122#[derive(Clone, Copy, Debug, GraphQLEnum)]
123#[allow(non_camel_case_types)]
124pub enum GraphQLUserCommandsKind {
125    PAYMENT,
126    STAKE_DELEGATION,
127}
128
129impl TryFrom<AppliedBlock> for GraphQLBlock {
130    type Error = ConversionError;
131    fn try_from(value: AppliedBlock) -> Result<Self, Self::Error> {
132        let block = value.block;
133        let blockchain_state = GraphQLBlockchainState {
134            snarked_ledger_hash: block.snarked_ledger_hash().to_string(),
135            staged_ledger_hash: block
136                .staged_ledger_hashes()
137                .non_snark
138                .ledger_hash
139                .to_string(),
140            date: block
141                .header()
142                .protocol_state
143                .body
144                .blockchain_state
145                .timestamp
146                .to_string(),
147            utc_date: block
148                .header()
149                .protocol_state
150                .body
151                .blockchain_state
152                .timestamp
153                .to_string(),
154            staged_ledger_proof_emitted: value.just_emitted_a_proof,
155        };
156
157        let protocol_state = GraphQLProtocolState {
158            previous_state_hash: block.pred_hash().to_string(),
159            blockchain_state,
160            consensus_state: block
161                .header()
162                .protocol_state
163                .body
164                .consensus_state
165                .clone()
166                .into(),
167        };
168
169        let command_transaction_count = block.body().diff().0.commands.len() as i32;
170
171        let snark_jobs = block
172            .body()
173            .completed_works_iter()
174            .map(GraphQLSnarkJob::from)
175            .collect();
176
177        Ok(Self {
178            creator_account_key: AccountPublicKey::from(block.producer().clone())
179                .try_into()
180                .map_err(|_| ConversionError::Custom("Invalid public key".to_string()))?,
181            winner_account_key: AccountPublicKey::from(block.block_stake_winner().clone())
182                .try_into()
183                .map_err(|_| ConversionError::Custom("Invalid public key".to_string()))?,
184            protocol_state,
185            state_hash: block.hash.to_string(),
186            state_hash_field: block.hash.to_decimal(),
187            creator: block.producer().to_string(),
188            transactions: block.body().diff().clone().try_into()?,
189            command_transaction_count,
190            snark_jobs,
191        })
192    }
193}
194
195#[derive(GraphQLObject, Debug)]
196pub struct GraphQLProtocolState {
197    pub previous_state_hash: String,
198    pub blockchain_state: GraphQLBlockchainState,
199    pub consensus_state: GraphQLConsensusState,
200}
201
202#[derive(GraphQLObject, Debug)]
203pub struct GraphQLBlockchainState {
204    pub snarked_ledger_hash: String,
205    pub staged_ledger_hash: String,
206    pub date: String,
207    pub utc_date: String,
208    pub staged_ledger_proof_emitted: bool,
209}
210
211#[derive(GraphQLObject, Debug)]
212pub struct GraphQLConsensusState {
213    pub block_height: String,
214    pub slot_since_genesis: String,
215    pub slot: String,
216    pub next_epoch_data: GraphQLEpochData,
217    pub staking_epoch_data: GraphQLEpochData,
218    pub epoch_count: String,
219    pub min_window_density: String,
220    pub total_currency: String,
221    pub epoch: String,
222}
223
224#[derive(GraphQLObject, Debug)]
225pub struct GraphQLEpochData {
226    pub ledger: GraphQLLedger,
227    pub seed: String,
228    pub start_checkpoint: String,
229    pub lock_checkpoint: String,
230    pub epoch_length: String,
231}
232
233#[derive(GraphQLObject, Debug)]
234pub struct GraphQLLedger {
235    pub hash: String,
236    pub total_currency: String,
237}
238
239impl TryFrom<mina_p2p_messages::v2::StagedLedgerDiffDiffDiffStableV2> for GraphQLTransactions {
240    type Error = ConversionError;
241    fn try_from(
242        value: mina_p2p_messages::v2::StagedLedgerDiffDiffDiffStableV2,
243    ) -> Result<Self, Self::Error> {
244        use mina_p2p_messages::v2::{
245            MinaBaseTransactionStatusStableV2, MinaBaseUserCommandStableV2,
246        };
247
248        let also_zkapp_commands = value
249            .1
250            .map_or_else(Vec::new, |v| v.commands.into_iter().collect::<Vec<_>>());
251
252        let commands = value
253            .0
254            .commands
255            .into_iter()
256            .chain(also_zkapp_commands)
257            .rev();
258
259        let mut zkapp_commands = Vec::new();
260        let mut user_commands = Vec::new();
261
262        for command in commands {
263            match command.data {
264                MinaBaseUserCommandStableV2::SignedCommand(user_command) => {
265                    user_commands.push(GraphQLUserCommands::try_from(user_command)?);
266                }
267                MinaBaseUserCommandStableV2::ZkappCommand(zkapp) => {
268                    let failure_reason =
269                        if let MinaBaseTransactionStatusStableV2::Failed(failure_collection) =
270                            command.status
271                        {
272                            let res = failure_collection
273                                .0
274                                .into_iter()
275                                .enumerate()
276                                .skip(1)
277                                .map(|(index, failure_list)| {
278                                    let fl =
279                                        failure_list.into_iter().map(|v| v.to_string()).collect();
280                                    GraphQLFailureReason {
281                                        index: index.to_string(),
282                                        failures: fl,
283                                    }
284                                })
285                                .rev()
286                                .collect();
287
288                            Some(res)
289                        } else {
290                            None
291                        };
292
293                    let account_updates = zkapp
294                        .account_updates
295                        .clone()
296                        .into_iter()
297                        .map(|v| v.elt.account_update.try_into())
298                        .collect::<Result<Vec<_>, _>>()?;
299
300                    zkapp_commands.push(GraphQLZkapp {
301                        hash: zkapp.hash()?.to_string(),
302                        failure_reason,
303                        id: zkapp.to_base64()?,
304                        zkapp_command: GraphQLZkappCommand {
305                            memo: zkapp.memo.to_base58check(),
306                            account_updates,
307                            fee_payer: GraphQLFeePayer::from(zkapp.fee_payer),
308                        },
309                    });
310                }
311            }
312        }
313
314        Ok(Self {
315            zkapp_commands,
316            user_commands,
317        })
318    }
319}
320
321impl From<mina_p2p_messages::v2::MinaBaseEpochLedgerValueStableV1> for GraphQLLedger {
322    fn from(value: mina_p2p_messages::v2::MinaBaseEpochLedgerValueStableV1) -> Self {
323        Self {
324            hash: value.hash.to_string(),
325            total_currency: value.total_currency.as_u64().to_string(),
326        }
327    }
328}
329
330impl From<mina_p2p_messages::v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1>
331    for GraphQLEpochData
332{
333    fn from(
334        value: mina_p2p_messages::v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1,
335    ) -> Self {
336        Self {
337            ledger: value.ledger.into(),
338            seed: value.seed.to_string(),
339            start_checkpoint: value.start_checkpoint.to_string(),
340            lock_checkpoint: value.lock_checkpoint.to_string(),
341            epoch_length: value.epoch_length.as_u32().to_string(),
342        }
343    }
344}
345
346impl
347    From<
348        mina_p2p_messages::v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1,
349    > for GraphQLEpochData
350{
351    fn from(
352        value: mina_p2p_messages::v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1,
353    ) -> Self {
354        Self {
355            ledger: value.ledger.into(),
356            seed: value.seed.to_string(),
357            start_checkpoint: value.start_checkpoint.to_string(),
358            lock_checkpoint: value.lock_checkpoint.to_string(),
359            epoch_length: value.epoch_length.as_u32().to_string(),
360        }
361    }
362}
363
364impl From<mina_p2p_messages::v2::ConsensusProofOfStakeDataConsensusStateValueStableV2>
365    for GraphQLConsensusState
366{
367    fn from(
368        value: mina_p2p_messages::v2::ConsensusProofOfStakeDataConsensusStateValueStableV2,
369    ) -> Self {
370        let slot = value.curr_global_slot_since_hard_fork.slot_number.as_u32()
371            % value
372                .curr_global_slot_since_hard_fork
373                .slots_per_epoch
374                .as_u32();
375
376        Self {
377            block_height: value.blockchain_length.as_u32().to_string(),
378            slot_since_genesis: value.global_slot_since_genesis.as_u32().to_string(),
379            slot: slot.to_string(),
380            next_epoch_data: value.next_epoch_data.into(),
381            staking_epoch_data: value.staking_epoch_data.into(),
382            epoch_count: value.epoch_count.as_u32().to_string(),
383            min_window_density: value.min_window_density.as_u32().to_string(),
384            total_currency: value.total_currency.as_u64().to_string(),
385            epoch: value.epoch_count.as_u32().to_string(),
386        }
387    }
388}
389
390impl From<&TransactionSnarkWorkTStableV2> for GraphQLSnarkJob {
391    fn from(value: &TransactionSnarkWorkTStableV2) -> Self {
392        Self {
393            fee: value.fee.to_string(),
394            prover: value.prover.to_string(),
395        }
396    }
397}
398
399impl TryFrom<MinaBaseSignedCommandStableV2> for GraphQLUserCommands {
400    type Error = ConversionError;
401
402    fn try_from(user_command: MinaBaseSignedCommandStableV2) -> Result<Self, Self::Error> {
403        let is_delegation = matches!(
404            user_command.payload.body,
405            MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(_)
406        );
407        let hash = user_command.hash()?.to_string();
408        let id = user_command.to_base64()?;
409
410        let fee = user_command.payload.common.fee.to_string();
411        let memo = user_command.payload.common.memo.to_base58check();
412        let nonce = user_command.payload.common.nonce.as_u32() as i32;
413        let valid_until = user_command.payload.common.valid_until.as_u32().to_string();
414
415        let (to, amount, kind) = match user_command.payload.body {
416            MinaBaseSignedCommandPayloadBodyStableV2::Payment(payment) => (
417                payment.receiver_pk.to_string(),
418                Some(payment.amount.to_string()),
419                GraphQLUserCommandsKind::PAYMENT,
420            ),
421            MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(
422                MinaBaseStakeDelegationStableV2::SetDelegate { new_delegate },
423            ) => (
424                new_delegate.to_string(),
425                None,
426                GraphQLUserCommandsKind::STAKE_DELEGATION,
427            ),
428        };
429
430        Ok(GraphQLUserCommands {
431            hash,
432            from: user_command.signer.to_string(),
433            to,
434            is_delegation,
435            amount,
436            failure_reason: Default::default(),
437            fee,
438            fee_token: Default::default(),
439            id,
440            kind,
441            memo,
442            nonce,
443            token: Default::default(),
444            valid_until,
445        })
446    }
447}