diff --git a/bin/miden-cli/src/commands/sync.rs b/bin/miden-cli/src/commands/sync.rs index da5b763d8..cc6f1e7b8 100644 --- a/bin/miden-cli/src/commands/sync.rs +++ b/bin/miden-cli/src/commands/sync.rs @@ -12,8 +12,7 @@ impl SyncCmd { let new_details = client.sync_state().await?; println!("State synced to block {}", new_details.block_num); - println!("New public notes: {}", new_details.received_notes.len()); - println!("Tracked notes updated: {}", new_details.committed_notes.len()); + println!("Committed notes: {}", new_details.committed_notes.len()); println!("Tracked notes consumed: {}", new_details.consumed_notes.len()); println!("Tracked accounts updated: {}", new_details.updated_accounts.len()); println!("Locked accounts: {}", new_details.locked_accounts.len()); diff --git a/bin/miden-cli/src/lib.rs b/bin/miden-cli/src/lib.rs index 208281d31..d9f1deebf 100644 --- a/bin/miden-cli/src/lib.rs +++ b/bin/miden-cli/src/lib.rs @@ -112,8 +112,8 @@ impl Cli { let authenticator = ClientAuthenticator::new(rng, keystore.clone()); let client = Client::new( - Box::new(TonicRpcClient::new( - &(cli_config.rpc.endpoint.clone().into()), + Arc::new(TonicRpcClient::new( + &cli_config.rpc.endpoint.clone().into(), cli_config.rpc.timeout_ms, )), rng, diff --git a/bin/miden-cli/src/tests.rs b/bin/miden-cli/src/tests.rs index 618761a63..12ab0dfb4 100644 --- a/bin/miden-cli/src/tests.rs +++ b/bin/miden-cli/src/tests.rs @@ -3,6 +3,7 @@ use std::{ fs::File, io::{Read, Write}, path::{Path, PathBuf}, + sync::Arc, }; use assert_cmd::Command; @@ -497,7 +498,7 @@ fn sync_cli(cli_path: &Path) -> u64 { let updated_notes = String::from_utf8(output.stdout) .unwrap() .split_whitespace() - .skip_while(|&word| word != "updated:") + .skip_while(|&word| word != "notes:") .find(|word| word.parse::().is_ok()) .unwrap() .parse() @@ -657,7 +658,7 @@ async fn create_rust_client_with_store_path(store_path: &Path) -> (TestClient, F let authenticator = ClientAuthenticator::new(rng, keystore.clone()); ( TestClient::new( - Box::new(TonicRpcClient::new(&rpc_config.endpoint.into(), rpc_config.timeout_ms)), + Arc::new(TonicRpcClient::new(&rpc_config.endpoint.into(), rpc_config.timeout_ms)), rng, store, std::sync::Arc::new(authenticator), diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index 83cbb7780..1b48d32ef 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -25,7 +25,7 @@ idxdb = ["dep:base64", "dep:serde-wasm-bindgen", "dep:wasm-bindgen", "dep:wasm-b sqlite = ["dep:rusqlite", "dep:deadpool-sqlite", "std"] std = ["miden-objects/std","miden-proving-service-client/std"] testing = ["miden-objects/testing", "miden-lib/testing", "miden-tx/testing"] -tonic = ["std", "tonic/transport", "tonic/tls", "tonic/tls-native-roots"] +tonic = ["std", "tonic/transport", "tonic/tls", "tonic/tls-native-roots", "dep:tokio"] web-tonic = ["dep:tonic-web-wasm-client"] [dependencies] diff --git a/crates/rust-client/src/builder.rs b/crates/rust-client/src/builder.rs index 66a4f220c..5194c34de 100644 --- a/crates/rust-client/src/builder.rs +++ b/crates/rust-client/src/builder.rs @@ -1,5 +1,4 @@ use alloc::{ - boxed::Box, string::{String, ToString}, sync::Arc, }; @@ -43,7 +42,7 @@ pub struct ClientBuilder { /// An optional RPC endpoint. rpc_endpoint: Option, /// An optional custom RPC client. If provided, this takes precedence over `rpc_endpoint`. - rpc_api: Option>, + rpc_api: Option>, /// The timeout (in milliseconds) used when constructing the RPC client. timeout_ms: u64, /// An optional store provided by the user. @@ -84,7 +83,7 @@ impl ClientBuilder { /// /// This method overrides any previously set RPC endpoint. #[must_use] - pub fn with_rpc(mut self, client: Box) -> Self { + pub fn with_rpc(mut self, client: Arc) -> Self { self.rpc_api = Some(client); self } @@ -168,10 +167,10 @@ impl ClientBuilder { /// - Returns an error if the keystore is not specified or fails to initialize. pub async fn build(self) -> Result, ClientError> { // Determine the RPC client to use. - let rpc_api: Box = if let Some(client) = self.rpc_api { + let rpc_api: Arc = if let Some(client) = self.rpc_api { client } else if let Some(endpoint) = self.rpc_endpoint { - Box::new(TonicRpcClient::new(&endpoint, self.timeout_ms)) + Arc::new(TonicRpcClient::new(&endpoint, self.timeout_ms)) } else { return Err(ClientError::ClientInitializationError( "RPC client or endpoint is required. Call `.with_rpc(...)` or `.with_rpc_client(...)`." diff --git a/crates/rust-client/src/lib.rs b/crates/rust-client/src/lib.rs index 5de8ce9ac..1001b4b08 100644 --- a/crates/rust-client/src/lib.rs +++ b/crates/rust-client/src/lib.rs @@ -83,7 +83,7 @@ //! // Instantiate the client using a Tonic RPC client //! let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291)); //! let client: Client = Client::new( -//! Box::new(TonicRpcClient::new(&endpoint, 10_000)), +//! Arc::new(TonicRpcClient::new(&endpoint, 10_000)), //! rng, //! store, //! Arc::new(authenticator), @@ -102,8 +102,6 @@ #[macro_use] extern crate alloc; -use alloc::boxed::Box; - #[cfg(feature = "std")] extern crate std; @@ -221,7 +219,7 @@ pub struct Client { rng: R, /// An instance of [`NodeRpcClient`] which provides a way for the client to connect to the /// Miden node. - rpc_api: Box, + rpc_api: Arc, /// An instance of a [`LocalTransactionProver`] which will be the default prover for the /// client. tx_prover: Arc, @@ -257,7 +255,7 @@ impl Client { /// /// Returns an error if the client couldn't be instantiated. pub fn new( - rpc_api: Box, + rpc_api: Arc, rng: R, store: Arc, authenticator: Arc, @@ -298,7 +296,7 @@ impl Client { // -------------------------------------------------------------------------------------------- #[cfg(any(test, feature = "testing"))] - pub fn test_rpc_api(&mut self) -> &mut Box { + pub fn test_rpc_api(&mut self) -> &mut Arc { &mut self.rpc_api } diff --git a/crates/rust-client/src/mock.rs b/crates/rust-client/src/mock.rs index 45f2b3f4c..7baee0d3b 100644 --- a/crates/rust-client/src/mock.rs +++ b/crates/rust-client/src/mock.rs @@ -199,7 +199,7 @@ use alloc::boxed::Box; #[async_trait(?Send)] impl NodeRpcClient for MockRpcApi { async fn sync_notes( - &mut self, + &self, _block_num: BlockNumber, _note_tags: &[NoteTag], ) -> Result { @@ -215,7 +215,7 @@ impl NodeRpcClient for MockRpcApi { /// Executes the specified sync state request and returns the response. async fn sync_state( - &mut self, + &self, block_num: BlockNumber, _account_ids: &[AccountId], _note_tags: &[NoteTag], @@ -229,7 +229,7 @@ impl NodeRpcClient for MockRpcApi { /// Creates and executes a [GetBlockHeaderByNumberRequest]. /// Only used for retrieving genesis block right now so that's the only case we need to cover. async fn get_block_header_by_number( - &mut self, + &self, block_num: Option, include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError> { @@ -251,7 +251,7 @@ impl NodeRpcClient for MockRpcApi { Ok((block.header().clone(), mmr_proof)) } - async fn get_notes_by_id(&mut self, note_ids: &[NoteId]) -> Result, RpcError> { + async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result, RpcError> { // assume all private notes for now let hit_notes = note_ids.iter().filter_map(|id| self.notes.get(id)); let mut return_notes = vec![]; @@ -266,7 +266,7 @@ impl NodeRpcClient for MockRpcApi { } async fn submit_proven_transaction( - &mut self, + &self, _proven_transaction: ProvenTransaction, ) -> std::result::Result<(), RpcError> { // TODO: add some basic validations to test error cases @@ -274,15 +274,15 @@ impl NodeRpcClient for MockRpcApi { } async fn get_account_details( - &mut self, + &self, _account_id: AccountId, ) -> Result { - unimplemented!("shouldn't be used for now") + panic!("shouldn't be used for now") } async fn get_account_proofs( - &mut self, - _account_ids: &BTreeSet, + &self, + _: &BTreeSet, _code_commitments: Vec, ) -> Result { // TODO: Implement fully @@ -290,7 +290,7 @@ impl NodeRpcClient for MockRpcApi { } async fn check_nullifiers_by_prefix( - &mut self, + &self, _prefix: &[u16], _block_num: BlockNumber, ) -> Result, RpcError> { @@ -298,15 +298,12 @@ impl NodeRpcClient for MockRpcApi { Ok(vec![]) } - async fn check_nullifiers( - &mut self, - _nullifiers: &[Nullifier], - ) -> Result, RpcError> { + async fn check_nullifiers(&self, _nullifiers: &[Nullifier]) -> Result, RpcError> { unimplemented!("shouldn't be used for now") } async fn get_account_state_delta( - &mut self, + &self, _account_id: AccountId, _from_block: BlockNumber, _to_block: BlockNumber, @@ -314,10 +311,7 @@ impl NodeRpcClient for MockRpcApi { unimplemented!("shouldn't be used for now") } - async fn get_block_by_number( - &mut self, - block_num: BlockNumber, - ) -> Result { + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result { let block = self .blocks .iter() @@ -345,9 +339,9 @@ pub async fn create_test_client() -> (MockClient, MockRpcApi, FilesystemKeyStore let authenticator = ClientAuthenticator::new(rng, keystore.clone()); let rpc_api = MockRpcApi::new(); - let boxed_rpc_api = Box::new(rpc_api.clone()); + let arc_rpc_api = Arc::new(rpc_api.clone()); - let client = MockClient::new(boxed_rpc_api, rng, store, Arc::new(authenticator.clone()), true); + let client = MockClient::new(arc_rpc_api, rng, store, Arc::new(authenticator.clone()), true); (client, rpc_api, keystore) } diff --git a/crates/rust-client/src/note/import.rs b/crates/rust-client/src/note/import.rs index d696de2fb..3e6fb824c 100644 --- a/crates/rust-client/src/note/import.rs +++ b/crates/rust-client/src/note/import.rs @@ -94,7 +94,7 @@ impl Client { /// - If the note doesn't exist on the node. /// - If the note exists but is private. async fn import_note_record_by_id( - &mut self, + &self, previous_note: Option, id: NoteId, ) -> Result, ClientError> { @@ -136,7 +136,7 @@ impl Client { /// If the note isn't consumed and it was committed in the past relative to the client, then /// the MMR for the relevant block is fetched from the node and stored. async fn import_note_record_by_proof( - &mut self, + &self, previous_note: Option, note: Note, inclusion_proof: NoteInclusionProof, diff --git a/crates/rust-client/src/note/mod.rs b/crates/rust-client/src/note/mod.rs index f99285bd3..a1ab94a03 100644 --- a/crates/rust-client/src/note/mod.rs +++ b/crates/rust-client/src/note/mod.rs @@ -56,7 +56,11 @@ //! For more details on the API and error handling, see the documentation for the specific functions //! and types in this module. -use alloc::{collections::BTreeSet, string::ToString, vec::Vec}; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::ToString, + vec::Vec, +}; use miden_lib::transaction::TransactionKernel; use miden_objects::{account::AccountId, crypto::rand::FeltRng}; @@ -240,84 +244,70 @@ pub async fn get_input_note_with_id_prefix( // ------------------------------------------------------------------------------------------------ /// Contains note changes to apply to the store. +#[derive(Clone, Debug, Default)] pub struct NoteUpdates { - /// A list of new input notes. - new_input_notes: Vec, - /// A list of new output notes. - new_output_notes: Vec, - /// A list of updated input note records corresponding to locally-tracked input notes. - updated_input_notes: Vec, - /// A list of updated output note records corresponding to locally-tracked output notes. - updated_output_notes: Vec, + /// A map of new and updated input note records to be upserted in the store. + updated_input_notes: BTreeMap, + /// A map of updated output note records to be upserted in the store. + updated_output_notes: BTreeMap, } impl NoteUpdates { /// Creates a [`NoteUpdates`]. pub fn new( - new_input_notes: Vec, - new_output_notes: Vec, - updated_input_notes: Vec, - updated_output_notes: Vec, + updated_input_notes: impl IntoIterator, + updated_output_notes: impl IntoIterator, ) -> Self { Self { - new_input_notes, - new_output_notes, - updated_input_notes, - updated_output_notes, + updated_input_notes: updated_input_notes + .into_iter() + .map(|note| (note.id(), note)) + .collect(), + updated_output_notes: updated_output_notes + .into_iter() + .map(|note| (note.id(), note)) + .collect(), } } - /// Combines two [`NoteUpdates`] into a single one. - #[must_use] - pub fn combine_with(mut self, other: Self) -> Self { - self.new_input_notes.extend(other.new_input_notes); - self.new_output_notes.extend(other.new_output_notes); - self.updated_input_notes.extend(other.updated_input_notes); - self.updated_output_notes.extend(other.updated_output_notes); - - self - } - - /// Returns all new input note records, meant to be tracked by the client. - pub fn new_input_notes(&self) -> &[InputNoteRecord] { - &self.new_input_notes - } - - /// Returns all new output note records, meant to be tracked by the client. - pub fn new_output_notes(&self) -> &[OutputNoteRecord] { - &self.new_output_notes - } - - /// Returns all updated input note records. That is, any input notes that are locally tracked - /// and have been updated. - pub fn updated_input_notes(&self) -> &[InputNoteRecord] { - &self.updated_input_notes + /// Returns all input note records that have been updated. + /// This may include: + /// - New notes that have been created that should be inserted. + /// - Existing tracked notes that should be updated. + pub fn updated_input_notes(&self) -> impl Iterator { + self.updated_input_notes.values() } - /// Returns all updated output note records. That is, any output notes that are locally tracked - /// and have been updated. - pub fn updated_output_notes(&self) -> &[OutputNoteRecord] { - &self.updated_output_notes + /// Returns all output note records that have been updated. + /// This may include: + /// - New notes that have been created that should be inserted. + /// - Existing tracked notes that should be updated. + pub fn updated_output_notes(&self) -> impl Iterator { + self.updated_output_notes.values() } /// Returns whether no new note-related information has been retrieved. pub fn is_empty(&self) -> bool { - self.updated_input_notes.is_empty() - && self.updated_output_notes.is_empty() - && self.new_input_notes.is_empty() - && self.new_output_notes.is_empty() + self.updated_input_notes.is_empty() && self.updated_output_notes.is_empty() + } + + /// Returns any note that has been committed into the chain in this update (either new or + /// already locally tracked) + pub fn committed_input_notes(&self) -> impl Iterator { + self.updated_input_notes.values().filter(|note| note.is_committed()) } - /// Returns the IDs of all notes that have been committed. + /// Returns the IDs of all notes that have been committed in this update. + /// This includes both new notes and tracked expected notes that were committed in this update. pub fn committed_note_ids(&self) -> BTreeSet { let committed_output_note_ids = self .updated_output_notes - .iter() + .values() .filter_map(|note_record| note_record.is_committed().then_some(note_record.id())); let committed_input_note_ids = self .updated_input_notes - .iter() + .values() .filter_map(|note_record| note_record.is_committed().then_some(note_record.id())); committed_input_note_ids @@ -325,16 +315,17 @@ impl NoteUpdates { .collect::>() } - /// Returns the IDs of all notes that have been consumed + /// Returns the IDs of all notes that have been consumed. + /// This includes both notes that have been consumed locally or externally in this update. pub fn consumed_note_ids(&self) -> BTreeSet { let consumed_output_note_ids = self .updated_output_notes - .iter() + .values() .filter_map(|note_record| note_record.is_consumed().then_some(note_record.id())); let consumed_input_note_ids = self .updated_input_notes - .iter() + .values() .filter_map(|note_record| note_record.is_consumed().then_some(note_record.id())); consumed_input_note_ids.chain(consumed_output_note_ids).collect::>() diff --git a/crates/rust-client/src/rpc/mod.rs b/crates/rust-client/src/rpc/mod.rs index 836ef6bfb..1a187f44e 100644 --- a/crates/rust-client/src/rpc/mod.rs +++ b/crates/rust-client/src/rpc/mod.rs @@ -99,7 +99,7 @@ pub trait NodeRpcClient { /// Given a Proven Transaction, send it to the node for it to be included in a future block /// using the `/SubmitProvenTransaction` RPC endpoint. async fn submit_proven_transaction( - &mut self, + &self, proven_transaction: ProvenTransaction, ) -> Result<(), RpcError>; @@ -110,24 +110,21 @@ pub trait NodeRpcClient { /// /// When `None` is provided, returns info regarding the latest block. async fn get_block_header_by_number( - &mut self, + &self, block_num: Option, include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError>; /// Given a block number, fetches the block corresponding to that height from the node using /// the `/GetBlockByNumber` RPC endpoint. - async fn get_block_by_number( - &mut self, - block_num: BlockNumber, - ) -> Result; + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result; /// Fetches note-related data for a list of [NoteId] using the `/GetNotesById` rpc endpoint. /// /// For any NoteType::Private note, the return data is only the /// [miden_objects::note::NoteMetadata], whereas for NoteType::Onchain notes, the return /// data includes all details. - async fn get_notes_by_id(&mut self, note_ids: &[NoteId]) -> Result, RpcError>; + async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result, RpcError>; /// Fetches info from the node necessary to perform a state sync using the /// `/SyncState` RPC endpoint. @@ -142,7 +139,7 @@ pub trait NodeRpcClient { /// - `nullifiers_tags` similar to `note_tags`, is a list of tags used to filter the nullifiers /// corresponding to some notes the client is interested in. async fn sync_state( - &mut self, + &self, block_num: BlockNumber, account_ids: &[AccountId], note_tags: &[NoteTag], @@ -152,17 +149,14 @@ pub trait NodeRpcClient { /// endpoint. /// /// - `account_id` is the ID of the wanted account. - async fn get_account_details( - &mut self, - account_id: AccountId, - ) -> Result; + async fn get_account_details(&self, account_id: AccountId) -> Result; /// Fetches the notes related to the specified tags using the `/SyncNotes` RPC endpoint. /// /// - `block_num` is the last block number known by the client. /// - `note_tags` is a list of tags used to filter the notes the client is interested in. async fn sync_notes( - &mut self, + &self, block_num: BlockNumber, note_tags: &[NoteTag], ) -> Result; @@ -174,17 +168,14 @@ pub trait NodeRpcClient { /// - `block_num` is the block number to start the search from. Nullifiers created in this block /// or the following blocks will be included. async fn check_nullifiers_by_prefix( - &mut self, + &self, prefix: &[u16], block_num: BlockNumber, ) -> Result, RpcError>; /// Fetches the nullifier proofs corresponding to a list of nullifiers using the /// `/CheckNullifiers` RPC endpoint. - async fn check_nullifiers( - &mut self, - nullifiers: &[Nullifier], - ) -> Result, RpcError>; + async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result, RpcError>; /// Fetches the account data needed to perform a Foreign Procedure Invocation (FPI) on the /// specified foreign accounts, using the `GetAccountProofs` endpoint. @@ -193,7 +184,7 @@ pub trait NodeRpcClient { /// to prevent unnecessary data fetching. Returns the block number and the FPI account data. If /// one of the tracked accounts is not found in the node, the method will return an error. async fn get_account_proofs( - &mut self, + &self, account_storage_requests: &BTreeSet, known_account_codes: Vec, ) -> Result; @@ -201,7 +192,7 @@ pub trait NodeRpcClient { /// Fetches the account state delta for the specified account between the specified blocks /// using the `/GetAccountStateDelta` RPC endpoint. async fn get_account_state_delta( - &mut self, + &self, account_id: AccountId, from_block: BlockNumber, to_block: BlockNumber, @@ -213,7 +204,7 @@ pub trait NodeRpcClient { /// /// The default implementation of this method uses [NodeRpcClient::check_nullifiers_by_prefix]. async fn get_nullifier_commit_height( - &mut self, + &self, nullifier: &Nullifier, block_num: BlockNumber, ) -> Result, RpcError> { @@ -231,7 +222,7 @@ pub trait NodeRpcClient { /// /// The default implementation of this method uses [NodeRpcClient::get_notes_by_id]. async fn get_public_note_records( - &mut self, + &self, note_ids: &[NoteId], current_timestamp: Option, ) -> Result, RpcError> { @@ -262,7 +253,7 @@ pub trait NodeRpcClient { /// change, it is ignored and will not be included in the returned list. /// The default implementation of this method uses [NodeRpcClient::get_account_details]. async fn get_updated_public_accounts( - &mut self, + &self, local_accounts: &[&AccountHeader], ) -> Result, RpcError> { let mut public_accounts = vec![]; @@ -286,7 +277,7 @@ pub trait NodeRpcClient { /// /// The default implementation of this method uses [NodeRpcClient::get_block_header_by_number]. async fn get_block_header_with_proof( - &mut self, + &self, block_num: BlockNumber, ) -> Result<(BlockHeader, MmrProof), RpcError> { let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?; @@ -299,7 +290,7 @@ pub trait NodeRpcClient { /// /// Errors: /// - [RpcError::NoteNotFound] if the note with the specified ID is not found. - async fn get_note_by_id(&mut self, note_id: NoteId) -> Result { + async fn get_note_by_id(&self, note_id: NoteId) -> Result { let notes = self.get_notes_by_id(&[note_id]).await?; notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id)) } diff --git a/crates/rust-client/src/rpc/tonic_client/mod.rs b/crates/rust-client/src/rpc/tonic_client/mod.rs index 490fcdaef..b72659061 100644 --- a/crates/rust-client/src/rpc/tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/tonic_client/mod.rs @@ -17,7 +17,10 @@ use miden_objects::{ utils::Deserializable, Digest, }; -use miden_tx::utils::Serializable; +use miden_tx::utils::{ + sync::{RwLock, RwLockWriteGuard}, + Serializable, +}; use tracing::info; use super::{ @@ -62,30 +65,31 @@ type InnerClient = tonic::transport::Channel; /// In both cases, the [`TonicRpcClient`] depends on the types inside the `generated` module, which /// are generated by the build script and also depend on the target architecture. pub struct TonicRpcClient { - client: Option>, + client: RwLock>>, endpoint: String, #[allow(dead_code)] timeout_ms: u64, } +#[allow(clippy::await_holding_lock, clippy::unused_async)] impl TonicRpcClient { /// Returns a new instance of [`TonicRpcClient`] that'll do calls to the provided [`Endpoint`] /// with the given timeout in milliseconds. pub fn new(endpoint: &Endpoint, timeout_ms: u64) -> TonicRpcClient { TonicRpcClient { - client: None, + client: RwLock::new(None), endpoint: endpoint.to_string(), timeout_ms, } } - /// Takes care of establishing the RPC connection if not connected yet and returns a reference - /// to the inner `ApiClient`. - #[allow(clippy::unused_async)] - async fn rpc_api(&mut self) -> Result<&mut ApiClient, RpcError> { - if self.client.is_some() { - Ok(self.client.as_mut().unwrap()) - } else { + /// Takes care of establishing the RPC connection if not connected yet. It ensures that the + /// `rpc_api` field is initialized and returns a write guard to it. + async fn ensure_connected( + &self, + ) -> Result>>, RpcError> { + let mut client = self.client.write(); + if client.is_none() { #[cfg(feature = "web-tonic")] let new_client = { let wasm_client = tonic_web_wasm_client::Client::new(self.endpoint.clone()); @@ -103,21 +107,27 @@ impl TonicRpcClient { .map_err(|err| RpcError::ConnectionError(err.to_string()))? }; - Ok(self.client.insert(new_client)) + client.replace(new_client); } + + Ok(client) } } #[async_trait(?Send)] +#[allow(clippy::await_holding_lock)] impl NodeRpcClient for TonicRpcClient { async fn submit_proven_transaction( - &mut self, + &self, proven_transaction: ProvenTransaction, ) -> Result<(), RpcError> { let request = SubmitProvenTransactionRequest { transaction: proven_transaction.to_bytes(), }; - let rpc_api = self.rpc_api().await?; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + rpc_api.submit_proven_transaction(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::SubmitProvenTx.to_string(), @@ -129,7 +139,7 @@ impl NodeRpcClient for TonicRpcClient { } async fn get_block_header_by_number( - &mut self, + &self, block_num: Option, include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError> { @@ -140,7 +150,9 @@ impl NodeRpcClient for TonicRpcClient { info!("Calling GetBlockHeaderByNumber: {:?}", request); - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let api_response = rpc_api.get_block_header_by_number(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::GetBlockHeaderByNumber.to_string(), @@ -176,11 +188,14 @@ impl NodeRpcClient for TonicRpcClient { Ok((block_header, mmr_proof)) } - async fn get_notes_by_id(&mut self, note_ids: &[NoteId]) -> Result, RpcError> { + async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result, RpcError> { let request = GetNotesByIdRequest { note_ids: note_ids.iter().map(|id| id.inner().into()).collect(), }; - let rpc_api = self.rpc_api().await?; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let api_response = rpc_api.get_notes_by_id(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::GetBlockHeaderByNumber.to_string(), @@ -229,7 +244,7 @@ impl NodeRpcClient for TonicRpcClient { /// Sends a sync state request to the Miden node, validates and converts the response /// into a [StateSyncInfo] struct. async fn sync_state( - &mut self, + &self, block_num: BlockNumber, account_ids: &[AccountId], note_tags: &[NoteTag], @@ -244,7 +259,9 @@ impl NodeRpcClient for TonicRpcClient { note_tags, }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let response = rpc_api.sync_state(request).await.map_err(|err| { RpcError::RequestError(NodeRpcClientEndpoint::SyncState.to_string(), err.to_string()) })?; @@ -262,13 +279,11 @@ impl NodeRpcClient for TonicRpcClient { /// - The answer had a `None` for one of the expected fields (account, summary, account_hash, /// details). /// - There is an error during [Account] deserialization. - async fn get_account_details( - &mut self, - account_id: AccountId, - ) -> Result { + async fn get_account_details(&self, account_id: AccountId) -> Result { let request = GetAccountDetailsRequest { account_id: Some(account_id.into()) }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); let response = rpc_api.get_account_details(request).await.map_err(|err| { RpcError::RequestError( @@ -317,7 +332,7 @@ impl NodeRpcClient for TonicRpcClient { /// - The answer had a `None` for one of the expected fields. /// - There is an error during storage deserialization. async fn get_account_proofs( - &mut self, + &self, account_requests: &BTreeSet, known_account_codes: Vec, ) -> Result { @@ -341,7 +356,9 @@ impl NodeRpcClient for TonicRpcClient { code_commitments: known_account_codes.keys().map(Into::into).collect(), }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let response = rpc_api .get_account_proofs(request) .await @@ -403,7 +420,7 @@ impl NodeRpcClient for TonicRpcClient { /// Sends a `SyncNoteRequest` to the Miden node, and extracts a [NoteSyncInfo] from the /// response. async fn sync_notes( - &mut self, + &self, block_num: BlockNumber, note_tags: &[NoteTag], ) -> Result { @@ -411,7 +428,8 @@ impl NodeRpcClient for TonicRpcClient { let request = SyncNoteRequest { block_num: block_num.as_u32(), note_tags }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); let response = rpc_api.sync_notes(request).await.map_err(|err| { RpcError::RequestError(NodeRpcClientEndpoint::SyncNotes.to_string(), err.to_string()) @@ -421,7 +439,7 @@ impl NodeRpcClient for TonicRpcClient { } async fn check_nullifiers_by_prefix( - &mut self, + &self, prefixes: &[u16], block_num: BlockNumber, ) -> Result, RpcError> { @@ -430,7 +448,10 @@ impl NodeRpcClient for TonicRpcClient { prefix_len: 16, block_num: block_num.as_u32(), }; - let rpc_api = self.rpc_api().await?; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let response = rpc_api.check_nullifiers_by_prefix(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::CheckNullifiersByPrefix.to_string(), @@ -448,14 +469,13 @@ impl NodeRpcClient for TonicRpcClient { Ok(nullifiers) } - async fn check_nullifiers( - &mut self, - nullifiers: &[Nullifier], - ) -> Result, RpcError> { + async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result, RpcError> { let request = CheckNullifiersRequest { nullifiers: nullifiers.iter().map(|nul| nul.inner().into()).collect(), }; - let rpc_api = self.rpc_api().await?; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); let response = rpc_api.check_nullifiers(request).await.map_err(|err| { RpcError::RequestError( @@ -471,7 +491,7 @@ impl NodeRpcClient for TonicRpcClient { } async fn get_account_state_delta( - &mut self, + &self, account_id: AccountId, from_block: BlockNumber, to_block: BlockNumber, @@ -482,7 +502,9 @@ impl NodeRpcClient for TonicRpcClient { to_block_num: to_block.as_u32(), }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let response = rpc_api.get_account_state_delta(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::GetAccountStateDelta.to_string(), @@ -498,13 +520,12 @@ impl NodeRpcClient for TonicRpcClient { Ok(delta) } - async fn get_block_by_number( - &mut self, - block_num: BlockNumber, - ) -> Result { + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result { let request = GetBlockByNumberRequest { block_num: block_num.as_u32() }; - let rpc_api = self.rpc_api().await?; + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + let response = rpc_api.get_block_by_number(request).await.map_err(|err| { RpcError::RequestError( NodeRpcClientEndpoint::GetBlockByNumber.to_string(), diff --git a/crates/rust-client/src/store/sqlite_store/note.rs b/crates/rust-client/src/store/sqlite_store/note.rs index da7a477b9..cfc04b9d0 100644 --- a/crates/rust-client/src/store/sqlite_store/note.rs +++ b/crates/rust-client/src/store/sqlite_store/note.rs @@ -589,17 +589,11 @@ pub(crate) fn apply_note_updates_tx( tx: &Transaction, note_updates: &NoteUpdates, ) -> Result<(), StoreError> { - for input_note in - note_updates.new_input_notes().iter().chain(note_updates.updated_input_notes()) - { + for input_note in note_updates.updated_input_notes() { upsert_input_note_tx(tx, input_note)?; } - for output_note in note_updates - .new_output_notes() - .iter() - .chain(note_updates.updated_output_notes()) - { + for output_note in note_updates.updated_output_notes() { upsert_output_note_tx(tx, output_note)?; } diff --git a/crates/rust-client/src/store/sqlite_store/sync.rs b/crates/rust-client/src/store/sqlite_store/sync.rs index 129b860a2..f9129933b 100644 --- a/crates/rust-client/src/store/sqlite_store/sync.rs +++ b/crates/rust-client/src/store/sqlite_store/sync.rs @@ -104,13 +104,12 @@ impl SqliteStore { ) -> Result<(), StoreError> { let StateSyncUpdate { block_header, - note_updates, - transactions_to_commit: committed_transactions, + block_has_relevant_notes, new_mmr_peaks, new_authentication_nodes, - updated_accounts, - block_has_relevant_notes, - transactions_to_discard: discarded_transactions, + note_updates, + transaction_updates, + account_updates, tags_to_remove, } = state_sync_update; @@ -134,17 +133,17 @@ impl SqliteStore { Self::insert_chain_mmr_nodes_tx(&tx, &new_authentication_nodes)?; // Mark transactions as committed - Self::mark_transactions_as_committed(&tx, &committed_transactions)?; + Self::mark_transactions_as_committed(&tx, transaction_updates.committed_transactions())?; // Marc transactions as discarded - Self::mark_transactions_as_discarded(&tx, &discarded_transactions)?; + Self::mark_transactions_as_discarded(&tx, transaction_updates.discarded_transactions())?; // Update public accounts on the db that have been updated onchain - for account in updated_accounts.updated_public_accounts() { + for account in account_updates.updated_public_accounts() { update_account(&tx, account)?; } - for (account_id, _) in updated_accounts.mismatched_private_accounts() { + for (account_id, _) in account_updates.mismatched_private_accounts() { lock_account(&tx, *account_id)?; } diff --git a/crates/rust-client/src/store/web_store/note/utils.rs b/crates/rust-client/src/store/web_store/note/utils.rs index 1265c5453..6b891fe3d 100644 --- a/crates/rust-client/src/store/web_store/note/utils.rs +++ b/crates/rust-client/src/store/web_store/note/utils.rs @@ -192,17 +192,11 @@ pub fn parse_output_note_idxdb_object( } pub(crate) async fn apply_note_updates_tx(note_updates: &NoteUpdates) -> Result<(), StoreError> { - for input_note in - note_updates.new_input_notes().iter().chain(note_updates.updated_input_notes()) - { + for input_note in note_updates.updated_input_notes() { upsert_input_note_tx(input_note).await?; } - for output_note in note_updates - .new_output_notes() - .iter() - .chain(note_updates.updated_output_notes()) - { + for output_note in note_updates.updated_output_notes() { upsert_output_note_tx(output_note).await?; } diff --git a/crates/rust-client/src/store/web_store/sync/mod.rs b/crates/rust-client/src/store/web_store/sync/mod.rs index bf55a0ea7..a039a9b5e 100644 --- a/crates/rust-client/src/store/web_store/sync/mod.rs +++ b/crates/rust-client/src/store/web_store/sync/mod.rs @@ -109,14 +109,12 @@ impl WebStore { ) -> Result<(), StoreError> { let StateSyncUpdate { block_header, - note_updates, - transactions_to_commit: committed_transactions, + block_has_relevant_notes, new_mmr_peaks, new_authentication_nodes, - updated_accounts, - block_has_relevant_notes, - transactions_to_discard: _transactions_to_discard, /* TODO: Add support for discarded - * transactions in web store */ + note_updates, + transaction_updates, + account_updates, tags_to_remove, } = state_sync_update; @@ -153,22 +151,24 @@ impl WebStore { .collect(); // Serialize data for updating committed transactions - let transactions_to_commit_block_nums_as_str = committed_transactions + let transactions_to_commit_block_nums_as_str = transaction_updates + .committed_transactions() .iter() .map(|tx_update| tx_update.block_num.to_string()) .collect(); - let transactions_to_commit_as_str: Vec = committed_transactions + let transactions_to_commit_as_str: Vec = transaction_updates + .committed_transactions() .iter() .map(|tx_update| tx_update.transaction_id.to_string()) .collect(); // TODO: LOP INTO idxdb_apply_state_sync call // Update public accounts on the db that have been updated onchain - for account in updated_accounts.updated_public_accounts() { + for account in account_updates.updated_public_accounts() { update_account(&account.clone()).await.unwrap(); } - for (account_id, _) in updated_accounts.mismatched_private_accounts() { + for (account_id, _) in account_updates.mismatched_private_accounts() { lock_account(account_id).await.unwrap(); } diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index e23bf353a..88bae531b 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -85,11 +85,7 @@ impl Client { let note_screener = NoteScreener::new(self.store.clone()); // Find all relevant Input Notes using the note checker - for input_note in committed_notes - .updated_input_notes() - .iter() - .chain(committed_notes.new_input_notes().iter()) - { + for input_note in committed_notes.updated_input_notes() { if !note_screener .check_relevance(&input_note.try_into().map_err(ClientError::NoteRecordError)?) .await? @@ -152,7 +148,7 @@ impl Client { /// If the store already contains MMR data for the requested block number, the request isn't /// done and the stored block header is returned. pub(crate) async fn get_and_store_authenticated_block( - &mut self, + &self, block_num: BlockNumber, current_partial_mmr: &mut PartialMmr, ) -> Result { diff --git a/crates/rust-client/src/sync/mod.rs b/crates/rust-client/src/sync/mod.rs index 98b7a7613..8d7646339 100644 --- a/crates/rust-client/src/sync/mod.rs +++ b/crates/rust-client/src/sync/mod.rs @@ -38,7 +38,6 @@ //! let sync_summary: SyncSummary = client.sync_state().await?; //! //! println!("Synced up to block number: {}", sync_summary.block_num); -//! println!("Received notes: {}", sync_summary.received_notes.len()); //! println!("Committed notes: {}", sync_summary.committed_notes.len()); //! println!("Consumed notes: {}", sync_summary.consumed_notes.len()); //! println!("Updated accounts: {}", sync_summary.updated_accounts.len()); @@ -58,11 +57,10 @@ use alloc::{collections::BTreeMap, vec::Vec}; use core::cmp::max; -use crypto::merkle::{InOrderIndex, MmrPeaks}; use miden_objects::{ account::{Account, AccountHeader, AccountId}, block::{BlockHeader, BlockNumber}, - crypto::{self, rand::FeltRng}, + crypto::rand::FeltRng, note::{NoteId, NoteInclusionProof, NoteTag, Nullifier}, transaction::TransactionId, Digest, @@ -75,7 +73,7 @@ use crate::{ note::CommittedNote, nullifier::NullifierUpdate, transaction::TransactionUpdate, }, store::{AccountUpdates, InputNoteRecord, NoteFilter, OutputNoteRecord, TransactionFilter}, - transaction::TransactionStatus, + transaction::{TransactionStatus, TransactionUpdates}, Client, ClientError, }; @@ -85,12 +83,13 @@ use block_header::apply_mmr_changes; mod tag; pub use tag::{NoteTagRecord, NoteTagSource}; +mod state_sync_update; +pub use state_sync_update::StateSyncUpdate; + /// Contains stats about the sync operation. pub struct SyncSummary { /// Block number up to which the client has been synced. pub block_num: BlockNumber, - /// IDs of new notes received. - pub received_notes: Vec, /// IDs of tracked notes that received inclusion proofs. pub committed_notes: Vec, /// IDs of notes that have been consumed. @@ -106,7 +105,6 @@ pub struct SyncSummary { impl SyncSummary { pub fn new( block_num: BlockNumber, - received_notes: Vec, committed_notes: Vec, consumed_notes: Vec, updated_accounts: Vec, @@ -115,7 +113,6 @@ impl SyncSummary { ) -> Self { Self { block_num, - received_notes, committed_notes, consumed_notes, updated_accounts, @@ -127,7 +124,6 @@ impl SyncSummary { pub fn new_empty(block_num: BlockNumber) -> Self { Self { block_num, - received_notes: vec![], committed_notes: vec![], consumed_notes: vec![], updated_accounts: vec![], @@ -137,8 +133,7 @@ impl SyncSummary { } pub fn is_empty(&self) -> bool { - self.received_notes.is_empty() - && self.committed_notes.is_empty() + self.committed_notes.is_empty() && self.consumed_notes.is_empty() && self.updated_accounts.is_empty() && self.locked_accounts.is_empty() @@ -147,7 +142,6 @@ impl SyncSummary { pub fn combine_with(&mut self, mut other: Self) { self.block_num = max(self.block_num, other.block_num); - self.received_notes.append(&mut other.received_notes); self.committed_notes.append(&mut other.committed_notes); self.consumed_notes.append(&mut other.consumed_notes); self.updated_accounts.append(&mut other.updated_accounts); @@ -169,31 +163,6 @@ impl SyncStatus { } } -/// Contains all information needed to apply the update in the store after syncing with the node. -pub struct StateSyncUpdate { - /// The new block header, returned as part of the - /// [`StateSyncInfo`](crate::rpc::domain::sync::StateSyncInfo) - pub block_header: BlockHeader, - /// Information about note changes after the sync. - pub note_updates: NoteUpdates, - /// Transaction updates for any transaction that was committed between the sync request's - /// block number and the response's block number. - pub transactions_to_commit: Vec, - /// Transaction IDs for any transactions that were discarded in the sync. - pub transactions_to_discard: Vec, - /// New MMR peaks for the locally tracked MMR of the blockchain. - pub new_mmr_peaks: MmrPeaks, - /// New authentications nodes that are meant to be stored in order to authenticate block - /// headers. - pub new_authentication_nodes: Vec<(InOrderIndex, Digest)>, - /// Information abount account changes after the sync. - pub updated_accounts: AccountUpdates, - /// Whether the block header has notes relevant to the client. - pub block_has_relevant_notes: bool, - /// Tag records that are no longer relevant. - pub tags_to_remove: Vec, -} - // CONSTANTS // ================================================================================================ @@ -310,7 +279,6 @@ impl Client { // Store summary to return later let sync_summary = SyncSummary::new( response.block_header.block_num(), - note_updates.new_input_notes().iter().map(InputNoteRecord::id).collect(), note_updates.committed_note_ids().into_iter().collect(), note_updates.consumed_note_ids().into_iter().collect(), updated_public_accounts.iter().map(Account::id).collect(), @@ -320,16 +288,15 @@ impl Client { let response_block_num = response.block_header.block_num(); let state_sync_update = StateSyncUpdate { block_header: response.block_header, - note_updates, - transactions_to_commit, + block_has_relevant_notes: incoming_block_has_relevant_notes, new_mmr_peaks: new_peaks, new_authentication_nodes, - updated_accounts: AccountUpdates::new( + note_updates, + transaction_updates: TransactionUpdates::new(transactions_to_commit, vec![]), + account_updates: AccountUpdates::new( updated_public_accounts, mismatched_private_accounts, ), - block_has_relevant_notes: incoming_block_has_relevant_notes, - transactions_to_discard: vec![], tags_to_remove, }; @@ -395,16 +362,11 @@ impl Client { // Store summary to return later let sync_summary = SyncSummary::new( 0.into(), - consumed_note_updates - .new_input_notes() - .iter() - .map(InputNoteRecord::id) - .collect(), consumed_note_updates.committed_note_ids().into_iter().collect(), consumed_note_updates.consumed_note_ids().into_iter().collect(), vec![], vec![], - vec![], + committed_transactions.iter().map(|tx| tx.transaction_id).collect(), ); // Apply received and computed updates to the store @@ -493,9 +455,7 @@ impl Client { Ok(( NoteUpdates::new( - new_public_notes, - vec![], - committed_tracked_input_notes, + [new_public_notes, committed_tracked_input_notes].concat(), committed_tracked_output_notes, ), removed_tags, @@ -594,12 +554,7 @@ impl Client { } Ok(( - NoteUpdates::new( - vec![], - vec![], - consumed_tracked_input_notes, - consumed_tracked_output_notes, - ), + NoteUpdates::new(consumed_tracked_input_notes, consumed_tracked_output_notes), discarded_transactions, )) } diff --git a/crates/rust-client/src/sync/state_sync_update.rs b/crates/rust-client/src/sync/state_sync_update.rs new file mode 100644 index 000000000..f5f887c6f --- /dev/null +++ b/crates/rust-client/src/sync/state_sync_update.rs @@ -0,0 +1,64 @@ +use alloc::vec::Vec; + +use miden_objects::{ + account::Account, + block::BlockHeader, + crypto::merkle::{InOrderIndex, MmrPeaks}, + Digest, +}; + +use super::{NoteTagRecord, SyncSummary}; +use crate::{note::NoteUpdates, store::AccountUpdates, transaction::TransactionUpdates}; + +// STATE SYNC UPDATE +// ================================================================================================ + +/// Contains all information needed to apply the update in the store after syncing with the node. +pub struct StateSyncUpdate { + /// The new block header, returned as part of the + /// [`StateSyncInfo`](crate::rpc::domain::sync::StateSyncInfo) + pub block_header: BlockHeader, + /// Whether the block header has notes relevant to the client. + pub block_has_relevant_notes: bool, + /// New MMR peaks for the locally tracked MMR of the blockchain. + pub new_mmr_peaks: MmrPeaks, + /// New authentications nodes that are meant to be stored in order to authenticate block + /// headers. + pub new_authentication_nodes: Vec<(InOrderIndex, Digest)>, + /// New and updated notes to be upserted in the store. + pub note_updates: NoteUpdates, + /// Committed and discarded transactions after the sync. + pub transaction_updates: TransactionUpdates, + /// Public account updates and mismatched private accounts after the sync. + pub account_updates: AccountUpdates, + /// Tag records that are no longer relevant. + pub tags_to_remove: Vec, +} + +impl From<&StateSyncUpdate> for SyncSummary { + fn from(value: &StateSyncUpdate) -> Self { + SyncSummary::new( + value.block_header.block_num(), + value.note_updates.committed_note_ids().into_iter().collect(), + value.note_updates.consumed_note_ids().into_iter().collect(), + value + .account_updates + .updated_public_accounts() + .iter() + .map(Account::id) + .collect(), + value + .account_updates + .mismatched_private_accounts() + .iter() + .map(|(id, _)| *id) + .collect(), + value + .transaction_updates + .committed_transactions() + .iter() + .map(|t| t.transaction_id) + .collect(), + ) + } +} diff --git a/crates/rust-client/src/tests.rs b/crates/rust-client/src/tests.rs index f3a8a3eeb..391fb243d 100644 --- a/crates/rust-client/src/tests.rs +++ b/crates/rust-client/src/tests.rs @@ -327,7 +327,7 @@ async fn test_sync_state() { #[tokio::test] async fn test_sync_state_mmr() { // generate test client with a random store name - let (mut client, mut rpc_api, keystore) = create_test_client().await; + let (mut client, rpc_api, keystore) = create_test_client().await; // Import note and create wallet so that synced notes do not get discarded (due to being // irrelevant) insert_new_wallet(&mut client, AccountStorageMode::Private, &keystore) diff --git a/crates/rust-client/src/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index f3d083675..9767e4cab 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -95,7 +95,7 @@ use super::{Client, FeltRng}; use crate::{ account::procedure_roots::RPO_FALCON_512_AUTH, note::{NoteScreener, NoteUpdates}, - rpc::domain::account::AccountProof, + rpc::domain::{account::AccountProof, transaction::TransactionUpdate}, store::{ input_note_states::ExpectedNoteState, InputNoteRecord, InputNoteState, NoteFilter, OutputNoteRecord, StoreError, TransactionFilter, @@ -120,7 +120,7 @@ pub use miden_tx::{DataStoreError, TransactionExecutorError}; pub use script_builder::TransactionScriptBuilderError; // TRANSACTION RESULT -// -------------------------------------------------------------------------------------------- +// ================================================================================================ /// Represents the result of executing a transaction by the client. /// @@ -242,7 +242,7 @@ impl Deserializable for TransactionResult { } // TRANSACTION RECORD -// -------------------------------------------------------------------------------------------- +// ================================================================================================ /// Describes a transaction that has been executed and is being tracked on the Client. /// @@ -312,7 +312,7 @@ impl fmt::Display for TransactionStatus { } // TRANSACTION STORE UPDATE -// -------------------------------------------------------------------------------------------- +// ================================================================================================ /// Represents the changes that need to be applied to the client store as a result of a /// transaction execution. @@ -341,10 +341,8 @@ impl TransactionStoreUpdate { executed_transaction, updated_account, note_updates: NoteUpdates::new( - created_input_notes, + [created_input_notes, updated_input_notes].concat(), created_output_notes, - updated_input_notes, - vec![], ), new_tags, } @@ -371,6 +369,50 @@ impl TransactionStoreUpdate { } } +/// Contains transaction changes to apply to the store. +#[derive(Default)] +pub struct TransactionUpdates { + /// Transaction updates for any transaction that was committed between the sync request's block + /// number and the response's block number. + committed_transactions: Vec, + /// Transaction IDs for any transactions that were discarded in the sync. + discarded_transactions: Vec, +} + +impl TransactionUpdates { + /// Creates a new [`TransactionUpdate`] + pub fn new( + committed_transactions: Vec, + discarded_transactions: Vec, + ) -> Self { + Self { + committed_transactions, + discarded_transactions, + } + } + + /// Extends the transaction update information with `other`. + pub fn extend(&mut self, other: Self) { + self.committed_transactions.extend(other.committed_transactions); + self.discarded_transactions.extend(other.discarded_transactions); + } + + /// Returns a reference to committed transactions. + pub fn committed_transactions(&self) -> &[TransactionUpdate] { + &self.committed_transactions + } + + /// Returns a reference to discarded transactions. + pub fn discarded_transactions(&self) -> &[TransactionId] { + &self.discarded_transactions + } + + /// Inserts a discarded transaction into the transaction updates. + pub fn insert_discarded_transaction(&mut self, transaction_id: TransactionId) { + self.discarded_transactions.push(transaction_id); + } +} + /// Transaction management methods impl Client { // TRANSACTION DATA RETRIEVAL diff --git a/crates/web-client/src/lib.rs b/crates/web-client/src/lib.rs index ad4a8800c..e0eef5fbd 100644 --- a/crates/web-client/src/lib.rs +++ b/crates/web-client/src/lib.rs @@ -90,7 +90,7 @@ impl WebClient { Endpoint::try_from(url.as_str()).map_err(|_| JsValue::from_str("Invalid node URL")) })?; - let web_rpc_client = Box::new(TonicRpcClient::new(&endpoint, 0)); + let web_rpc_client = Arc::new(TonicRpcClient::new(&endpoint, 0)); self.remote_prover = prover_url.map(|prover_url| Arc::new(RemoteTransactionProver::new(prover_url))); diff --git a/crates/web-client/src/models/sync_summary.rs b/crates/web-client/src/models/sync_summary.rs index 7178855de..f26697909 100644 --- a/crates/web-client/src/models/sync_summary.rs +++ b/crates/web-client/src/models/sync_summary.rs @@ -13,11 +13,6 @@ impl SyncSummary { self.0.block_num.as_u32() } - #[wasm_bindgen(js_name = "receivedNotes")] - pub fn received_notes(&self) -> Vec { - self.0.received_notes.iter().map(Into::into).collect() - } - #[wasm_bindgen(js_name = "committedNotes")] pub fn committed_notes(&self) -> Vec { self.0.committed_notes.iter().map(Into::into).collect() diff --git a/docs/library.md b/docs/library.md index 2dcd1283e..d6212ab15 100644 --- a/docs/library.md +++ b/docs/library.md @@ -37,7 +37,7 @@ let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng); // Instantiate the client using a Tonic RPC client let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291)); let client: Client = Client::new( - Box::new(TonicRpcClient::new(&endpoint, 10_000)), + Arc::new(TonicRpcClient::new(&endpoint, 10_000)), rng, store, Arc::new(authenticator), diff --git a/eslint.config.js b/eslint.config.js index dca2c53ef..db8ac9b31 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,6 +4,7 @@ module.exports = [ ignores: [ "crates/web-client/dist/**/*", "target/**/*", + "**/target/**/*", "miden-node/**/*", "**/*.d.ts", ], diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 14f827292..b321a5943 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -65,7 +65,7 @@ pub async fn create_test_client() -> (TestClient, FilesystemKeyStore) { let authenticator = ClientAuthenticator::new(rng, keystore.clone()); ( TestClient::new( - Box::new(TonicRpcClient::new(&rpc_endpoint, rpc_timeout)), + Arc::new(TonicRpcClient::new(&rpc_endpoint, rpc_timeout)), rng, store, Arc::new(authenticator), diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 18842dd8a..c2d3e8168 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -56,7 +56,7 @@ async fn test_client_builder_initializes_client_with_rpc() -> Result<(), ClientE let endpoint = Endpoint::new("https".to_string(), "rpc.testnet.miden.io".to_string(), Some(443)); let timeout_ms = 10_000; - let rpc_api = Box::new(TonicRpcClient::new(&endpoint, timeout_ms)); + let rpc_api = Arc::new(TonicRpcClient::new(&endpoint, timeout_ms)); let mut client = ClientBuilder::new() .with_rpc(rpc_api) @@ -881,7 +881,7 @@ async fn test_get_account_update() { // [`AccountDetails`] should be received. // TODO: should we expose the `get_account_update` endpoint from the Client? let (endpoint, timeout, ..) = get_client_config(); - let mut rpc_api = TonicRpcClient::new(&endpoint, timeout); + let rpc_api = TonicRpcClient::new(&endpoint, timeout); let details1 = rpc_api.get_account_details(basic_wallet_1.id()).await.unwrap(); let details2 = rpc_api.get_account_details(basic_wallet_2.id()).await.unwrap(); @@ -934,8 +934,7 @@ async fn test_sync_detail_values() { // Second client sync should have new note let new_details = client2.sync_state().await.unwrap(); - assert_eq!(new_details.received_notes.len(), 1); - assert_eq!(new_details.committed_notes.len(), 0); + assert_eq!(new_details.committed_notes.len(), 1); assert_eq!(new_details.consumed_notes.len(), 0); assert_eq!(new_details.updated_accounts.len(), 0); @@ -945,7 +944,6 @@ async fn test_sync_detail_values() { // First client sync should have a new nullifier as the note was consumed let new_details = client1.sync_state().await.unwrap(); - assert_eq!(new_details.received_notes.len(), 0); assert_eq!(new_details.committed_notes.len(), 0); assert_eq!(new_details.consumed_notes.len(), 1); }