Skip to main content

miden_client/rpc/
mod.rs

1//! Provides an interface for the client to communicate with a Miden node using
2//! Remote Procedure Calls (RPC).
3//!
4//! This module defines the [`NodeRpcClient`] trait which abstracts calls to the RPC protocol used
5//! to:
6//!
7//! - Submit proven transactions.
8//! - Retrieve block headers (optionally with MMR proofs).
9//! - Sync state updates (including notes, nullifiers, and account updates).
10//! - Fetch details for specific notes and accounts.
11//!
12//! The client implementation adapts to the target environment automatically:
13//! - Native targets use `tonic` transport with TLS.
14//! - `wasm32` targets use `tonic-web-wasm-client` transport.
15//!
16//! ## Example
17//!
18//! ```no_run
19//! # use miden_client::rpc::{Endpoint, NodeRpcClient, GrpcClient};
20//! # use miden_protocol::block::BlockNumber;
21//! # #[tokio::main]
22//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! // Create a gRPC client instance (assumes default endpoint configuration).
24//! let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291));
25//! let mut rpc_client = GrpcClient::new(&endpoint, 1000);
26//!
27//! // Fetch the latest block header (by passing None).
28//! let (block_header, mmr_proof) = rpc_client.get_block_header_by_number(None, true).await?;
29//!
30//! println!("Latest block number: {}", block_header.block_num());
31//! if let Some(proof) = mmr_proof {
32//!     println!("MMR proof received accordingly");
33//! }
34//!
35//! #    Ok(())
36//! # }
37//! ```
38//! The client also makes use of this component in order to communicate with the node.
39//!
40//! For further details and examples, see the documentation for the individual methods in the
41//! [`NodeRpcClient`] trait.
42
43use alloc::boxed::Box;
44use alloc::collections::{BTreeMap, BTreeSet};
45use alloc::string::String;
46use alloc::vec::Vec;
47use core::fmt;
48
49use domain::account::{AccountProof, FetchedAccount};
50use domain::note::{FetchedNote, NoteSyncInfo};
51use domain::nullifier::NullifierUpdate;
52use domain::sync::ChainMmrInfo;
53use miden_protocol::Word;
54use miden_protocol::account::{Account, AccountCode, AccountHeader, AccountId};
55use miden_protocol::address::NetworkId;
56use miden_protocol::block::{BlockHeader, BlockNumber, ProvenBlock};
57use miden_protocol::crypto::merkle::mmr::MmrProof;
58use miden_protocol::crypto::merkle::smt::SmtProof;
59use miden_protocol::note::{NoteId, NoteScript, NoteTag, Nullifier};
60use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
61
62/// Contains domain types related to RPC requests and responses, as well as utility functions
63/// for dealing with them.
64pub mod domain;
65
66mod errors;
67pub use errors::*;
68
69mod endpoint;
70pub(crate) use domain::limits::RPC_LIMITS_STORE_SETTING;
71pub use domain::limits::RpcLimits;
72pub use domain::status::RpcStatusInfo;
73pub use endpoint::Endpoint;
74
75#[cfg(not(feature = "testing"))]
76mod generated;
77#[cfg(feature = "testing")]
78pub mod generated;
79
80#[cfg(feature = "tonic")]
81mod tonic_client;
82#[cfg(feature = "tonic")]
83pub use tonic_client::GrpcClient;
84
85use crate::rpc::domain::account::AccountStorageRequirements;
86use crate::rpc::domain::account_vault::AccountVaultInfo;
87use crate::rpc::domain::storage_map::StorageMapInfo;
88use crate::rpc::domain::transaction::TransactionsInfo;
89use crate::store::InputNoteRecord;
90use crate::store::input_note_states::UnverifiedNoteState;
91
92/// Represents the state that we want to retrieve from the network
93pub enum AccountStateAt {
94    /// Gets the latest state, for the current chain tip
95    ChainTip,
96    /// Gets the state at a specific block number
97    Block(BlockNumber),
98}
99
100// NODE RPC CLIENT TRAIT
101// ================================================================================================
102
103/// Defines the interface for communicating with the Miden node.
104///
105/// The implementers are responsible for connecting to the Miden node, handling endpoint
106/// requests/responses, and translating responses into domain objects relevant for each of the
107/// endpoints.
108#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
109#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
110pub trait NodeRpcClient: Send + Sync {
111    /// Sets the genesis commitment for the client and reconnects to the node providing the
112    /// genesis commitment in the request headers. If the genesis commitment is already set,
113    /// this method does nothing.
114    async fn set_genesis_commitment(&self, commitment: Word) -> Result<(), RpcError>;
115
116    /// Returns the genesis commitment if it has been set, without fetching from the node.
117    fn has_genesis_commitment(&self) -> Option<Word>;
118
119    /// Given a Proven Transaction, send it to the node for it to be included in a future block
120    /// using the `/SubmitProvenTransaction` RPC endpoint.
121    async fn submit_proven_transaction(
122        &self,
123        proven_transaction: ProvenTransaction,
124        transaction_inputs: TransactionInputs,
125    ) -> Result<BlockNumber, RpcError>;
126
127    /// Given a block number, fetches the block header corresponding to that height from the node
128    /// using the `/GetBlockHeaderByNumber` endpoint.
129    /// If `include_mmr_proof` is set to true and the function returns an `Ok`, the second value
130    /// of the return tuple should always be Some(MmrProof).
131    ///
132    /// When `None` is provided, returns info regarding the latest block.
133    async fn get_block_header_by_number(
134        &self,
135        block_num: Option<BlockNumber>,
136        include_mmr_proof: bool,
137    ) -> Result<(BlockHeader, Option<MmrProof>), RpcError>;
138
139    /// Given a block number, fetches the block corresponding to that height from the node using
140    /// the `/GetBlockByNumber` RPC endpoint.
141    async fn get_block_by_number(&self, block_num: BlockNumber) -> Result<ProvenBlock, RpcError>;
142
143    /// Fetches note-related data for a list of [`NoteId`] using the `/GetNotesById`
144    /// RPC endpoint.
145    ///
146    /// For [`miden_protocol::note::NoteType::Private`] notes, the response includes only the
147    /// [`miden_protocol::note::NoteMetadata`].
148    ///
149    /// For [`miden_protocol::note::NoteType::Public`] notes, the response includes all note details
150    /// (recipient, assets, script, etc.).
151    ///
152    /// In both cases, a [`miden_protocol::note::NoteInclusionProof`] is returned so the caller can
153    /// verify that each note is part of the block's note tree.
154    async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError>;
155
156    /// Fetches the MMR delta for a given block range using the `/SyncChainMmr` RPC endpoint.
157    ///
158    /// - `block_from` is the last block number already present in the caller's MMR.
159    /// - `block_to` is the optional upper bound of the range. If `None`, syncs up to the chain tip.
160    async fn sync_chain_mmr(
161        &self,
162        block_from: BlockNumber,
163        block_to: Option<BlockNumber>,
164    ) -> Result<ChainMmrInfo, RpcError>;
165
166    /// Fetches the current state of an account from the node using the `/GetAccountDetails` RPC
167    /// endpoint.
168    ///
169    /// - `account_id` is the ID of the wanted account.
170    async fn get_account_details(&self, account_id: AccountId) -> Result<FetchedAccount, RpcError>;
171
172    /// Fetches the notes related to the specified tags using the `/SyncNotes` RPC endpoint.
173    ///
174    /// - `block_num` is the last block number known by the client.
175    /// - `note_tags` is a list of tags used to filter the notes the client is interested in.
176    async fn sync_notes(
177        &self,
178        block_num: BlockNumber,
179        block_to: Option<BlockNumber>,
180        note_tags: &BTreeSet<NoteTag>,
181    ) -> Result<NoteSyncInfo, RpcError>;
182
183    /// Fetches the nullifiers corresponding to a list of prefixes using the
184    /// `/SyncNullifiers` RPC endpoint.
185    ///
186    /// - `prefix` is a list of nullifiers prefixes to search for.
187    /// - `block_num` is the block number to start the search from. Nullifiers created in this block
188    ///   or the following blocks will be included.
189    /// - `block_to` is the optional block number to stop the search at. If not provided, syncs up
190    ///   to the network chain tip.
191    async fn sync_nullifiers(
192        &self,
193        prefix: &[u16],
194        block_num: BlockNumber,
195        block_to: Option<BlockNumber>,
196    ) -> Result<Vec<NullifierUpdate>, RpcError>;
197
198    /// Fetches the nullifier proofs corresponding to a list of nullifiers using the
199    /// `/CheckNullifiers` RPC endpoint.
200    async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result<Vec<SmtProof>, RpcError>;
201
202    /// Fetches the account proof and optionally its details from the node, using the
203    /// `GetAccountProof` endpoint.
204    ///
205    /// The `account_state` parameter specifies the block number from which to retrieve
206    /// the account proof from (the state of the account at that block).
207    ///
208    /// The `storage_requirements` parameter specifies which storage slots and map keys
209    /// should be included in the response for public accounts.
210    ///
211    /// The `known_account_code` parameter is the known code commitment
212    /// to prevent unnecessary data fetching. Returns the block number and the account proof. If
213    /// the account is not found in the node, the method will return an error.
214    async fn get_account_proof(
215        &self,
216        account_id: AccountId,
217        storage_requirements: AccountStorageRequirements,
218        account_state: AccountStateAt,
219        known_account_code: Option<AccountCode>,
220    ) -> Result<(BlockNumber, AccountProof), RpcError>;
221
222    /// Fetches the commit height where the nullifier was consumed. If the nullifier isn't found,
223    /// then `None` is returned.
224    /// The `block_num` parameter is the block number to start the search from.
225    ///
226    /// The default implementation of this method uses
227    /// [`NodeRpcClient::sync_nullifiers`].
228    async fn get_nullifier_commit_heights(
229        &self,
230        requested_nullifiers: BTreeSet<Nullifier>,
231        block_from: BlockNumber,
232    ) -> Result<BTreeMap<Nullifier, Option<BlockNumber>>, RpcError> {
233        let prefixes: Vec<u16> =
234            requested_nullifiers.iter().map(crate::note::Nullifier::prefix).collect();
235        let retrieved_nullifiers = self.sync_nullifiers(&prefixes, block_from, None).await?;
236
237        let mut nullifiers_height = BTreeMap::new();
238        for nullifier in requested_nullifiers {
239            if let Some(update) =
240                retrieved_nullifiers.iter().find(|update| update.nullifier == nullifier)
241            {
242                nullifiers_height.insert(nullifier, Some(update.block_num));
243            } else {
244                nullifiers_height.insert(nullifier, None);
245            }
246        }
247
248        Ok(nullifiers_height)
249    }
250
251    /// Fetches public note-related data for a list of [`NoteId`] and builds [`InputNoteRecord`]s
252    /// with it. If a note is not found or it's private, it is ignored and will not be included
253    /// in the returned list.
254    ///
255    /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
256    async fn get_public_note_records(
257        &self,
258        note_ids: &[NoteId],
259        current_timestamp: Option<u64>,
260    ) -> Result<Vec<InputNoteRecord>, RpcError> {
261        if note_ids.is_empty() {
262            return Ok(vec![]);
263        }
264
265        let mut public_notes = Vec::with_capacity(note_ids.len());
266        let note_details = self.get_notes_by_id(note_ids).await?;
267
268        for detail in note_details {
269            if let FetchedNote::Public(note, inclusion_proof) = detail {
270                let state = UnverifiedNoteState {
271                    metadata: note.metadata().clone(),
272                    inclusion_proof,
273                }
274                .into();
275                let note = InputNoteRecord::new(note.into(), current_timestamp, state);
276
277                public_notes.push(note);
278            }
279        }
280
281        Ok(public_notes)
282    }
283
284    /// Fetches the public accounts that have been updated since the last known state of the
285    /// accounts.
286    ///
287    /// The `local_accounts` parameter is a list of account headers that the client has
288    /// stored locally and that it wants to check for updates. If an account is private or didn't
289    /// change, it is ignored and will not be included in the returned list.
290    /// The default implementation of this method uses [`NodeRpcClient::get_account_details`].
291    async fn get_updated_public_accounts(
292        &self,
293        local_accounts: &[&AccountHeader],
294    ) -> Result<Vec<Account>, RpcError> {
295        let mut public_accounts = vec![];
296
297        for local_account in local_accounts {
298            let response = self.get_account_details(local_account.id()).await?;
299
300            if let FetchedAccount::Public(account, _) = response {
301                let account = *account;
302                // We should only return an account if it's newer, otherwise we ignore it
303                if account.nonce().as_int() > local_account.nonce().as_int() {
304                    public_accounts.push(account);
305                }
306            }
307        }
308
309        Ok(public_accounts)
310    }
311
312    /// Given a block number, fetches the block header corresponding to that height from the node
313    /// along with the MMR proof.
314    ///
315    /// The default implementation of this method uses
316    /// [`NodeRpcClient::get_block_header_by_number`].
317    async fn get_block_header_with_proof(
318        &self,
319        block_num: BlockNumber,
320    ) -> Result<(BlockHeader, MmrProof), RpcError> {
321        let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?;
322        Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?))
323    }
324
325    /// Fetches the note with the specified ID.
326    ///
327    /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
328    ///
329    /// Errors:
330    /// - [`RpcError::NoteNotFound`] if the note with the specified ID is not found.
331    async fn get_note_by_id(&self, note_id: NoteId) -> Result<FetchedNote, RpcError> {
332        let notes = self.get_notes_by_id(&[note_id]).await?;
333        notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id))
334    }
335
336    /// Fetches the note script with the specified root.
337    ///
338    /// Errors:
339    /// - [`RpcError::ExpectedDataMissing`] if the note with the specified root is not found.
340    async fn get_note_script_by_root(&self, root: Word) -> Result<NoteScript, RpcError>;
341
342    /// Fetches storage map updates for specified account and storage slots within a block range,
343    /// using the `/SyncStorageMaps` RPC endpoint.
344    ///
345    /// - `block_from`: The starting block number for the range.
346    /// - `block_to`: The ending block number for the range.
347    /// - `account_id`: The account ID for which to fetch storage map updates.
348    async fn sync_storage_maps(
349        &self,
350        block_from: BlockNumber,
351        block_to: Option<BlockNumber>,
352        account_id: AccountId,
353    ) -> Result<StorageMapInfo, RpcError>;
354
355    /// Fetches account vault updates for specified account within a block range,
356    /// using the `/SyncAccountVault` RPC endpoint.
357    ///
358    /// - `block_from`: The starting block number for the range.
359    /// - `block_to`: The ending block number for the range.
360    /// - `account_id`: The account ID for which to fetch storage map updates.
361    async fn sync_account_vault(
362        &self,
363        block_from: BlockNumber,
364        block_to: Option<BlockNumber>,
365        account_id: AccountId,
366    ) -> Result<AccountVaultInfo, RpcError>;
367
368    /// Fetches transactions records for specific accounts within a block range.
369    /// Using the `/SyncTransactions` RPC endpoint.
370    ///
371    /// - `block_from`: The starting block number for the range.
372    /// - `block_to`: The ending block number for the range.
373    /// - `account_ids`: The account IDs for which to fetch storage map updates.
374    async fn sync_transactions(
375        &self,
376        block_from: BlockNumber,
377        block_to: Option<BlockNumber>,
378        account_ids: Vec<AccountId>,
379    ) -> Result<TransactionsInfo, RpcError>;
380
381    /// Fetches the network ID of the node.
382    /// Errors:
383    /// - [`RpcError::ExpectedDataMissing`] if the note with the specified root is not found.
384    async fn get_network_id(&self) -> Result<NetworkId, RpcError>;
385
386    /// Fetches the RPC limits configured on the node.
387    ///
388    /// Implementations may cache the result internally to avoid repeated network calls.
389    async fn get_rpc_limits(&self) -> Result<RpcLimits, RpcError>;
390
391    /// Returns the RPC limits if they have been set, without fetching from the node.
392    fn has_rpc_limits(&self) -> Option<RpcLimits>;
393
394    /// Sets the RPC limits internally to be used by the client.
395    async fn set_rpc_limits(&self, limits: RpcLimits);
396
397    /// Fetches the RPC status without requiring Accept header validation.
398    ///
399    /// This is useful for diagnostics when version negotiation fails, as it allows
400    /// retrieving node information even when there's a version mismatch.
401    async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError>;
402}
403
404// RPC API ENDPOINT
405// ================================================================================================
406//
407/// RPC methods for the Miden protocol.
408#[derive(Debug, Clone, Copy)]
409pub enum RpcEndpoint {
410    Status,
411    CheckNullifiers,
412    SyncNullifiers,
413    GetAccount,
414    GetBlockByNumber,
415    GetBlockHeaderByNumber,
416    GetNotesById,
417    SyncChainMmr,
418    SubmitProvenTx,
419    SyncNotes,
420    GetNoteScriptByRoot,
421    SyncStorageMaps,
422    SyncAccountVault,
423    SyncTransactions,
424    GetLimits,
425}
426
427impl RpcEndpoint {
428    /// Returns the endpoint name as used in the RPC service definition.
429    pub fn proto_name(&self) -> &'static str {
430        match self {
431            RpcEndpoint::Status => "Status",
432            RpcEndpoint::CheckNullifiers => "CheckNullifiers",
433            RpcEndpoint::SyncNullifiers => "SyncNullifiers",
434            RpcEndpoint::GetAccount => "GetAccount",
435            RpcEndpoint::GetBlockByNumber => "GetBlockByNumber",
436            RpcEndpoint::GetBlockHeaderByNumber => "GetBlockHeaderByNumber",
437            RpcEndpoint::GetNotesById => "GetNotesById",
438            RpcEndpoint::SyncChainMmr => "SyncChainMmr",
439            RpcEndpoint::SubmitProvenTx => "SubmitProvenTransaction",
440            RpcEndpoint::SyncNotes => "SyncNotes",
441            RpcEndpoint::GetNoteScriptByRoot => "GetNoteScriptByRoot",
442            RpcEndpoint::SyncStorageMaps => "SyncStorageMaps",
443            RpcEndpoint::SyncAccountVault => "SyncAccountVault",
444            RpcEndpoint::SyncTransactions => "SyncTransactions",
445            RpcEndpoint::GetLimits => "GetLimits",
446        }
447    }
448}
449
450impl fmt::Display for RpcEndpoint {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        match self {
453            RpcEndpoint::Status => write!(f, "status"),
454            RpcEndpoint::CheckNullifiers => write!(f, "check_nullifiers"),
455            RpcEndpoint::SyncNullifiers => {
456                write!(f, "sync_nullifiers")
457            },
458            RpcEndpoint::GetAccount => write!(f, "get_account_proof"),
459            RpcEndpoint::GetBlockByNumber => write!(f, "get_block_by_number"),
460            RpcEndpoint::GetBlockHeaderByNumber => {
461                write!(f, "get_block_header_by_number")
462            },
463            RpcEndpoint::GetNotesById => write!(f, "get_notes_by_id"),
464            RpcEndpoint::SyncChainMmr => write!(f, "sync_chain_mmr"),
465            RpcEndpoint::SubmitProvenTx => write!(f, "submit_proven_transaction"),
466            RpcEndpoint::SyncNotes => write!(f, "sync_notes"),
467            RpcEndpoint::GetNoteScriptByRoot => write!(f, "get_note_script_by_root"),
468            RpcEndpoint::SyncStorageMaps => write!(f, "sync_storage_maps"),
469            RpcEndpoint::SyncAccountVault => write!(f, "sync_account_vault"),
470            RpcEndpoint::SyncTransactions => write!(f, "sync_transactions"),
471            RpcEndpoint::GetLimits => write!(f, "get_limits"),
472        }
473    }
474}