node/rpc/
mod.rs

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