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)]
18pub(crate) struct GraphQLBlock {
20 creator: String,
21 creator_account_key: CompressedPubKey,
22 winner_account_key: CompressedPubKey,
23 state_hash: String,
24 state_hash_field: String,
26 protocol_state: GraphQLProtocolState,
27 transactions: GraphQLTransactions,
30 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 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}