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::StateSyncInfo;
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 use endpoint::Endpoint;
71
72#[cfg(not(feature = "testing"))]
73mod generated;
74#[cfg(feature = "testing")]
75pub mod generated;
76
77#[cfg(feature = "tonic")]
78mod tonic_client;
79#[cfg(feature = "tonic")]
80pub use tonic_client::GrpcClient;
81
82use crate::rpc::domain::account_vault::AccountVaultInfo;
83use crate::rpc::domain::storage_map::StorageMapInfo;
84use crate::rpc::domain::transaction::TransactionsInfo;
85use crate::store::InputNoteRecord;
86use crate::store::input_note_states::UnverifiedNoteState;
87use crate::transaction::ForeignAccount;
88
89/// Represents the state that we want to retrieve from the network
90pub enum AccountStateAt {
91 /// Gets the latest state, for the current chain tip
92 ChainTip,
93 /// Gets the state at a specific block number
94 Block(BlockNumber),
95}
96
97// RPC ENDPOINT LIMITS
98// ================================================================================================
99
100// TODO: We need a better structured way of getting limits as defined by the node (#1139)
101pub const NOTE_IDS_LIMIT: usize = 100;
102pub const NULLIFIER_PREFIXES_LIMIT: usize = 1000;
103pub const ACCOUNT_ID_LIMIT: usize = 1000;
104pub const NOTE_TAG_LIMIT: usize = 1000;
105
106// NODE RPC CLIENT TRAIT
107// ================================================================================================
108
109/// Defines the interface for communicating with the Miden node.
110///
111/// The implementers are responsible for connecting to the Miden node, handling endpoint
112/// requests/responses, and translating responses into domain objects relevant for each of the
113/// endpoints.
114#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
115#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
116pub trait NodeRpcClient: Send + Sync {
117 /// Sets the genesis commitment for the client and reconnects to the node providing the
118 /// genesis commitment in the request headers. If the genesis commitment is already set,
119 /// this method does nothing.
120 async fn set_genesis_commitment(&self, commitment: Word) -> Result<(), RpcError>;
121
122 /// Given a Proven Transaction, send it to the node for it to be included in a future block
123 /// using the `/SubmitProvenTransaction` RPC endpoint.
124 async fn submit_proven_transaction(
125 &self,
126 proven_transaction: ProvenTransaction,
127 transaction_inputs: TransactionInputs,
128 ) -> Result<BlockNumber, RpcError>;
129
130 /// Given a block number, fetches the block header corresponding to that height from the node
131 /// using the `/GetBlockHeaderByNumber` endpoint.
132 /// If `include_mmr_proof` is set to true and the function returns an `Ok`, the second value
133 /// of the return tuple should always be Some(MmrProof).
134 ///
135 /// When `None` is provided, returns info regarding the latest block.
136 async fn get_block_header_by_number(
137 &self,
138 block_num: Option<BlockNumber>,
139 include_mmr_proof: bool,
140 ) -> Result<(BlockHeader, Option<MmrProof>), RpcError>;
141
142 /// Given a block number, fetches the block corresponding to that height from the node using
143 /// the `/GetBlockByNumber` RPC endpoint.
144 async fn get_block_by_number(&self, block_num: BlockNumber) -> Result<ProvenBlock, RpcError>;
145
146 /// Fetches note-related data for a list of [`NoteId`] using the `/GetNotesById`
147 /// RPC endpoint.
148 ///
149 /// For [`miden_protocol::note::NoteType::Private`] notes, the response includes only the
150 /// [`miden_protocol::note::NoteMetadata`].
151 ///
152 /// For [`miden_protocol::note::NoteType::Public`] notes, the response includes all note details
153 /// (recipient, assets, script, etc.).
154 ///
155 /// In both cases, a [`miden_protocol::note::NoteInclusionProof`] is returned so the caller can
156 /// verify that each note is part of the block's note tree.
157 async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError>;
158
159 /// Fetches info from the node necessary to perform a state sync using the
160 /// `/SyncState` RPC endpoint.
161 ///
162 /// - `block_num` is the last block number known by the client. The returned [`StateSyncInfo`]
163 /// should contain data starting from the next block, until the first block which contains a
164 /// note of matching the requested tag, or the chain tip if there are no notes.
165 /// - `account_ids` is a list of account IDs and determines the accounts the client is
166 /// interested in and should receive account updates of.
167 /// - `note_tags` is a list of tags used to filter the notes the client is interested in, which
168 /// serves as a "note group" filter. Notice that you can't filter by a specific note ID.
169 /// - `nullifiers_tags` similar to `note_tags`, is a list of tags used to filter the nullifiers
170 /// corresponding to some notes the client is interested in.
171 async fn sync_state(
172 &self,
173 block_num: BlockNumber,
174 account_ids: &[AccountId],
175 note_tags: &BTreeSet<NoteTag>,
176 ) -> Result<StateSyncInfo, RpcError>;
177
178 /// Fetches the current state of an account from the node using the `/GetAccountDetails` RPC
179 /// endpoint.
180 ///
181 /// - `account_id` is the ID of the wanted account.
182 async fn get_account_details(&self, account_id: AccountId) -> Result<FetchedAccount, RpcError>;
183
184 /// Fetches the notes related to the specified tags using the `/SyncNotes` RPC endpoint.
185 ///
186 /// - `block_num` is the last block number known by the client.
187 /// - `note_tags` is a list of tags used to filter the notes the client is interested in.
188 async fn sync_notes(
189 &self,
190 block_num: BlockNumber,
191 block_to: Option<BlockNumber>,
192 note_tags: &BTreeSet<NoteTag>,
193 ) -> Result<NoteSyncInfo, RpcError>;
194
195 /// Fetches the nullifiers corresponding to a list of prefixes using the
196 /// `/SyncNullifiers` RPC endpoint.
197 ///
198 /// - `prefix` is a list of nullifiers prefixes to search for.
199 /// - `block_num` is the block number to start the search from. Nullifiers created in this block
200 /// or the following blocks will be included.
201 /// - `block_to` is the optional block number to stop the search at. If not provided, syncs up
202 /// to the network chain tip.
203 async fn sync_nullifiers(
204 &self,
205 prefix: &[u16],
206 block_num: BlockNumber,
207 block_to: Option<BlockNumber>,
208 ) -> Result<Vec<NullifierUpdate>, RpcError>;
209
210 /// Fetches the nullifier proofs corresponding to a list of nullifiers using the
211 /// `/CheckNullifiers` RPC endpoint.
212 async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result<Vec<SmtProof>, RpcError>;
213
214 /// Fetches the account data needed to perform a Foreign Procedure Invocation (FPI) on the
215 /// specified foreign account, using the `GetAccountProof` endpoint.
216 ///
217 /// The `account_state` parameter specifies the block number from which to retrieve
218 /// the account proof from (the state of the account at that block).
219 ///
220 /// The `known_account_code` parameter is the known code commitment
221 /// to prevent unnecessary data fetching. Returns the block number and the FPI account data. If
222 /// the tracked account is not found in the node, the method will return an error.
223 async fn get_account(
224 &self,
225 foreign_account: ForeignAccount,
226 account_state: AccountStateAt,
227 known_account_code: Option<AccountCode>,
228 ) -> Result<(BlockNumber, AccountProof), RpcError>;
229
230 /// Fetches the commit height where the nullifier was consumed. If the nullifier isn't found,
231 /// then `None` is returned.
232 /// The `block_num` parameter is the block number to start the search from.
233 ///
234 /// The default implementation of this method uses
235 /// [`NodeRpcClient::sync_nullifiers`].
236 async fn get_nullifier_commit_heights(
237 &self,
238 requested_nullifiers: BTreeSet<Nullifier>,
239 block_from: BlockNumber,
240 ) -> Result<BTreeMap<Nullifier, Option<BlockNumber>>, RpcError> {
241 let prefixes: Vec<u16> =
242 requested_nullifiers.iter().map(crate::note::Nullifier::prefix).collect();
243 let retrieved_nullifiers = self.sync_nullifiers(&prefixes, block_from, None).await?;
244
245 let mut nullifiers_height = BTreeMap::new();
246 for nullifier in requested_nullifiers {
247 if let Some(update) =
248 retrieved_nullifiers.iter().find(|update| update.nullifier == nullifier)
249 {
250 nullifiers_height.insert(nullifier, Some(update.block_num));
251 } else {
252 nullifiers_height.insert(nullifier, None);
253 }
254 }
255
256 Ok(nullifiers_height)
257 }
258
259 /// Fetches public note-related data for a list of [`NoteId`] and builds [`InputNoteRecord`]s
260 /// with it. If a note is not found or it's private, it is ignored and will not be included
261 /// in the returned list.
262 ///
263 /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
264 async fn get_public_note_records(
265 &self,
266 note_ids: &[NoteId],
267 current_timestamp: Option<u64>,
268 ) -> Result<Vec<InputNoteRecord>, RpcError> {
269 if note_ids.is_empty() {
270 return Ok(vec![]);
271 }
272
273 let mut public_notes = Vec::with_capacity(note_ids.len());
274 let note_details = self.get_notes_by_id(note_ids).await?;
275
276 for detail in note_details {
277 if let FetchedNote::Public(note, inclusion_proof) = detail {
278 let state = UnverifiedNoteState {
279 metadata: note.metadata().clone(),
280 inclusion_proof,
281 }
282 .into();
283 let note = InputNoteRecord::new(note.into(), current_timestamp, state);
284
285 public_notes.push(note);
286 }
287 }
288
289 Ok(public_notes)
290 }
291
292 /// Fetches the public accounts that have been updated since the last known state of the
293 /// accounts.
294 ///
295 /// The `local_accounts` parameter is a list of account headers that the client has
296 /// stored locally and that it wants to check for updates. If an account is private or didn't
297 /// change, it is ignored and will not be included in the returned list.
298 /// The default implementation of this method uses [`NodeRpcClient::get_account_details`].
299 async fn get_updated_public_accounts(
300 &self,
301 local_accounts: &[&AccountHeader],
302 ) -> Result<Vec<Account>, RpcError> {
303 let mut public_accounts = vec![];
304
305 for local_account in local_accounts {
306 let response = self.get_account_details(local_account.id()).await?;
307
308 if let FetchedAccount::Public(account, _) = response {
309 let account = *account;
310 // We should only return an account if it's newer, otherwise we ignore it
311 if account.nonce().as_int() > local_account.nonce().as_int() {
312 public_accounts.push(account);
313 }
314 }
315 }
316
317 Ok(public_accounts)
318 }
319
320 /// Given a block number, fetches the block header corresponding to that height from the node
321 /// along with the MMR proof.
322 ///
323 /// The default implementation of this method uses
324 /// [`NodeRpcClient::get_block_header_by_number`].
325 async fn get_block_header_with_proof(
326 &self,
327 block_num: BlockNumber,
328 ) -> Result<(BlockHeader, MmrProof), RpcError> {
329 let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?;
330 Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?))
331 }
332
333 /// Fetches the note with the specified ID.
334 ///
335 /// The default implementation of this method uses [`NodeRpcClient::get_notes_by_id`].
336 ///
337 /// Errors:
338 /// - [`RpcError::NoteNotFound`] if the note with the specified ID is not found.
339 async fn get_note_by_id(&self, note_id: NoteId) -> Result<FetchedNote, RpcError> {
340 let notes = self.get_notes_by_id(&[note_id]).await?;
341 notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id))
342 }
343
344 /// Fetches the note script with the specified root.
345 ///
346 /// Errors:
347 /// - [`RpcError::ExpectedDataMissing`] if the note with the specified root is not found.
348 async fn get_note_script_by_root(&self, root: Word) -> Result<NoteScript, RpcError>;
349
350 /// Fetches storage map updates for specified account and storage slots within a block range,
351 /// using the `/SyncStorageMaps` RPC endpoint.
352 ///
353 /// - `block_from`: The starting block number for the range.
354 /// - `block_to`: The ending block number for the range.
355 /// - `account_id`: The account ID for which to fetch storage map updates.
356 async fn sync_storage_maps(
357 &self,
358 block_from: BlockNumber,
359 block_to: Option<BlockNumber>,
360 account_id: AccountId,
361 ) -> Result<StorageMapInfo, RpcError>;
362
363 /// Fetches account vault updates for specified account within a block range,
364 /// using the `/SyncAccountVault` RPC endpoint.
365 ///
366 /// - `block_from`: The starting block number for the range.
367 /// - `block_to`: The ending block number for the range.
368 /// - `account_id`: The account ID for which to fetch storage map updates.
369 async fn sync_account_vault(
370 &self,
371 block_from: BlockNumber,
372 block_to: Option<BlockNumber>,
373 account_id: AccountId,
374 ) -> Result<AccountVaultInfo, RpcError>;
375
376 /// Fetches transactions records for specific accounts within a block range.
377 /// Using the `/SyncTransactions` RPC endpoint.
378 ///
379 /// - `block_from`: The starting block number for the range.
380 /// - `block_to`: The ending block number for the range.
381 /// - `account_ids`: The account IDs for which to fetch storage map updates.
382 async fn sync_transactions(
383 &self,
384 block_from: BlockNumber,
385 block_to: Option<BlockNumber>,
386 account_ids: Vec<AccountId>,
387 ) -> Result<TransactionsInfo, RpcError>;
388
389 /// Fetches the network ID of the node.
390 /// Errors:
391 /// - [`RpcError::ExpectedDataMissing`] if the note with the specified root is not found.
392 async fn get_network_id(&self) -> Result<NetworkId, RpcError>;
393}
394
395// RPC API ENDPOINT
396// ================================================================================================
397//
398/// RPC methods for the Miden protocol.
399#[derive(Debug)]
400pub enum NodeRpcClientEndpoint {
401 CheckNullifiers,
402 SyncNullifiers,
403 GetAccount,
404 GetAccountStateDelta,
405 GetBlockByNumber,
406 GetBlockHeaderByNumber,
407 GetNotesById,
408 SyncState,
409 SubmitProvenTx,
410 SyncNotes,
411 GetNoteScriptByRoot,
412 SyncStorageMaps,
413 SyncAccountVault,
414 SyncTransactions,
415}
416
417impl fmt::Display for NodeRpcClientEndpoint {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 match self {
420 NodeRpcClientEndpoint::CheckNullifiers => write!(f, "check_nullifiers"),
421 NodeRpcClientEndpoint::SyncNullifiers => {
422 write!(f, "sync_nullifiers")
423 },
424 NodeRpcClientEndpoint::GetAccount => write!(f, "get_account"),
425 NodeRpcClientEndpoint::GetAccountStateDelta => write!(f, "get_account_state_delta"),
426 NodeRpcClientEndpoint::GetBlockByNumber => write!(f, "get_block_by_number"),
427 NodeRpcClientEndpoint::GetBlockHeaderByNumber => {
428 write!(f, "get_block_header_by_number")
429 },
430 NodeRpcClientEndpoint::GetNotesById => write!(f, "get_notes_by_id"),
431 NodeRpcClientEndpoint::SyncState => write!(f, "sync_state"),
432 NodeRpcClientEndpoint::SubmitProvenTx => write!(f, "submit_proven_transaction"),
433 NodeRpcClientEndpoint::SyncNotes => write!(f, "sync_notes"),
434 NodeRpcClientEndpoint::GetNoteScriptByRoot => write!(f, "get_note_script_by_root"),
435 NodeRpcClientEndpoint::SyncStorageMaps => write!(f, "sync_storage_maps"),
436 NodeRpcClientEndpoint::SyncAccountVault => write!(f, "sync_account_vault"),
437 NodeRpcClientEndpoint::SyncTransactions => write!(f, "sync_transactions"),
438 }
439 }
440}