Skip to main content

mina_node/rpc/
mod.rs

1mod rpc_state;
2use std::{collections::BTreeMap, str::FromStr};
3
4use crate::p2p::bootstrap::P2pNetworkKadBootstrapStats;
5use ledger::{
6    scan_state::{
7        currency::{Amount, Balance, Fee, Nonce, Slot},
8        transaction_logic::{signed_command, signed_command::SignedCommandPayload, valid, Memo},
9    },
10    transaction_pool::{diff, ValidCommandWithHash},
11    Account, AccountId,
12};
13use mina_core::{
14    block::{AppliedBlock, ArcBlockWithHash},
15    consensus::{ConsensusConstants, ConsensusTime},
16};
17use mina_node_account::AccountPublicKey;
18use mina_p2p_messages::{
19    bigint::{BigInt, InvalidBigInt},
20    v2::{
21        LedgerHash, MinaBaseSignedCommandPayloadBodyStableV2, MinaBaseSignedCommandStableV2,
22        MinaBaseTransactionStatusStableV2, MinaBaseUserCommandStableV2,
23        MinaBaseZkappCommandTStableV1WireStableV1, MinaTransactionTransactionStableV2,
24        SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse, StateHash, TransactionHash,
25        TransactionSnarkWorkTStableV2,
26    },
27};
28pub use rpc_state::*;
29
30mod rpc_actions;
31pub use rpc_actions::*;
32
33mod rpc_reducer;
34pub use rpc_reducer::collect_rpc_peers_info;
35
36mod rpc_impls;
37
38mod heartbeat;
39pub use heartbeat::{NodeHeartbeat, ProducedBlockInfo, SignedNodeHeartbeat};
40
41pub use mina_core::requests::{RpcId, RpcIdType};
42
43use ledger::scan_state::scan_state::{transaction_snark::OneOrTwo, AvailableJobMessage};
44use mina_core::snark::SnarkJobId;
45use mina_p2p_messages::v2::{CurrencyFeeStableV1, NonZeroCurvePoint};
46use redux::Timestamp;
47use serde::{Deserialize, Serialize};
48
49use crate::{
50    external_snark_worker::{
51        ExternalSnarkWorkerError, ExternalSnarkWorkerWorkError, SnarkWorkSpecError,
52    },
53    ledger::{
54        read::{LedgerReadId, LedgerReadKind, LedgerStatus},
55        write::LedgerWriteKind,
56    },
57    p2p::{
58        connection::{
59            incoming::P2pConnectionIncomingInitOpts, outgoing::P2pConnectionOutgoingInitOpts,
60        },
61        PeerId,
62    },
63    service::Queues,
64    snark_pool::{JobCommitment, JobState, JobSummary},
65    stats::{
66        actions::{ActionStatsForBlock, ActionStatsSnapshot},
67        block_producer::{
68            BlockProductionAttempt, BlockProductionAttemptWonSlot, VrfEvaluatorStats,
69        },
70        sync::SyncStatsSnapshot,
71    },
72};
73
74#[derive(Serialize, Deserialize, Debug, Clone)]
75pub enum RpcRequest {
76    StateGet(Option<String>),
77    StatusGet,
78    HeartbeatGet,
79    ActionStatsGet(ActionStatsQuery),
80    SyncStatsGet(SyncStatsQuery),
81    BlockProducerStatsGet,
82    MessageProgressGet,
83    PeersGet,
84    P2pConnectionOutgoing(P2pConnectionOutgoingInitOpts),
85    P2pConnectionIncoming(P2pConnectionIncomingInitOpts),
86    ScanStateSummaryGet(RpcScanStateSummaryGetQuery),
87    SnarkPoolGet,
88    SnarkPoolJobGet { job_id: SnarkJobId },
89    SnarkPoolCompletedJobsGet,
90    SnarkPoolPendingJobsGet,
91    SnarkerConfig,
92    SnarkerJobCommit { job_id: SnarkJobId },
93    SnarkerJobSpec { job_id: SnarkJobId },
94    SnarkerWorkers,
95    HealthCheck,
96    ReadinessCheck,
97    DiscoveryRoutingTable,
98    DiscoveryBoostrapStats,
99    TransactionPoolGet,
100    LedgerAccountsGet(AccountQuery),
101    TransactionInject(Vec<MinaBaseUserCommandStableV2>),
102    TransitionFrontierUserCommandsGet,
103    BestChain(MaxLength),
104    ConsensusConstantsGet,
105    TransactionStatusGet(MinaBaseUserCommandStableV2),
106    GetBlock(GetBlockQuery),
107    PooledUserCommands(PooledUserCommandsQuery),
108    PooledZkappCommands(PooledZkappsCommandsQuery),
109    GenesisBlockGet,
110    ConsensusTimeGet(ConsensusTimeQuery),
111    LedgerStatusGet(LedgerHash),
112    LedgerAccountDelegatorsGet(LedgerHash, AccountId),
113}
114
115#[derive(Serialize, Deserialize, Debug, Clone)]
116pub enum ConsensusTimeQuery {
117    Now,
118    BestTip,
119}
120
121pub type MaxLength = u32;
122
123#[derive(Serialize, Deserialize, Debug, Clone)]
124pub struct RpcInjectPayment {
125    fee: u64,
126    amount: u64,
127    to: AccountPublicKey,
128    from: AccountPublicKey,
129    memo: String,
130    nonce: u32,
131    valid_until: u32,
132    signature_field: BigInt,
133    signature_scalar: BigInt,
134}
135// MinaBaseUserCommandStableV2
136impl TryFrom<RpcInjectPayment> for MinaBaseUserCommandStableV2 {
137    type Error = InvalidBigInt;
138
139    fn try_from(value: RpcInjectPayment) -> Result<Self, Self::Error> {
140        let signature = mina_signer::Signature {
141            rx: value.signature_field.try_into()?,
142            s: value.signature_scalar.try_into()?,
143        };
144        println!("Signature: {signature}");
145        let sc = signed_command::SignedCommand {
146            payload: SignedCommandPayload::create(
147                Fee::from_u64(value.fee),
148                value.from.clone().try_into().map_err(|_| InvalidBigInt)?,
149                Nonce::from_u32(value.nonce),
150                Some(Slot::from_u32(value.valid_until)),
151                Memo::from_str(&value.memo).unwrap(),
152                signed_command::Body::Payment(signed_command::PaymentPayload {
153                    receiver_pk: value.to.try_into().map_err(|_| InvalidBigInt)?,
154                    amount: Amount::from_u64(value.amount),
155                }),
156            ),
157            signer: value.from.try_into().map_err(|_| InvalidBigInt)?,
158            signature,
159        };
160
161        Ok(MinaBaseUserCommandStableV2::SignedCommand(sc.into()))
162    }
163}
164
165#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
166pub enum ActionStatsQuery {
167    SinceStart,
168    ForLatestBlock,
169    ForBlockWithId(u64),
170}
171
172#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
173pub struct SyncStatsQuery {
174    pub limit: Option<usize>,
175}
176
177#[derive(Serialize, Deserialize, Debug, Clone)]
178pub enum RpcScanStateSummaryGetQuery {
179    ForBestTip,
180    ForBlockWithHash(StateHash),
181    ForBlockWithHeight(u32),
182}
183
184#[derive(Serialize, Deserialize, Debug, Clone)]
185#[serde(tag = "kind")]
186#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
187pub enum ActionStatsResponse {
188    SinceStart { stats: ActionStatsSnapshot },
189    ForBlock(ActionStatsForBlock),
190}
191
192#[derive(Serialize, Deserialize, Debug, Clone, strum_macros::Display)]
193#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
194pub enum PeerConnectionStatus {
195    Disconnecting,
196    Disconnected,
197    Connecting,
198    Connected,
199}
200
201#[derive(Serialize, Deserialize, Debug, Clone)]
202#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
203pub struct RpcPeerInfo {
204    pub peer_id: PeerId,
205    // TODO(openapi): generate ToSchema in Base58CheckOfBinProt macro
206    #[cfg_attr(feature = "openapi", schema(value_type = Option<String>))]
207    pub best_tip: Option<StateHash>,
208    pub best_tip_height: Option<u32>,
209    pub best_tip_global_slot: Option<u32>,
210    pub best_tip_timestamp: Option<u64>,
211    pub connection_status: PeerConnectionStatus,
212    pub connecting_details: Option<String>,
213    pub address: Option<String>,
214    pub incoming: bool,
215    pub is_libp2p: bool,
216    pub time: u64,
217}
218
219#[derive(Serialize, Deserialize, Debug, Clone)]
220#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
221pub struct RpcScanStateSummary {
222    pub block: RpcScanStateSummaryBlock,
223    pub scan_state: Vec<Vec<RpcScanStateSummaryScanStateJob>>,
224}
225
226#[derive(Serialize, Deserialize, Debug, Clone)]
227#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
228pub struct RpcScanStateSummaryBlock {
229    pub hash: StateHash,
230    pub height: u32,
231    pub global_slot: u32,
232    pub transactions: Vec<RpcScanStateSummaryBlockTransaction>,
233    pub completed_works: Vec<SnarkJobId>,
234}
235
236#[derive(Serialize, Deserialize, Debug, Clone)]
237#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
238pub struct RpcScanStateSummaryBlockTransaction {
239    /// None if hashing fails.
240    pub hash: Option<TransactionHash>,
241    pub kind: RpcScanStateSummaryBlockTransactionKind,
242    pub status: MinaBaseTransactionStatusStableV2,
243}
244
245#[derive(Serialize, Deserialize, Debug, Clone)]
246#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
247pub enum RpcScanStateSummaryBlockTransactionKind {
248    Payment,
249    StakeDelegation,
250    Zkapp,
251    FeeTransfer,
252    Coinbase,
253}
254
255#[derive(Serialize, Deserialize, Debug, Clone)]
256#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
257#[serde(tag = "status")]
258pub enum RpcScanStateSummaryScanStateJob {
259    Empty,
260    Todo {
261        job_id: SnarkJobId,
262        bundle_job_id: SnarkJobId,
263        job: RpcScanStateSummaryScanStateJobKind,
264        seq_no: u64,
265    },
266    Pending {
267        job_id: SnarkJobId,
268        bundle_job_id: SnarkJobId,
269        job: Box<RpcScanStateSummaryScanStateJobKind>,
270        seq_no: u64,
271        commitment: Option<Box<JobCommitment>>,
272        snark: Option<Box<RpcSnarkPoolJobSnarkWork>>,
273    },
274    Done {
275        job_id: SnarkJobId,
276        bundle_job_id: SnarkJobId,
277        job: Box<RpcScanStateSummaryScanStateJobKind>,
278        seq_no: u64,
279        snark: Box<RpcSnarkPoolJobSnarkWorkDone>,
280    },
281}
282
283#[derive(Serialize, Deserialize, Debug, Clone)]
284#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
285#[serde(tag = "kind")]
286pub enum RpcScanStateSummaryScanStateJobKind {
287    Base(RpcScanStateSummaryBlockTransaction),
288    Merge,
289}
290
291#[derive(Serialize, Deserialize, Debug, Clone)]
292#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
293pub enum RpcScanStateSummaryScanStateJobStatus {
294    Todo,
295    Done,
296}
297
298#[derive(Serialize, Debug, Clone)]
299#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
300pub struct RpcSnarkPoolJobSummary {
301    pub time: Timestamp,
302    pub id: SnarkJobId,
303    pub commitment: Option<JobCommitment>,
304    pub snark: Option<RpcSnarkPoolJobSnarkWork>,
305}
306
307#[derive(Serialize, Debug, Clone)]
308#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
309pub struct RpcSnarkPoolJobFull {
310    pub time: Timestamp,
311    pub id: SnarkJobId,
312    #[cfg_attr(feature = "openapi", schema(value_type = Object))]
313    pub job: OneOrTwo<AvailableJobMessage>,
314    pub commitment: Option<JobCommitment>,
315    pub snark: Option<RpcSnarkPoolJobSnarkWork>,
316}
317
318#[derive(Serialize, Deserialize, Debug, Clone)]
319#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
320pub struct RpcSnarkPoolJobSnarkWork {
321    pub snarker: NonZeroCurvePoint,
322    pub fee: CurrencyFeeStableV1,
323    pub received_t: Timestamp,
324    pub sender: PeerId,
325}
326
327#[derive(Serialize, Deserialize, Debug, Clone)]
328#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
329pub struct RpcSnarkPoolJobSnarkWorkDone {
330    pub snarker: NonZeroCurvePoint,
331    pub fee: CurrencyFeeStableV1,
332}
333
334#[derive(Serialize, Deserialize, Debug, Clone)]
335#[serde(tag = "kind")]
336pub enum RpcSnarkerJobCommitResponse {
337    Ok,
338    JobNotFound,
339    JobTaken,
340    SnarkerBusy,
341}
342
343#[derive(Serialize, Deserialize, Debug, Clone)]
344#[serde(tag = "kind")]
345pub enum RpcSnarkerJobSpecResponse {
346    Ok(Box<SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse>),
347    Err(SnarkWorkSpecError),
348    JobNotFound,
349}
350
351#[derive(Serialize, Deserialize, Debug, Clone)]
352#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
353pub struct RpcMessageProgressResponse {
354    pub messages_stats: BTreeMap<PeerId, MessagesStats>,
355    pub staking_ledger_sync: Option<LedgerSyncProgress>,
356    pub next_epoch_ledger_sync: Option<LedgerSyncProgress>,
357    pub root_ledger_sync: Option<RootLedgerSyncProgress>,
358}
359
360#[derive(Serialize, Deserialize, Debug, Clone)]
361#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
362pub struct MessagesStats {
363    pub current_request: Option<CurrentMessageProgress>,
364    pub responses: BTreeMap<String, usize>,
365}
366
367#[derive(Serialize, Deserialize, Debug, Clone)]
368#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
369pub struct LedgerSyncProgress {
370    pub fetched: u64,
371    pub estimation: u64,
372}
373
374#[derive(Serialize, Deserialize, Debug, Clone)]
375#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
376pub struct RootLedgerSyncProgress {
377    pub fetched: u64,
378    pub estimation: u64,
379    pub staged: Option<RootStagedLedgerSyncProgress>,
380}
381
382#[derive(Serialize, Deserialize, Debug, Clone)]
383#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
384pub struct RootStagedLedgerSyncProgress {
385    pub fetched: u64,
386    pub total: u64,
387}
388
389#[derive(Serialize, Deserialize, Debug, Clone)]
390#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
391pub struct CurrentMessageProgress {
392    pub name: String,
393    pub received_bytes: usize,
394    pub total_bytes: usize,
395}
396
397#[derive(Serialize, Deserialize, Debug, Clone, thiserror::Error)]
398pub enum RpcStateGetError {
399    #[error("failed to parse filter expression: {0}")]
400    FilterError(String),
401}
402
403pub type RpcStateGetResponse = Result<serde_json::Value, RpcStateGetError>;
404pub type RpcStatusGetResponse = Option<RpcNodeStatus>;
405pub type RpcHeartbeatGetResponse = Option<SignedNodeHeartbeat>;
406pub type RpcActionStatsGetResponse = Option<ActionStatsResponse>;
407pub type RpcSyncStatsGetResponse = Option<Vec<SyncStatsSnapshot>>;
408pub type RpcBlockProducerStatsGetResponse = Option<RpcBlockProducerStats>;
409pub type RpcPeersGetResponse = Vec<RpcPeerInfo>;
410pub type RpcP2pConnectionOutgoingResponse = Result<(), String>;
411pub type RpcScanStateSummaryGetResponse = Result<RpcScanStateSummary, String>;
412pub type RpcSnarkPoolGetResponse = Vec<RpcSnarkPoolJobSummary>;
413pub type RpcSnarkPoolCompletedJobsResponse = Vec<TransactionSnarkWorkTStableV2>;
414pub type RpcSnarkPoolPendingJobsGetResponse = Vec<JobState>;
415pub type RpcSnarkPoolJobGetResponse = Option<RpcSnarkPoolJobFull>;
416pub type RpcSnarkerConfigGetResponse = Option<RpcSnarkerConfig>;
417pub type RpcTransactionPoolResponse = Vec<ValidCommandWithHash>;
418pub type RpcLedgerSlimAccountsResponse = Vec<AccountSlim>;
419pub type RpcLedgerAccountsResponse = Vec<Account>;
420pub type RpcTransitionFrontierUserCommandsResponse = Vec<MinaBaseUserCommandStableV2>;
421pub type RpcBestChainResponse = Vec<AppliedBlock>;
422pub type RpcConsensusConstantsGetResponse = ConsensusConstants;
423pub type RpcTransactionStatusGetResponse = TransactionStatus;
424pub type RpcPooledUserCommandsResponse = Vec<MinaBaseSignedCommandStableV2>;
425pub type RpcPooledZkappCommandsResponse = Vec<MinaBaseZkappCommandTStableV1WireStableV1>;
426pub type RpcGenesisBlockResponse = Option<ArcBlockWithHash>;
427pub type RpcConsensusTimeGetResponse = Option<ConsensusTime>;
428pub type RpcLedgerStatusGetResponse = Option<LedgerStatus>;
429pub type RpcLedgerAccountDelegatorsGetResponse = Option<Vec<Account>>;
430
431#[derive(Serialize, Deserialize, Debug, Clone, strum_macros::Display)]
432#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
433pub enum TransactionStatus {
434    Pending,
435    Included,
436    Unknown,
437}
438
439// TODO(adonagy): rework this to handle all the possible user commands (enum..)
440#[derive(Serialize, Deserialize, Debug, Clone)]
441pub struct RpcTransactionInjectedPayment {
442    pub amount: Amount,
443    pub fee: Fee,
444    // pub fee_token: TokenId,
445    pub from: AccountPublicKey,
446    pub to: AccountPublicKey,
447    pub hash: String, // TODO(adonagy)
448    // pub id: String, // TODO(adonagy)
449    pub is_delegation: bool,
450    pub memo: String, // TODO(adonagy)
451    // pub memo: Memo, // TODO(adonagy)
452    pub nonce: Nonce,
453}
454
455// TODO(adonagy): remove this, not needed anymore
456// #[derive(Serialize, Deserialize, Debug, Clone)]
457// pub enum RpcTransactionInjectedCommand {
458//     Payment(valid::UserCommand),
459//     Delegation(valid::UserCommand),
460//     Zkapp(valid::UserCommand),
461// }
462
463pub type RpcTransactionInjectSuccess = Vec<valid::UserCommand>;
464pub type RpcTransactionInjectRejected = Vec<(valid::UserCommand, diff::Error)>;
465/// Errors
466pub type RpcTransactionInjectFailure = Vec<String>;
467
468#[derive(Serialize, Deserialize, Debug, Clone)]
469#[serde(untagged)]
470pub enum RpcTransactionInjectResponse {
471    Success(RpcTransactionInjectSuccess),
472    Rejected(RpcTransactionInjectRejected),
473    Failure(RpcTransactionInjectFailure),
474}
475
476// impl From<ValidCommandWithHash> for RpcTransactionInjectedCommand {
477//     fn from(value: ValidCommandWithHash) -> Self {
478//         match value.data {
479//             transaction_logic::valid::UserCommand::SignedCommand(ref signedcmd) => {
480//                 match signedcmd.payload.body {
481//                     transaction_logic::signed_command::Body::Payment(_) => {
482//                         Self::Payment(value.data.clone())
483//                     }
484//                     transaction_logic::signed_command::Body::StakeDelegation(_) => {
485//                         Self::Delegation(value.data.clone())
486//                     }
487//                 }
488//             }
489//             transaction_logic::valid::UserCommand::ZkAppCommand(_) => {
490//                 Self::Zkapp(value.data.clone())
491//             }
492//         }
493//     }
494// }
495
496// impl From<ValidCommandWithHash> for RpcTransactionInjectedCommand {
497//     fn from(value: ValidCommandWithHash) -> Self {
498//         match value.data {
499//             transaction_logic::valid::UserCommand::SignedCommand(signedcmd) => {
500//                 match signedcmd.payload.body {
501//                     transaction_logic::signed_command::Body::Payment(ref payment) => {
502//                         Self::RpcPayment(RpcTransactionInjectedPayment {
503//                             amount: payment.amount,
504//                             fee: signedcmd.fee(),
505//                             // fee_token: signedcmd.fee_token(),
506//                             from: signedcmd.fee_payer_pk().clone().into(),
507//                             to: payment.receiver_pk.clone().into(),
508//                             hash: value.hash.to_string(),
509//                             is_delegation: false,
510//                             // memo: signedcmd.payload.common.memo.clone(),
511//                             memo: signedcmd.payload.common.memo.to_string(),
512//                             nonce: signedcmd.nonce(),
513//                         })
514//                     }
515//                     transaction_logic::signed_command::Body::StakeDelegation(_) => {
516//                         todo!("inject stake delegation")
517//                     }
518//                 }
519//             }
520//             transaction_logic::valid::UserCommand::ZkAppCommand(_) => {
521//                 Self::Zkapp(value.data.clone())
522//             }
523//         }
524//     }
525// }
526
527#[derive(Serialize, Debug, Clone)]
528pub struct AccountSlim {
529    pub public_key: AccountPublicKey,
530    pub balance: Balance,
531    pub nonce: Nonce,
532}
533
534impl From<Account> for AccountSlim {
535    fn from(value: Account) -> Self {
536        Self {
537            public_key: AccountPublicKey::from(value.public_key),
538            balance: value.balance,
539            nonce: value.nonce,
540        }
541    }
542}
543
544/// Comprehensive node status returned by the `/status` RPC endpoint.
545///
546/// Contains information about the node's current state including sync status,
547/// peer connections, resource usage, and block production configuration.
548#[derive(Serialize, Debug, Clone)]
549#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
550pub struct RpcNodeStatus {
551    /// Network chain identifier (e.g., "mainnet", "devnet").
552    pub chain_id: Option<String>,
553    /// Transition frontier state including best tip and sync status.
554    pub transition_frontier: RpcNodeStatusTransitionFrontier,
555    /// Ledger state and pending operations.
556    pub ledger: RpcNodeStatusLedger,
557    /// SNARK pool statistics.
558    pub snark_pool: RpcNodeStatusSnarkPool,
559    /// Transaction pool statistics.
560    pub transaction_pool: RpcNodeStatusTransactionPool,
561    /// Current block production attempt, if any.
562    pub current_block_production_attempt: Option<BlockProductionAttempt>,
563    /// Previous block production attempt, if any.
564    pub previous_block_production_attempt: Option<BlockProductionAttempt>,
565    /// Connected peer information.
566    pub peers: Vec<RpcPeerInfo>,
567    /// Resource usage statistics.
568    pub resources_status: RpcNodeStatusResources,
569    /// Service queue statistics.
570    pub service_queues: Queues,
571    /// Network configuration information.
572    pub network_info: RpcNodeStatusNetworkInfo,
573    /// Block producer public key, if configured.
574    pub block_producer: Option<AccountPublicKey>,
575    /// Coinbase receiver public key, if configured.
576    pub coinbase_receiver: Option<AccountPublicKey>,
577}
578
579/// Network configuration information for the node.
580#[derive(Serialize, Debug, Clone)]
581#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
582pub struct RpcNodeStatusNetworkInfo {
583    /// IP address the node is bound to.
584    pub bind_ip: String,
585    /// External IP address, if known.
586    pub external_ip: Option<String>,
587    /// Client port for incoming connections.
588    pub client_port: Option<u16>,
589    /// libp2p port for P2P communication.
590    pub libp2p_port: Option<u16>,
591}
592
593/// Ledger state and pending operations.
594#[derive(Serialize, Debug, Clone)]
595#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
596pub struct RpcNodeStatusLedger {
597    /// Number of alive ledger masks after the last commit.
598    pub alive_masks_after_last_commit: usize,
599    /// Pending ledger write operations with timestamps.
600    pub pending_writes: Vec<(LedgerWriteKind, redux::Timestamp)>,
601    /// Pending ledger read operations with IDs, kinds, and timestamps.
602    pub pending_reads: Vec<(LedgerReadId, LedgerReadKind, redux::Timestamp)>,
603}
604
605/// Resource usage statistics for the node.
606#[derive(Serialize, Debug, Clone)]
607#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
608pub struct RpcNodeStatusResources {
609    /// Memory allocated for P2P operations in bytes.
610    pub p2p_malloc_size: usize,
611    /// Transition frontier resource usage.
612    pub transition_frontier: serde_json::Value,
613    /// SNARK pool resource usage.
614    pub snark_pool: serde_json::Value,
615}
616
617/// Transition frontier state including best tip and sync information.
618#[derive(Serialize, Deserialize, Debug, Clone)]
619#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
620pub struct RpcNodeStatusTransitionFrontier {
621    /// Current best tip block summary.
622    pub best_tip: Option<RpcNodeStatusTransitionFrontierBlockSummary>,
623    /// Synchronization status.
624    pub sync: RpcNodeStatusTransitionFrontierSync,
625}
626
627/// Synchronization status information.
628#[derive(Serialize, Deserialize, Debug, Clone)]
629#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
630pub struct RpcNodeStatusTransitionFrontierSync {
631    /// Timestamp of the last sync update.
632    pub time: Option<redux::Timestamp>,
633    /// Current sync status (e.g., "Synced", "Bootstrap", "Catchup").
634    pub status: String,
635    /// Current sync phase.
636    pub phase: String,
637    /// Target block for synchronization, if syncing.
638    pub target: Option<RpcNodeStatusTransitionFrontierBlockSummary>,
639}
640
641/// Summary of a block in the transition frontier.
642#[derive(Serialize, Deserialize, Debug, Clone)]
643#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
644pub struct RpcNodeStatusTransitionFrontierBlockSummary {
645    /// State hash of the block.
646    pub hash: StateHash,
647    /// Block height.
648    pub height: u32,
649    /// Global slot number.
650    pub global_slot: u32,
651}
652
653/// Transaction pool statistics.
654#[derive(Serialize, Deserialize, Debug, Default, Clone)]
655#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
656pub struct RpcNodeStatusTransactionPool {
657    /// Total number of transactions in the pool.
658    pub transactions: usize,
659    /// Number of transactions ready for propagation.
660    pub transactions_for_propagation: usize,
661    /// Number of transaction candidates being processed.
662    pub transaction_candidates: usize,
663}
664
665/// SNARK pool statistics.
666#[derive(Serialize, Deserialize, Debug, Default, Clone)]
667#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
668pub struct RpcNodeStatusSnarkPool {
669    /// Total number of SNARK jobs in the pool.
670    pub total_jobs: usize,
671    /// Number of completed SNARK proofs.
672    pub snarks: usize,
673}
674
675#[derive(Serialize, Deserialize, Debug, Clone)]
676#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
677pub struct RpcBlockProducerStats {
678    pub current_time: redux::Timestamp,
679    pub current_global_slot: Option<u32>,
680    pub current_epoch: Option<u32>,
681    pub epoch_start: Option<u32>,
682    pub epoch_end: Option<u32>,
683    pub public_key: AccountPublicKey,
684    pub attempts: Vec<BlockProductionAttempt>,
685    pub future_won_slots: Vec<BlockProductionAttemptWonSlot>,
686    pub current_epoch_vrf_stats: Option<VrfEvaluatorStats>,
687    pub vrf_stats: BTreeMap<u32, VrfEvaluatorStats>,
688}
689
690#[derive(Serialize, Deserialize, Debug, Clone)]
691pub struct RpcSnarkerConfig {
692    pub public_key: NonZeroCurvePoint,
693    pub fee: CurrencyFeeStableV1,
694}
695
696#[derive(Serialize, Debug, Clone)]
697pub struct RpcSnarkWorker {
698    pub time: Option<Timestamp>,
699    pub id: Option<String>,
700    pub status: RpcSnarkWorkerStatus,
701}
702
703#[derive(Serialize, Deserialize, Debug, Clone)]
704#[serde(tag = "kind")]
705pub enum RpcSnarkWorkerStatus {
706    None,
707    Starting,
708    Idle,
709    Working {
710        job_id: SnarkJobId,
711        summary: JobSummary,
712    },
713    WorkReady {
714        job_id: SnarkJobId,
715    },
716    WorkError {
717        job_id: SnarkJobId,
718        error: ExternalSnarkWorkerWorkError,
719    },
720    Cancelling {
721        job_id: SnarkJobId,
722    },
723    Cancelled {
724        job_id: SnarkJobId,
725    },
726    Error {
727        error: ExternalSnarkWorkerError,
728        permanent: bool,
729    },
730    Killing,
731}
732
733pub type RpcSnarkerWorkersResponse = Vec<RpcSnarkWorker>;
734
735impl From<&MinaTransactionTransactionStableV2> for RpcScanStateSummaryBlockTransactionKind {
736    fn from(value: &MinaTransactionTransactionStableV2) -> Self {
737        match value {
738            MinaTransactionTransactionStableV2::Command(v) => (&**v).into(),
739            MinaTransactionTransactionStableV2::FeeTransfer(_) => Self::FeeTransfer,
740            MinaTransactionTransactionStableV2::Coinbase(_) => Self::Coinbase,
741        }
742    }
743}
744
745impl From<&MinaBaseUserCommandStableV2> for RpcScanStateSummaryBlockTransactionKind {
746    fn from(value: &MinaBaseUserCommandStableV2) -> Self {
747        match value {
748            MinaBaseUserCommandStableV2::SignedCommand(v) => match &v.payload.body {
749                MinaBaseSignedCommandPayloadBodyStableV2::Payment(_) => Self::Payment,
750                MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(_) => {
751                    Self::StakeDelegation
752                }
753            },
754            MinaBaseUserCommandStableV2::ZkappCommand(_) => Self::Zkapp,
755        }
756    }
757}
758
759pub type RpcHealthCheckResponse = Result<(), String>;
760pub type RpcReadinessCheckResponse = Result<(), String>;
761
762pub type RpcDiscoveryRoutingTableResponse = Option<discovery::RpcDiscoveryRoutingTable>;
763pub type RpcDiscoveryBoostrapStatsResponse = Option<P2pNetworkKadBootstrapStats>;
764
765#[derive(Serialize, Deserialize, Debug, Clone)]
766pub enum GetBlockQuery {
767    Hash(StateHash),
768    Height(u32),
769}
770
771pub type RpcGetBlockResponse = Option<AppliedBlock>;
772
773#[derive(Serialize, Deserialize, Debug, Clone)]
774pub struct PooledCommandsQuery<ID> {
775    pub public_key: Option<AccountPublicKey>,
776    pub hashes: Option<Vec<TransactionHash>>,
777    pub ids: Option<Vec<ID>>,
778}
779
780pub type PooledUserCommandsQuery = PooledCommandsQuery<MinaBaseSignedCommandStableV2>;
781pub type PooledZkappsCommandsQuery = PooledCommandsQuery<MinaBaseZkappCommandTStableV1WireStableV1>;
782
783pub mod discovery {
784    use crate::p2p::{
785        libp2p_identity::DecodingError, ConnectionType, P2pNetworkKadBucket, P2pNetworkKadDist,
786        P2pNetworkKadEntry, P2pNetworkKadKey, P2pNetworkKadRoutingTable, PeerId,
787    };
788    use serde::{Deserialize, Serialize};
789
790    #[derive(Serialize, Deserialize, Debug, Clone)]
791    pub struct RpcDiscoveryRoutingTable {
792        this_key: P2pNetworkKadKey,
793        buckets: Vec<RpcKBucket>,
794    }
795
796    impl TryFrom<&P2pNetworkKadRoutingTable> for RpcDiscoveryRoutingTable {
797        type Error = DecodingError;
798
799        fn try_from(value: &P2pNetworkKadRoutingTable) -> Result<Self, Self::Error> {
800            let mut buckets = Vec::new();
801
802            for (i, b) in value.buckets.iter().enumerate() {
803                buckets.push((b, P2pNetworkKadDist::from(i), &value.this_key).try_into()?);
804            }
805
806            Ok(RpcDiscoveryRoutingTable {
807                this_key: value.this_key,
808                buckets,
809            })
810        }
811    }
812
813    #[derive(Serialize, Deserialize, Debug, Clone)]
814    pub struct RpcKBucket {
815        max_dist: P2pNetworkKadDist,
816        entries: Vec<RpcEntry>,
817    }
818
819    impl<const K: usize>
820        TryFrom<(
821            &P2pNetworkKadBucket<K>,
822            P2pNetworkKadDist,
823            &P2pNetworkKadKey,
824        )> for RpcKBucket
825    {
826        type Error = DecodingError;
827
828        fn try_from(
829            (bucket, max_dist, this_key): (
830                &P2pNetworkKadBucket<K>,
831                P2pNetworkKadDist,
832                &P2pNetworkKadKey,
833            ),
834        ) -> Result<Self, Self::Error> {
835            let mut entries = Vec::new();
836
837            for entry in bucket.iter() {
838                entries.push((entry, this_key).try_into()?);
839            }
840            Ok(RpcKBucket { max_dist, entries })
841        }
842    }
843
844    #[derive(Serialize, Deserialize, Debug, Clone)]
845    pub struct RpcEntry {
846        peer_id: PeerId,
847        libp2p: crate::p2p::libp2p_identity::PeerId,
848        key: P2pNetworkKadKey,
849        dist: P2pNetworkKadDist,
850        addrs: Vec<crate::p2p::multiaddr::Multiaddr>,
851        connection: ConnectionType,
852    }
853
854    impl TryFrom<(&P2pNetworkKadEntry, &P2pNetworkKadKey)> for RpcEntry {
855        type Error = DecodingError;
856
857        fn try_from(
858            (value, this_key): (&P2pNetworkKadEntry, &P2pNetworkKadKey),
859        ) -> Result<Self, Self::Error> {
860            Ok(RpcEntry {
861                peer_id: value.peer_id,
862                libp2p: value.peer_id.try_into()?,
863                key: value.key,
864                dist: this_key.distance(&value.key),
865                addrs: value.addresses().clone(),
866                connection: value.connection,
867            })
868        }
869    }
870}