From 62ef7ecbefe3fb01f2601928eeae6823365d004b Mon Sep 17 00:00:00 2001 From: Tomas Rodriguez Dala <43424983+tomyrd@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:27:30 -0300 Subject: [PATCH 1/2] refactor: update structs for `StateSync` component (#727) * remove `received_notes` from `SyncSummary` * feat: use `BTreeMap` in `NoteUpdates` * update `StateSyncUpdate` * review: update `NoteUpdates` docs * review: fix `TransactionUpdates` naming --- bin/miden-cli/src/commands/sync.rs | 3 +- crates/rust-client/src/note/mod.rs | 111 +++++++++--------- .../src/store/sqlite_store/note.rs | 10 +- .../src/store/sqlite_store/sync.rs | 17 ++- .../src/store/web_store/note/utils.rs | 10 +- .../src/store/web_store/sync/mod.rs | 20 ++-- crates/rust-client/src/sync/block_header.rs | 6 +- crates/rust-client/src/sync/mod.rs | 70 +++-------- .../rust-client/src/sync/state_sync_update.rs | 64 ++++++++++ crates/rust-client/src/transaction/mod.rs | 56 +++++++-- crates/web-client/src/models/sync_summary.rs | 4 - tests/integration/main.rs | 4 +- 12 files changed, 211 insertions(+), 164 deletions(-) create mode 100644 crates/rust-client/src/sync/state_sync_update.rs 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/crates/rust-client/src/note/mod.rs b/crates/rust-client/src/note/mod.rs index f99285bd3..f8e6cb52e 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 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 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 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,18 +315,27 @@ 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::>() } + + /// Extends this note update information with `other`. If the two contain updates to the same + /// note (i.e. their IDs match), the updates from `other` will overwrite the updates in + /// `self`. + pub(crate) fn extend(&mut self, other: NoteUpdates) { + self.updated_input_notes.extend(other.updated_input_notes); + self.updated_output_notes.extend(other.updated_output_notes); + } } 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 64df7c9c6..a57ea6d4a 100644 --- a/crates/rust-client/src/store/sqlite_store/sync.rs +++ b/crates/rust-client/src/store/sqlite_store/sync.rs @@ -103,13 +103,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; @@ -133,17 +132,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 91a4f9aa6..b3829710b 100644 --- a/crates/rust-client/src/store/web_store/sync/mod.rs +++ b/crates/rust-client/src/store/web_store/sync/mod.rs @@ -107,14 +107,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; @@ -151,22 +149,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 d14730fc0..074a350b8 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? diff --git a/crates/rust-client/src/sync/mod.rs b/crates/rust-client/src/sync/mod.rs index 3e32bef85..54c94a324 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,6 +73,7 @@ use crate::{ note::CommittedNote, nullifier::NullifierUpdate, transaction::TransactionUpdate, }, store::{AccountUpdates, InputNoteRecord, NoteFilter, OutputNoteRecord, TransactionFilter}, + transaction::TransactionUpdates, Client, ClientError, }; @@ -84,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. @@ -105,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, @@ -114,7 +113,6 @@ impl SyncSummary { ) -> Self { Self { block_num, - received_notes, committed_notes, consumed_notes, updated_accounts, @@ -126,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![], @@ -136,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() @@ -145,7 +141,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); @@ -166,31 +161,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 // ================================================================================================ @@ -293,7 +263,9 @@ impl Client { let (consumed_note_updates, transactions_to_discard) = self.consumed_note_updates(response.nullifiers, &transactions_to_commit).await?; - let note_updates = committed_note_updates.combine_with(consumed_note_updates); + let mut note_updates = NoteUpdates::new(vec![], vec![]); + note_updates.extend(committed_note_updates); + note_updates.extend(consumed_note_updates); let (public_accounts, private_accounts): (Vec<_>, Vec<_>) = accounts.into_iter().partition(|account_header| account_header.id().is_public()); @@ -327,7 +299,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(), @@ -337,16 +308,18 @@ impl Client { 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, + transactions_to_discard, + ), + account_updates: AccountUpdates::new( updated_public_accounts, mismatched_private_accounts, ), - block_has_relevant_notes: incoming_block_has_relevant_notes, - transactions_to_discard, tags_to_remove, }; @@ -443,9 +416,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, @@ -545,12 +516,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/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index b193d5dfd..32d434bdc 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -94,7 +94,7 @@ use tracing::info; use super::{Client, FeltRng}; use crate::{ note::{NoteScreener, NoteUpdates}, - rpc::domain::account::AccountProof, + rpc::domain::{account::AccountProof, transaction::TransactionUpdate}, store::{ input_note_states::ExpectedNoteState, InputNoteRecord, InputNoteState, NoteFilter, OutputNoteRecord, StoreError, TransactionFilter, @@ -119,7 +119,7 @@ pub use miden_tx::{DataStoreError, TransactionExecutorError}; pub use script_builder::TransactionScriptBuilderError; // TRANSACTION RESULT -// -------------------------------------------------------------------------------------------- +// ================================================================================================ /// Represents the result of executing a transaction by the client. /// @@ -241,7 +241,7 @@ impl Deserializable for TransactionResult { } // TRANSACTION RECORD -// -------------------------------------------------------------------------------------------- +// ================================================================================================ /// Describes a transaction that has been executed and is being tracked on the Client. /// @@ -311,7 +311,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. @@ -340,10 +340,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, } @@ -370,6 +368,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/models/sync_summary.rs b/crates/web-client/src/models/sync_summary.rs index abaebe601..4a00acf81 100644 --- a/crates/web-client/src/models/sync_summary.rs +++ b/crates/web-client/src/models/sync_summary.rs @@ -12,10 +12,6 @@ impl SyncSummary { self.0.block_num.as_u32() } - pub fn received_notes(&self) -> Vec { - self.0.received_notes.iter().map(Into::into).collect() - } - pub fn committed_notes(&self) -> Vec { self.0.committed_notes.iter().map(Into::into).collect() } diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 461f27f23..f73113621 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -850,8 +850,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); @@ -861,7 +860,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); } From 47096e6e2904f1eb73b2074a1ed01b0a607d4a95 Mon Sep 17 00:00:00 2001 From: Tomas Rodriguez Dala <43424983+tomyrd@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:48:07 -0300 Subject: [PATCH 2/2] feat: give interior mutability to rpc client (#726) * feat: give interior mutability to `NodeRpcClient` * refactor: move client's rpc_api to Arc * feat: re-add lazy connect * address review comments * review: docs --- bin/miden-cli/src/lib.rs | 4 +- bin/miden-cli/src/tests.rs | 7 +- crates/rust-client/Cargo.toml | 2 +- crates/rust-client/src/lib.rs | 10 +-- crates/rust-client/src/mock.rs | 25 +++--- crates/rust-client/src/note/import.rs | 4 +- crates/rust-client/src/rpc/mod.rs | 29 ++++--- .../rust-client/src/rpc/tonic_client/mod.rs | 76 ++++++++++++------- .../src/rpc/web_tonic_client/mod.rs | 19 ++--- crates/rust-client/src/sync/block_header.rs | 2 +- crates/rust-client/src/tests.rs | 2 +- crates/web-client/src/lib.rs | 2 +- docs/library.md | 2 +- tests/integration/common.rs | 2 +- tests/integration/main.rs | 2 +- 15 files changed, 98 insertions(+), 90 deletions(-) diff --git a/bin/miden-cli/src/lib.rs b/bin/miden-cli/src/lib.rs index 31c71f341..fbaf94a4a 100644 --- a/bin/miden-cli/src/lib.rs +++ b/bin/miden-cli/src/lib.rs @@ -112,8 +112,8 @@ impl Cli { let authenticator = StoreAuthenticator::new_with_rng(store.clone() as Arc, rng); 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 84c1d9cc5..f1501a78c 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; @@ -699,7 +700,7 @@ async fn create_test_client_with_store_path(store_path: &Path) -> TestClient { let store = { let sqlite_store = SqliteStore::new(PathBuf::from(store_path)).await.unwrap(); - std::sync::Arc::new(sqlite_store) + Arc::new(sqlite_store) }; let mut rng = rand::thread_rng(); @@ -709,10 +710,10 @@ async fn create_test_client_with_store_path(store_path: &Path) -> TestClient { let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng); 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), + Arc::new(authenticator), true, ) } diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index fc6cb075f..bdd4aca32 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"] 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/lib.rs b/crates/rust-client/src/lib.rs index 756d83668..1744ab23c 100644 --- a/crates/rust-client/src/lib.rs +++ b/crates/rust-client/src/lib.rs @@ -80,7 +80,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), @@ -99,8 +99,6 @@ #[macro_use] extern crate alloc; -use alloc::boxed::Box; - #[cfg(feature = "std")] extern crate std; @@ -210,7 +208,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, @@ -246,7 +244,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, @@ -287,7 +285,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 df618e992..87ea92b8c 100644 --- a/crates/rust-client/src/mock.rs +++ b/crates/rust-client/src/mock.rs @@ -208,7 +208,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 { @@ -224,7 +224,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], @@ -239,7 +239,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> { @@ -261,7 +261,7 @@ impl NodeRpcClient for MockRpcApi { 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> { // assume all private notes for now let hit_notes = note_ids.iter().filter_map(|id| self.notes.get(id)); let mut return_notes = vec![]; @@ -276,23 +276,20 @@ 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 Ok(()) } - async fn get_account_update( - &mut self, - _account_id: AccountId, - ) -> Result { + async fn get_account_update(&self, _account_id: AccountId) -> Result { 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 @@ -300,7 +297,7 @@ impl NodeRpcClient for MockRpcApi { } async fn check_nullifiers_by_prefix( - &mut self, + &self, _prefix: &[u16], ) -> Result, RpcError> { // Always return an empty list for now since it's only used when importing @@ -322,9 +319,9 @@ pub async fn create_test_client() -> (MockClient, MockRpcApi) { let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng); 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), true); + let client = MockClient::new(arc_rpc_api, rng, store, Arc::new(authenticator), true); (client, rpc_api) } diff --git a/crates/rust-client/src/note/import.rs b/crates/rust-client/src/note/import.rs index 248218966..4b490a0af 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/rpc/mod.rs b/crates/rust-client/src/rpc/mod.rs index a8b92a083..27fc0b174 100644 --- a/crates/rust-client/src/rpc/mod.rs +++ b/crates/rust-client/src/rpc/mod.rs @@ -101,7 +101,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>; @@ -112,7 +112,7 @@ 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>; @@ -122,7 +122,7 @@ pub trait NodeRpcClient { /// 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. @@ -137,7 +137,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], @@ -148,13 +148,10 @@ pub trait NodeRpcClient { /// endpoint. /// /// - `account_id` is the ID of the wanted account. - async fn get_account_update( - &mut self, - account_id: AccountId, - ) -> Result; + async fn get_account_update(&self, account_id: AccountId) -> Result; async fn sync_notes( - &mut self, + &self, block_num: BlockNumber, note_tags: &[NoteTag], ) -> Result; @@ -162,7 +159,7 @@ pub trait NodeRpcClient { /// Fetches the nullifiers corresponding to a list of prefixes using the /// `/CheckNullifiersByPrefix` RPC endpoint. async fn check_nullifiers_by_prefix( - &mut self, + &self, prefix: &[u16], ) -> Result, RpcError>; @@ -173,7 +170,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; @@ -183,7 +180,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, ) -> Result, RpcError> { let nullifiers = @@ -198,7 +195,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> { @@ -229,7 +226,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_update]. async fn get_updated_public_accounts( - &mut self, + &self, local_accounts: &[&AccountHeader], ) -> Result, RpcError> { let mut public_accounts = vec![]; @@ -253,7 +250,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?; @@ -266,7 +263,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 06e413ce3..3d11a0e21 100644 --- a/crates/rust-client/src/rpc/tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/tonic_client/mod.rs @@ -17,6 +17,7 @@ use miden_objects::{ Digest, }; use miden_tx::utils::Serializable; +use tokio::sync::{RwLock, RwLockWriteGuard}; use tonic::transport::Channel; use tracing::info; @@ -45,7 +46,7 @@ use crate::{rpc::generated::requests::GetBlockHeaderByNumberRequest, transaction /// /// Wraps the `ApiClient` which defers establishing a connection with a node until necessary. pub struct TonicRpcClient { - rpc_api: Option>, + rpc_api: RwLock>>, endpoint: String, timeout_ms: u64, } @@ -55,39 +56,45 @@ impl TonicRpcClient { /// with the given timeout in milliseconds. pub fn new(endpoint: &Endpoint, timeout_ms: u64) -> TonicRpcClient { TonicRpcClient { - rpc_api: None, + rpc_api: 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`. - async fn rpc_api(&mut self) -> Result<&mut ApiClient, RpcError> { - if self.rpc_api.is_some() { - Ok(self.rpc_api.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 rpc_api = self.rpc_api.write().await; + if rpc_api.is_none() { let endpoint = tonic::transport::Endpoint::try_from(self.endpoint.clone()) .map_err(|err| RpcError::ConnectionError(err.to_string()))? .timeout(Duration::from_millis(self.timeout_ms)); - let rpc_api = ApiClient::connect(endpoint) + let connected_rpc_api = ApiClient::connect(endpoint) .await .map_err(|err| RpcError::ConnectionError(err.to_string()))?; - Ok(self.rpc_api.insert(rpc_api)) + rpc_api.replace(connected_rpc_api); } + + Ok(rpc_api) } } #[async_trait(?Send)] 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(), @@ -99,7 +106,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> { @@ -110,7 +117,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(), @@ -146,11 +155,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(), @@ -199,7 +211,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], @@ -218,7 +230,9 @@ impl NodeRpcClient for TonicRpcClient { nullifiers, }; - 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()) })?; @@ -236,13 +250,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_update( - &mut self, - account_id: AccountId, - ) -> Result { + async fn get_account_update(&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( @@ -291,7 +303,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 { @@ -315,7 +327,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 @@ -375,7 +389,7 @@ impl NodeRpcClient for TonicRpcClient { } async fn sync_notes( - &mut self, + &self, block_num: BlockNumber, note_tags: &[NoteTag], ) -> Result { @@ -383,7 +397,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()) @@ -393,14 +408,17 @@ impl NodeRpcClient for TonicRpcClient { } async fn check_nullifiers_by_prefix( - &mut self, + &self, prefixes: &[u16], ) -> Result, RpcError> { let request = CheckNullifiersByPrefixRequest { nullifiers: prefixes.iter().map(|&x| u32::from(x)).collect(), prefix_len: 16, }; - 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(), diff --git a/crates/rust-client/src/rpc/web_tonic_client/mod.rs b/crates/rust-client/src/rpc/web_tonic_client/mod.rs index fcb808438..9dfd28d42 100644 --- a/crates/rust-client/src/rpc/web_tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/web_tonic_client/mod.rs @@ -54,7 +54,7 @@ impl WebTonicRpcClient { #[async_trait(?Send)] impl NodeRpcClient for WebTonicRpcClient { async fn submit_proven_transaction( - &mut self, + &self, proven_transaction: ProvenTransaction, ) -> Result<(), RpcError> { let mut query_client = self.build_api_client(); @@ -74,7 +74,7 @@ impl NodeRpcClient for WebTonicRpcClient { } async fn get_block_header_by_number( - &mut self, + &self, block_num: Option, include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError> { @@ -123,7 +123,7 @@ impl NodeRpcClient for WebTonicRpcClient { 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 mut query_client = self.build_api_client(); let request = GetNotesByIdRequest { @@ -177,7 +177,7 @@ impl NodeRpcClient for WebTonicRpcClient { /// 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], @@ -203,7 +203,7 @@ impl NodeRpcClient for WebTonicRpcClient { } async fn sync_notes( - &mut self, + &self, block_num: BlockNumber, note_tags: &[NoteTag], ) -> Result { @@ -231,7 +231,7 @@ impl NodeRpcClient for WebTonicRpcClient { /// - 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 { @@ -326,10 +326,7 @@ impl NodeRpcClient for WebTonicRpcClient { /// - The answer had a `None` for its account, or the account had a `None` at the `details` /// field. /// - There is an error during [Account] deserialization. - async fn get_account_update( - &mut self, - account_id: AccountId, - ) -> Result { + async fn get_account_update(&self, account_id: AccountId) -> Result { let mut query_client = self.build_api_client(); let request = GetAccountDetailsRequest { account_id: Some(account_id.into()) }; @@ -371,7 +368,7 @@ impl NodeRpcClient for WebTonicRpcClient { } async fn check_nullifiers_by_prefix( - &mut self, + &self, prefixes: &[u16], ) -> Result, RpcError> { let mut query_client = self.build_api_client(); diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index 074a350b8..b1603c4a7 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -148,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/tests.rs b/crates/rust-client/src/tests.rs index cfdf4bf56..8e9a1e20c 100644 --- a/crates/rust-client/src/tests.rs +++ b/crates/rust-client/src/tests.rs @@ -343,7 +343,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) = create_test_client().await; + let (mut client, rpc_api) = 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).await.unwrap(); diff --git a/crates/web-client/src/lib.rs b/crates/web-client/src/lib.rs index e132b93b6..c5da023ad 100644 --- a/crates/web-client/src/lib.rs +++ b/crates/web-client/src/lib.rs @@ -78,7 +78,7 @@ impl WebClient { .map_err(|_| JsValue::from_str("Failed to initialize WebStore"))?; let web_store = Arc::new(web_store); let authenticator = Arc::new(StoreAuthenticator::new_with_rng(web_store.clone(), rng)); - let web_rpc_client = Box::new(WebTonicRpcClient::new( + let web_rpc_client = Arc::new(WebTonicRpcClient::new( &node_url.unwrap_or_else(|| miden_client::rpc::Endpoint::testnet().to_string()), )); 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/tests/integration/common.rs b/tests/integration/common.rs index 52073a6a5..2b6e7cf2b 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -58,7 +58,7 @@ pub async fn create_test_client() -> TestClient { let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng); 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 f73113621..f4577eec6 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -800,7 +800,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_update(basic_wallet_1.id()).await.unwrap(); let details2 = rpc_api.get_account_update(basic_wallet_2.id()).await.unwrap();