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 to:
5//!
6//! - Submit proven transactions.
7//! - Retrieve block headers (optionally with MMR proofs).
8//! - Sync state updates (including notes, nullifiers, and account updates).
9//! - Fetch details for specific notes and accounts.
10//!
11//! In addition, the module provides implementations for different environments (e.g. tonic-based or
12//! web-based) via feature flags ( `tonic` and `web-tonic`).
13//!
14//! ## Example
15//!
16//! ```no_run
17//! # use miden_client::rpc::{NodeRpcClient, TonicRpcClient};
18//! # use miden_objects::block::BlockNumber;
19//! # use miden_client::rpc::Endpoint;
20//! ##[tokio::main]
21//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! // Create a Tonic RPC client instance (assumes default endpoint configuration).
23//! let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291));
24//! let mut rpc_client = TonicRpcClient::new(endpoint, 1000);
25//!
26//! // Fetch the latest block header (by passing None).
27//! let (block_header, mmr_proof) = rpc_client.get_block_header_by_number(None, true).await?;
28//!
29//! println!("Latest block number: {}", block_header.block_num());
30//! if let Some(proof) = mmr_proof {
31//!     println!("MMR proof received accordingly");
32//! }
33//!
34//! #    Ok(())
35//! # }
36//! ```
37//! The client also makes use of this component in order to communicate with the node.
38//!
39//! For further details and examples, see the documentation for the individual methods in the
40//! [NodeRpcClient] trait.
41
42use alloc::{boxed::Box, collections::BTreeSet, string::String, vec::Vec};
43use core::fmt;
44
45use async_trait::async_trait;
46use domain::{
47    account::{AccountDetails, AccountProofs},
48    note::{NetworkNote, NoteSyncInfo},
49    sync::StateSyncInfo,
50};
51use miden_objects::{
52    account::{Account, AccountCode, AccountHeader, AccountId},
53    block::{BlockHeader, BlockNumber},
54    crypto::merkle::MmrProof,
55    note::{NoteId, NoteTag, Nullifier},
56    transaction::ProvenTransaction,
57};
58
59/// Contains domain types related to RPC requests and responses, as well as utility functions
60/// for dealing with them.
61pub mod domain;
62
63mod errors;
64pub use errors::RpcError;
65
66mod endpoint;
67pub use endpoint::Endpoint;
68
69#[cfg(not(test))]
70mod generated;
71#[cfg(test)]
72pub mod generated;
73
74#[cfg(feature = "tonic")]
75mod tonic_client;
76#[cfg(feature = "tonic")]
77pub use tonic_client::TonicRpcClient;
78
79#[cfg(feature = "web-tonic")]
80mod web_tonic_client;
81#[cfg(feature = "web-tonic")]
82pub use web_tonic_client::WebTonicRpcClient;
83
84use crate::{
85    store::{input_note_states::UnverifiedNoteState, InputNoteRecord},
86    sync::get_nullifier_prefix,
87    transaction::ForeignAccount,
88};
89
90// NODE RPC CLIENT TRAIT
91// ================================================================================================
92
93/// Defines the interface for communicating with the Miden node.
94///
95/// The implementers are responsible for connecting to the Miden node, handling endpoint
96/// requests/responses, and translating responses into domain objects relevant for each of the
97/// endpoints.
98#[async_trait(?Send)]
99pub trait NodeRpcClient {
100    /// Given a Proven Transaction, send it to the node for it to be included in a future block
101    /// using the `/SubmitProvenTransaction` RPC endpoint.
102    async fn submit_proven_transaction(
103        &mut self,
104        proven_transaction: ProvenTransaction,
105    ) -> Result<(), RpcError>;
106
107    /// Given a block number, fetches the block header corresponding to that height from the node
108    /// using the `/GetBlockHeaderByNumber` endpoint.
109    /// If `include_mmr_proof` is set to true and the function returns an `Ok`, the second value
110    /// of the return tuple should always be Some(MmrProof).
111    ///
112    /// When `None` is provided, returns info regarding the latest block.
113    async fn get_block_header_by_number(
114        &mut self,
115        block_num: Option<BlockNumber>,
116        include_mmr_proof: bool,
117    ) -> Result<(BlockHeader, Option<MmrProof>), RpcError>;
118
119    /// Fetches note-related data for a list of [NoteId] using the `/GetNotesById` rpc endpoint.
120    ///
121    /// For any NoteType::Private note, the return data is only the
122    /// [miden_objects::note::NoteMetadata], whereas for NoteType::Onchain notes, the return
123    /// data includes all details.
124    async fn get_notes_by_id(&mut self, note_ids: &[NoteId]) -> Result<Vec<NetworkNote>, RpcError>;
125
126    /// Fetches info from the node necessary to perform a state sync using the
127    /// `/SyncState` RPC endpoint.
128    ///
129    /// - `block_num` is the last block number known by the client. The returned [StateSyncInfo]
130    ///   should contain data starting from the next block, until the first block which contains a
131    ///   note of matching the requested tag, or the chain tip if there are no notes.
132    /// - `account_ids` is a list of account IDs and determines the accounts the client is
133    ///   interested in and should receive account updates of.
134    /// - `note_tags` is a list of tags used to filter the notes the client is interested in, which
135    ///   serves as a "note group" filter. Notice that you can't filter by a specific note ID.
136    /// - `nullifiers_tags` similar to `note_tags`, is a list of tags used to filter the nullifiers
137    ///   corresponding to some notes the client is interested in.
138    async fn sync_state(
139        &mut self,
140        block_num: BlockNumber,
141        account_ids: &[AccountId],
142        note_tags: &[NoteTag],
143        nullifiers_tags: &[u16],
144    ) -> Result<StateSyncInfo, RpcError>;
145
146    /// Fetches the current state of an account from the node using the `/GetAccountDetails` RPC
147    /// endpoint.
148    ///
149    /// - `account_id` is the ID of the wanted account.
150    async fn get_account_update(
151        &mut self,
152        account_id: AccountId,
153    ) -> Result<AccountDetails, RpcError>;
154
155    async fn sync_notes(
156        &mut self,
157        block_num: BlockNumber,
158        note_tags: &[NoteTag],
159    ) -> Result<NoteSyncInfo, RpcError>;
160
161    /// Fetches the nullifiers corresponding to a list of prefixes using the
162    /// `/CheckNullifiersByPrefix` RPC endpoint.
163    async fn check_nullifiers_by_prefix(
164        &mut self,
165        prefix: &[u16],
166    ) -> Result<Vec<(Nullifier, u32)>, RpcError>;
167
168    /// Fetches the account data needed to perform a Foreign Procedure Invocation (FPI) on the
169    /// specified foreign accounts, using the `GetAccountProofs` endpoint.
170    ///
171    /// The `code_commitments` parameter is a list of known code hashes
172    /// to prevent unnecessary data fetching. Returns the block number and the FPI account data. If
173    /// one of the tracked accounts is not found in the node, the method will return an error.
174    async fn get_account_proofs(
175        &mut self,
176        account_storage_requests: &BTreeSet<ForeignAccount>,
177        known_account_codes: Vec<AccountCode>,
178    ) -> Result<AccountProofs, RpcError>;
179
180    /// Fetches the commit height where the nullifier was consumed. If the nullifier isn't found,
181    /// then `None` is returned.
182    ///
183    /// The default implementation of this method uses [NodeRpcClient::check_nullifiers_by_prefix].
184    async fn get_nullifier_commit_height(
185        &mut self,
186        nullifier: &Nullifier,
187    ) -> Result<Option<u32>, RpcError> {
188        let nullifiers =
189            self.check_nullifiers_by_prefix(&[get_nullifier_prefix(nullifier)]).await?;
190
191        Ok(nullifiers.iter().find(|(n, _)| n == nullifier).map(|(_, block_num)| *block_num))
192    }
193
194    /// Fetches public note-related data for a list of [NoteId] and builds [InputNoteRecord]s with
195    /// it. If a note is not found or it's private, it is ignored and will not be included in the
196    /// returned list.
197    ///
198    /// The default implementation of this method uses [NodeRpcClient::get_notes_by_id].
199    async fn get_public_note_records(
200        &mut self,
201        note_ids: &[NoteId],
202        current_timestamp: Option<u64>,
203    ) -> Result<Vec<InputNoteRecord>, RpcError> {
204        let note_details = self.get_notes_by_id(note_ids).await?;
205
206        let mut public_notes = vec![];
207        for detail in note_details {
208            if let NetworkNote::Public(note, inclusion_proof) = detail {
209                let state = UnverifiedNoteState {
210                    metadata: *note.metadata(),
211                    inclusion_proof,
212                }
213                .into();
214                let note = InputNoteRecord::new(note.into(), current_timestamp, state);
215
216                public_notes.push(note);
217            }
218        }
219
220        Ok(public_notes)
221    }
222
223    /// Fetches the public accounts that have been updated since the last known state of the
224    /// accounts.
225    ///
226    /// The `local_accounts` parameter is a list of account headers that the client has
227    /// stored locally and that it wants to check for updates. If an account is private or didn't
228    /// change, it is ignored and will not be included in the returned list.
229    /// The default implementation of this method uses [NodeRpcClient::get_account_update].
230    async fn get_updated_public_accounts(
231        &mut self,
232        local_accounts: &[&AccountHeader],
233    ) -> Result<Vec<Account>, RpcError> {
234        let mut public_accounts = vec![];
235
236        for local_account in local_accounts {
237            let response = self.get_account_update(local_account.id()).await?;
238
239            if let AccountDetails::Public(account, _) = response {
240                // We should only return an account if it's newer, otherwise we ignore it
241                if account.nonce().as_int() > local_account.nonce().as_int() {
242                    public_accounts.push(account);
243                }
244            }
245        }
246
247        Ok(public_accounts)
248    }
249
250    /// Given a block number, fetches the block header corresponding to that height from the node
251    /// along with the MMR proof.
252    ///
253    /// The default implementation of this method uses [NodeRpcClient::get_block_header_by_number].
254    async fn get_block_header_with_proof(
255        &mut self,
256        block_num: BlockNumber,
257    ) -> Result<(BlockHeader, MmrProof), RpcError> {
258        let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?;
259        Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?))
260    }
261
262    /// Fetches the note with the specified ID.
263    ///
264    /// The default implementation of this method uses [NodeRpcClient::get_notes_by_id].
265    ///
266    /// Errors:
267    /// - [RpcError::NoteNotFound] if the note with the specified ID is not found.
268    async fn get_note_by_id(&mut self, note_id: NoteId) -> Result<NetworkNote, RpcError> {
269        let notes = self.get_notes_by_id(&[note_id]).await?;
270        notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id))
271    }
272}
273
274// RPC API ENDPOINT
275// ================================================================================================
276//
277/// RPC methods for the Miden protocol.
278#[derive(Debug)]
279pub enum NodeRpcClientEndpoint {
280    CheckNullifiersByPrefix,
281    GetAccountDetails,
282    GetAccountProofs,
283    GetBlockHeaderByNumber,
284    SyncState,
285    SubmitProvenTx,
286    SyncNotes,
287}
288
289impl fmt::Display for NodeRpcClientEndpoint {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            NodeRpcClientEndpoint::CheckNullifiersByPrefix => {
293                write!(f, "check_nullifiers_by_prefix")
294            },
295            NodeRpcClientEndpoint::GetAccountDetails => write!(f, "get_account_details"),
296            NodeRpcClientEndpoint::GetAccountProofs => write!(f, "get_account_proofs"),
297            NodeRpcClientEndpoint::GetBlockHeaderByNumber => {
298                write!(f, "get_block_header_by_number")
299            },
300            NodeRpcClientEndpoint::SyncState => write!(f, "sync_state"),
301            NodeRpcClientEndpoint::SubmitProvenTx => write!(f, "submit_proven_transaction"),
302            NodeRpcClientEndpoint::SyncNotes => write!(f, "sync_notes"),
303        }
304    }
305}