diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f974ef6..48231e28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Changes +* Refactored RPC functions and structs to improve code quality (#616). * [BREAKING] Added support for new two `Felt` account ID (#639). * [BREAKING] Removed unnecessary methods from `Client` (#631). * [BREAKING] Use `thiserror` 2.0 to derive errors (#623). diff --git a/Cargo.lock b/Cargo.lock index 360faedcc..2288a4324 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,7 +192,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -203,7 +203,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -451,9 +451,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -541,7 +541,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -829,7 +829,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1025,7 +1025,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1519,7 +1519,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1782,7 +1782,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.5", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2053,7 +2053,7 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "syn 2.0.93", + "syn 2.0.91", "terminal_size 0.3.0", "textwrap", "trybuild", @@ -2068,7 +2068,7 @@ checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2143,7 +2143,7 @@ checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2248,7 +2248,7 @@ checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2369,7 +2369,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2461,7 +2461,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2693,7 +2693,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2738,7 +2738,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3059,7 +3059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3103,7 +3103,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "version_check", "yansi", ] @@ -3149,7 +3149,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.93", + "syn 2.0.91", "tempfile", ] @@ -3163,7 +3163,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3505,9 +3505,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -3617,7 +3617,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3833,7 +3833,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3876,9 +3876,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.93" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", @@ -3905,7 +3905,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4031,7 +4031,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4042,7 +4042,7 @@ checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4132,7 +4132,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4257,7 +4257,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4402,7 +4402,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4711,7 +4711,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "wasm-bindgen-shared", ] @@ -4746,7 +4746,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4861,7 +4861,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4872,7 +4872,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5113,7 +5113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be43529f43f70306437d2c2c9f9e2b3a4d39b42e86702d8d7577f2357ea32fa6" dependencies = [ "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5210,7 +5210,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "synstructure", ] @@ -5232,7 +5232,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5252,7 +5252,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "synstructure", ] @@ -5275,7 +5275,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] diff --git a/crates/rust-client/src/accounts.rs b/crates/rust-client/src/accounts.rs index 71998d1b2..12dcce9c2 100644 --- a/crates/rust-client/src/accounts.rs +++ b/crates/rust-client/src/accounts.rs @@ -164,7 +164,7 @@ impl Client { /// Contains account changes to apply to the store. pub struct AccountUpdates { /// Updated public accounts. - updated_onchain_accounts: Vec, + updated_public_accounts: Vec, /// Node account hashes that don't match the tracked information. mismatched_offchain_accounts: Vec<(AccountId, Digest)>, } @@ -172,18 +172,18 @@ pub struct AccountUpdates { impl AccountUpdates { /// Creates a new instance of `AccountUpdates`. pub fn new( - updated_onchain_accounts: Vec, + updated_public_accounts: Vec, mismatched_offchain_accounts: Vec<(AccountId, Digest)>, ) -> Self { Self { - updated_onchain_accounts, + updated_public_accounts, mismatched_offchain_accounts, } } /// Returns the updated public accounts. - pub fn updated_onchain_accounts(&self) -> &[Account] { - &self.updated_onchain_accounts + pub fn updated_public_accounts(&self) -> &[Account] { + &self.updated_public_accounts } /// Returns the mismatched offchain accounts. diff --git a/crates/rust-client/src/mock.rs b/crates/rust-client/src/mock.rs index 57985a459..4f83f6d8d 100644 --- a/crates/rust-client/src/mock.rs +++ b/crates/rust-client/src/mock.rs @@ -32,7 +32,7 @@ use crate::{ rpc::{ domain::{ accounts::{AccountDetails, AccountProofs}, - notes::{NoteDetails, NoteInclusionDetails, NoteSyncInfo}, + notes::{NetworkNote, NoteSyncInfo}, sync::StateSyncInfo, }, generated::{ @@ -258,26 +258,15 @@ 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(&mut self, note_ids: &[NoteId]) -> Result, RpcError> { // assume all off-chain notes for now let hit_notes = note_ids.iter().filter_map(|id| self.notes.get(id)); let mut return_notes = vec![]; for note in hit_notes { - let inclusion_details = NoteInclusionDetails::new( - note.proof() - .expect("Note should have an inclusion proof") - .location() - .block_num(), - note.proof() - .expect("Note should have an inclusion proof") - .location() - .node_index_in_block(), - note.proof().expect("Note should have an inclusion proof").note_path().clone(), - ); - return_notes.push(NoteDetails::Private( + return_notes.push(NetworkNote::Private( note.id(), *note.note().metadata(), - inclusion_details, + note.proof().expect("Note should have an inclusion proof").clone(), )); } Ok(return_notes) diff --git a/crates/rust-client/src/notes/import.rs b/crates/rust-client/src/notes/import.rs index b1d2f35ea..76b6557a7 100644 --- a/crates/rust-client/src/notes/import.rs +++ b/crates/rust-client/src/notes/import.rs @@ -6,7 +6,7 @@ use miden_objects::{ }; use crate::{ - rpc::domain::notes::NoteDetails as RpcNoteDetails, + rpc::{domain::notes::NetworkNote, RpcError}, store::{input_note_states::ExpectedNoteState, InputNoteRecord, InputNoteState}, sync::NoteTagRecord, Client, ClientError, @@ -84,27 +84,17 @@ impl Client { previous_note: Option, id: NoteId, ) -> Result, ClientError> { - let mut chain_notes = self.rpc_api.get_notes_by_id(&[id]).await?; - if chain_notes.is_empty() { - return Err(ClientError::NoteNotFoundOnChain(id)); - } - - let note_details: RpcNoteDetails = - chain_notes.pop().expect("chain_notes should have at least one element"); - - let inclusion_details = note_details.inclusion_details(); + let network_note = self.rpc_api.get_note_by_id(id).await.map_err(|err| match err { + RpcError::NoteNotFound(note_id) => ClientError::NoteNotFoundOnChain(note_id), + err => ClientError::RpcError(err), + })?; - // Add the inclusion proof to the imported note - let inclusion_proof = NoteInclusionProof::new( - inclusion_details.block_num, - inclusion_details.note_index, - inclusion_details.merkle_path.clone(), - )?; + let inclusion_proof = network_note.inclusion_proof().clone(); match previous_note { Some(mut previous_note) => { if previous_note - .inclusion_proof_received(inclusion_proof, *note_details.metadata())? + .inclusion_proof_received(inclusion_proof, *network_note.metadata())? { self.store.remove_note_tag((&previous_note).try_into()?).await?; @@ -114,16 +104,16 @@ impl Client { } }, None => { - let node_note = match note_details { - RpcNoteDetails::Public(note, _) => note, - RpcNoteDetails::Private(..) => { + let network_note = match network_note { + NetworkNote::Public(note, _) => note, + NetworkNote::Private(..) => { return Err(ClientError::NoteImportError( "Incomplete imported note is private".to_string(), )) }, }; - self.import_note_record_by_proof(previous_note, node_note, inclusion_proof) + self.import_note_record_by_proof(previous_note, network_note, inclusion_proof) .await }, } diff --git a/crates/rust-client/src/rpc/domain/accounts.rs b/crates/rust-client/src/rpc/domain/accounts.rs index 2cdb3fe0d..f205f9bd2 100644 --- a/crates/rust-client/src/rpc/domain/accounts.rs +++ b/crates/rust-client/src/rpc/domain/accounts.rs @@ -14,14 +14,12 @@ use miden_tx::utils::{Deserializable, Serializable, ToHex}; use thiserror::Error; use crate::rpc::{ - errors::RpcConversionError, - generated::{ - account::{AccountHeader as ProtoAccountHeader, AccountId as ProtoAccountId}, - requests::get_account_proofs_request, - responses::{AccountStateHeader as ProtoAccountStateHeader, StorageSlotMapProof}, - }, - RpcError, -}; + errors::RpcConversionError, + generated::{ + account::{AccountHeader as ProtoAccountHeader, AccountId as ProtoAccountId}, requests::get_account_proofs_request, responses::{AccountStateHeader as ProtoAccountStateHeader, StorageSlotMapProof} + }, + RpcError, + }; // ACCOUNT DETAILS // ================================================================================================ diff --git a/crates/rust-client/src/rpc/domain/notes.rs b/crates/rust-client/src/rpc/domain/notes.rs index cbe4def97..89c5c198a 100644 --- a/crates/rust-client/src/rpc/domain/notes.rs +++ b/crates/rust-client/src/rpc/domain/notes.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use miden_objects::{ crypto::merkle::MerklePath, - notes::{Note, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}, + notes::{Note, NoteExecutionHint, NoteId, NoteInclusionProof, NoteMetadata, NoteTag, NoteType}, BlockHeader, Digest, Felt, }; @@ -161,59 +161,41 @@ impl CommittedNote { } } -/// Contains information related to the note inclusion, but not related to the block header -/// that contains the note. -pub struct NoteInclusionDetails { - /// Block number in which the note was included. - pub block_num: u32, - /// Index of the note in the block's note tree. - pub note_index: u16, - /// Merkle path to the note root of the block header. - pub merkle_path: MerklePath, -} - -impl NoteInclusionDetails { - /// Creates a new [NoteInclusionDetails]. - pub fn new(block_num: u32, note_index: u16, merkle_path: MerklePath) -> Self { - Self { block_num, note_index, merkle_path } - } -} - -// NOTE DETAILS +// NETWORK NOTE // ================================================================================================ /// Describes the possible responses from the `GetNotesById` endpoint for a single note. #[allow(clippy::large_enum_variant)] -pub enum NoteDetails { - /// Details for a private note only include its [NoteMetadata] and [NoteInclusionDetails]. +pub enum NetworkNote { + /// Details for a private note only include its [NoteMetadata] and [NoteInclusionProof]. /// Other details needed to consume the note are expected to be stored locally, off-chain. - Private(NoteId, NoteMetadata, NoteInclusionDetails), - /// Contains the full [Note] object alongside its [NoteInclusionDetails]. - Public(Note, NoteInclusionDetails), + Private(NoteId, NoteMetadata, NoteInclusionProof), + /// Contains the full [Note] object alongside its [NoteInclusionProof]. + Public(Note, NoteInclusionProof), } -impl NoteDetails { +impl NetworkNote { /// Returns the note's inclusion details. - pub fn inclusion_details(&self) -> &NoteInclusionDetails { + pub fn inclusion_proof(&self) -> &NoteInclusionProof { match self { - NoteDetails::Private(_, _, inclusion_details) => inclusion_details, - NoteDetails::Public(_, inclusion_details) => inclusion_details, + NetworkNote::Private(_, _, inclusion_proof) => inclusion_proof, + NetworkNote::Public(_, inclusion_proof) => inclusion_proof, } } /// Returns the note's metadata. pub fn metadata(&self) -> &NoteMetadata { match self { - NoteDetails::Private(_, metadata, _) => metadata, - NoteDetails::Public(note, _) => note.metadata(), + NetworkNote::Private(_, metadata, _) => metadata, + NetworkNote::Public(note, _) => note.metadata(), } } /// Returns the note's ID. pub fn id(&self) -> NoteId { match self { - NoteDetails::Private(id, ..) => *id, - NoteDetails::Public(note, _) => note.id(), + NetworkNote::Private(id, ..) => *id, + NetworkNote::Public(note, _) => note.id(), } } } diff --git a/crates/rust-client/src/rpc/errors.rs b/crates/rust-client/src/rpc/errors.rs index d6a85c2b8..4a2488f71 100644 --- a/crates/rust-client/src/rpc/errors.rs +++ b/crates/rust-client/src/rpc/errors.rs @@ -1,6 +1,6 @@ use alloc::string::{String, ToString}; -use miden_objects::{accounts::AccountId, utils::DeserializationError, NoteError}; +use miden_objects::{accounts::AccountId, notes::NoteId, utils::DeserializationError, NoteError}; use thiserror::Error; // RPC ERROR @@ -18,6 +18,8 @@ pub enum RpcError { ExpectedDataMissing(String), #[error("rpc api response is invalid: {0}")] InvalidResponse(String), + #[error("note with id {0} was not found")] + NoteNotFound(NoteId), #[error("rpc request failed for {0}: {1}")] RequestError(String, String), } diff --git a/crates/rust-client/src/rpc/mod.rs b/crates/rust-client/src/rpc/mod.rs index 3d43f0deb..b31320464 100644 --- a/crates/rust-client/src/rpc/mod.rs +++ b/crates/rust-client/src/rpc/mod.rs @@ -2,17 +2,22 @@ //! Remote Procedure Calls (RPC). It facilitates syncing with the network and submitting //! transactions. -use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; +use alloc::{ + boxed::Box, + collections::BTreeSet, + string::String, + vec::Vec, +}; use core::fmt; use async_trait::async_trait; use domain::{ accounts::{AccountDetails, AccountProofs}, - notes::{NoteDetails, NoteSyncInfo}, + notes::{NetworkNote, NoteSyncInfo}, sync::StateSyncInfo, }; use miden_objects::{ - accounts::{AccountCode, AccountId}, + accounts::{Account, AccountCode, AccountHeader, AccountId}, crypto::merkle::MmrProof, notes::{NoteId, NoteTag, Nullifier}, transaction::ProvenTransaction, @@ -42,7 +47,11 @@ mod web_tonic_client; #[cfg(feature = "web-tonic")] pub use web_tonic_client::WebTonicRpcClient; -use crate::{sync::get_nullifier_prefix, transactions::ForeignAccount}; +use crate::{ + store::{input_note_states::UnverifiedNoteState, InputNoteRecord}, + sync::get_nullifier_prefix, + transactions::ForeignAccount, +}; // NODE RPC CLIENT TRAIT // ================================================================================================ @@ -78,7 +87,7 @@ pub trait NodeRpcClient { /// For any NoteType::Private note, the return data is only the /// [miden_objects::notes::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(&mut self, note_ids: &[NoteId]) -> Result, RpcError>; /// Fetches info from the node necessary to perform a state sync using the /// `/SyncState` RPC endpoint. @@ -122,7 +131,12 @@ pub trait NodeRpcClient { prefix: &[u16], ) -> Result, RpcError>; - /// Fetches the current account state, using the `/GetAccountProofs` RPC endpoint. + /// Fetches the account data needed to perform a Foreign Procedure Invocation (FPI) on the + /// specified foreign accounts, using the `GetAccountProofs` endpoint. + /// + /// The `code_commitments` parameter is a list of known code hashes + /// 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, account_storage_requests: &BTreeSet, @@ -142,6 +156,85 @@ pub trait NodeRpcClient { Ok(nullifiers.iter().find(|(n, _)| n == nullifier).map(|(_, block_num)| *block_num)) } + + /// Fetches public note-related data for a list of [NoteId] and builds [InputNoteRecord]s with + /// it. If a note is not found or it's private, it is ignored and will not be included in the + /// returned list. + /// + /// The default implementation of this method uses [NodeRpcClient::get_notes_by_id]. + async fn get_public_note_records( + &mut self, + note_ids: &[NoteId], + current_timestamp: Option, + ) -> Result, RpcError> { + let note_details = self.get_notes_by_id(note_ids).await?; + + let mut public_notes = vec![]; + for detail in note_details { + if let NetworkNote::Public(note, inclusion_proof) = detail { + let state = UnverifiedNoteState { + metadata: *note.metadata(), + inclusion_proof, + } + .into(); + let note = InputNoteRecord::new(note.into(), current_timestamp, state); + + public_notes.push(note); + } + } + + Ok(public_notes) + } + + /// Fetches the public accounts that have been updated since the last known state of the + /// accounts. + /// + /// The `local_accounts` parameter is a list of account headers that the client has + /// stored locally and that it wants to check for updates. If an account is private or didn't + /// 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, + local_accounts: &[&AccountHeader], + ) -> Result, RpcError> { + let mut public_accounts = vec![]; + + for local_account in local_accounts { + let response = self.get_account_update(local_account.id()).await?; + + if let AccountDetails::Public(account, _) = response { + // We should only return an account if it's newer, otherwise we ignore it + if account.nonce().as_int() > local_account.nonce().as_int() { + public_accounts.push(account); + } + } + } + + Ok(public_accounts) + } + + /// Given a block number, fetches the block header corresponding to that height from the node + /// along with the MMR proof. + /// + /// The default implementation of this method uses [NodeRpcClient::get_block_header_by_number]. + async fn get_block_header_with_proof( + &mut self, + block_num: u32, + ) -> Result<(BlockHeader, MmrProof), RpcError> { + let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?; + Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?)) + } + + /// Fetches the note with the specified ID. + /// + /// The default implementation of this method uses [NodeRpcClient::get_notes_by_id]. + /// + /// 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 { + let notes = self.get_notes_by_id(&[note_id]).await?; + notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id)) + } } // RPC API ENDPOINT diff --git a/crates/rust-client/src/rpc/tonic_client/mod.rs b/crates/rust-client/src/rpc/tonic_client/mod.rs index 2c6994ee0..2fb3c8c38 100644 --- a/crates/rust-client/src/rpc/tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/tonic_client/mod.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use miden_objects::{ accounts::{Account, AccountCode, AccountId}, crypto::merkle::{MerklePath, MmrProof}, - notes::{Note, NoteId, NoteTag, Nullifier}, + notes::{Note, NoteId, NoteInclusionProof, NoteTag, Nullifier}, transaction::ProvenTransaction, utils::Deserializable, BlockHeader, Digest, @@ -22,7 +22,7 @@ use tracing::info; use super::{ domain::{ accounts::{AccountProof, AccountProofs, AccountUpdateSummary}, - notes::NoteInclusionDetails, + notes::NetworkNote, }, generated::{ requests::{ @@ -32,8 +32,8 @@ use super::{ }, rpc::api_client::ApiClient, }, - AccountDetails, Endpoint, NodeRpcClient, NodeRpcClientEndpoint, NoteDetails, NoteSyncInfo, - RpcError, StateSyncInfo, + AccountDetails, Endpoint, NodeRpcClient, NodeRpcClientEndpoint, NoteSyncInfo, RpcError, + StateSyncInfo, }; use crate::{ rpc::generated::requests::GetBlockHeaderByNumberRequest, transactions::ForeignAccount, @@ -147,7 +147,7 @@ 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(&mut self, note_ids: &[NoteId]) -> Result, RpcError> { let request = GetNotesByIdRequest { note_ids: note_ids.iter().map(|id| id.inner().into()).collect(), }; @@ -168,7 +168,7 @@ impl NodeRpcClient for TonicRpcClient { .ok_or(RpcError::ExpectedDataMissing("Notes.MerklePath".into()))? .try_into()?; - NoteInclusionDetails::new(note.block_num, note.note_index as u16, merkle_path) + NoteInclusionProof::new(note.block_num, note.note_index as u16, merkle_path)? }; let note = match note.details { @@ -176,7 +176,7 @@ impl NodeRpcClient for TonicRpcClient { Some(details) => { let note = Note::read_from_bytes(&details)?; - NoteDetails::Public(note, inclusion_details) + NetworkNote::Public(note, inclusion_details) }, // Off-chain notes do not have details None => { @@ -190,7 +190,7 @@ impl NodeRpcClient for TonicRpcClient { .ok_or(RpcError::ExpectedDataMissing("Notes.NoteId".into()))? .try_into()?; - NoteDetails::Private(NoteId::from(note_id), note_metadata, inclusion_details) + NetworkNote::Private(NoteId::from(note_id), note_metadata, inclusion_details) }, }; response_notes.push(note) @@ -288,7 +288,7 @@ impl NodeRpcClient for TonicRpcClient { /// /// This function will return an error if: /// - /// - One of the requested Accounts isn't public, or isn't returned by the node. + /// - One of the requested Accounts isn't returned by the node. /// - There was an error sending the request to the node. /// - The answer had a `None` for one of the expected fields. /// - There is an error during storage deserialization. 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 95b58d337..a05a5bf1b 100644 --- a/crates/rust-client/src/rpc/web_tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/web_tonic_client/mod.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use miden_objects::{ accounts::{Account, AccountCode, AccountId}, crypto::merkle::{MerklePath, MmrProof}, - notes::{Note, NoteId, NoteTag, Nullifier}, + notes::{Note, NoteId, NoteInclusionProof, NoteTag, Nullifier}, transaction::ProvenTransaction, utils::Deserializable, BlockHeader, Digest, @@ -20,7 +20,7 @@ use tonic_web_wasm_client::Client; use super::{ domain::{ accounts::{AccountDetails, AccountProof, AccountProofs, AccountUpdateSummary}, - notes::{NoteDetails, NoteInclusionDetails, NoteSyncInfo}, + notes::{NetworkNote, NoteSyncInfo}, sync::StateSyncInfo, }, generated::{ @@ -122,7 +122,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(&mut self, note_ids: &[NoteId]) -> Result, RpcError> { let mut query_client = self.build_api_client(); let request = GetNotesByIdRequest { @@ -145,7 +145,7 @@ impl NodeRpcClient for WebTonicRpcClient { .ok_or(RpcError::ExpectedDataMissing("Notes.MerklePath".into()))? .try_into()?; - NoteInclusionDetails::new(note.block_num, note.note_index as u16, merkle_path) + NoteInclusionProof::new(note.block_num, note.note_index as u16, merkle_path)? }; let note = match note.details { @@ -153,7 +153,7 @@ impl NodeRpcClient for WebTonicRpcClient { Some(details) => { let note = Note::read_from_bytes(&details)?; - NoteDetails::Public(note, inclusion_details) + NetworkNote::Public(note, inclusion_details) }, // Off-chain notes do not have details None => { @@ -166,7 +166,7 @@ impl NodeRpcClient for WebTonicRpcClient { .ok_or(RpcError::ExpectedDataMissing("Notes.NoteId".into()))? .try_into()?; - NoteDetails::Private(NoteId::from(note_id), note_metadata, inclusion_details) + NetworkNote::Private(NoteId::from(note_id), note_metadata, inclusion_details) }, }; response_notes.push(note) diff --git a/crates/rust-client/src/store/note_record/mod.rs b/crates/rust-client/src/store/note_record/mod.rs index 78addbc77..81efce71a 100644 --- a/crates/rust-client/src/store/note_record/mod.rs +++ b/crates/rust-client/src/store/note_record/mod.rs @@ -27,8 +27,9 @@ pub use input_note_record::{InputNoteRecord, InputNoteState}; pub use output_note_record::{NoteExportType, OutputNoteRecord, OutputNoteState}; pub mod input_note_states { pub use super::input_note_record::{ - CommittedNoteState, ConsumedAuthenticatedLocalNoteState, ExpectedNoteState, + CommittedNoteState, ConsumedAuthenticatedLocalNoteState, ExpectedNoteState, InputNoteState, InvalidNoteState, ProcessingAuthenticatedNoteState, ProcessingUnauthenticatedNoteState, + UnverifiedNoteState, }; } diff --git a/crates/rust-client/src/store/sqlite_store/sync.rs b/crates/rust-client/src/store/sqlite_store/sync.rs index dab152f13..98af1ebc3 100644 --- a/crates/rust-client/src/store/sqlite_store/sync.rs +++ b/crates/rust-client/src/store/sqlite_store/sync.rs @@ -132,8 +132,8 @@ impl SqliteStore { // Marc transactions as discarded Self::mark_transactions_as_discarded(&tx, &discarded_transactions)?; - // Update onchain accounts on the db that have been updated onchain - for account in updated_accounts.updated_onchain_accounts() { + // Update public accounts on the db that have been updated onchain + for account in updated_accounts.updated_public_accounts() { update_account(&tx, account)?; } 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 b7a83aca4..0f4643ced 100644 --- a/crates/rust-client/src/store/web_store/sync/mod.rs +++ b/crates/rust-client/src/store/web_store/sync/mod.rs @@ -157,8 +157,8 @@ impl WebStore { .collect(); // TODO: LOP INTO idxdb_apply_state_sync call - // Update onchain accounts on the db that have been updated onchain - for account in updated_accounts.updated_onchain_accounts() { + // Update public accounts on the db that have been updated onchain + for account in updated_accounts.updated_public_accounts() { update_account(&account.clone()).await.unwrap(); } diff --git a/crates/rust-client/src/sync/block_headers.rs b/crates/rust-client/src/sync/block_headers.rs index 80051ff73..fdd550dd3 100644 --- a/crates/rust-client/src/sync/block_headers.rs +++ b/crates/rust-client/src/sync/block_headers.rs @@ -152,11 +152,8 @@ impl Client { let (block_header, _) = self.store.get_block_header_by_num(block_num).await?; return Ok(block_header); } - let (block_header, mmr_proof) = - self.rpc_api.get_block_header_by_number(Some(block_num), true).await?; + let (block_header, mmr_proof) = self.rpc_api.get_block_header_with_proof(block_num).await?; - let mmr_proof = mmr_proof - .expect("NodeRpcApi::get_block_header_by_number() should have returned an MMR proof"); // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR // might be of a forest arbitrarily higher let path_nodes = adjust_merkle_path_for_forest( diff --git a/crates/rust-client/src/sync/mod.rs b/crates/rust-client/src/sync/mod.rs index d59d209f0..7c2b394c5 100644 --- a/crates/rust-client/src/sync/mod.rs +++ b/crates/rust-client/src/sync/mod.rs @@ -17,19 +17,10 @@ use tracing::info; use crate::{ accounts::AccountUpdates, notes::NoteUpdates, - rpc::{ - domain::{ - accounts::AccountDetails, - notes::{CommittedNote, NoteDetails}, - nullifiers::NullifierUpdate, - transactions::TransactionUpdate, - }, - RpcError, - }, - store::{ - input_note_states::CommittedNoteState, InputNoteRecord, NoteFilter, OutputNoteRecord, - TransactionFilter, + rpc::domain::{ + notes::CommittedNote, nullifiers::NullifierUpdate, transactions::TransactionUpdate, }, + store::{InputNoteRecord, NoteFilter, OutputNoteRecord, TransactionFilter}, Client, ClientError, }; @@ -251,11 +242,11 @@ impl Client { let note_updates = committed_note_updates.combine_with(consumed_note_updates); - let (onchain_accounts, offchain_accounts): (Vec<_>, Vec<_>) = + let (public_accounts, offchain_accounts): (Vec<_>, Vec<_>) = accounts.into_iter().partition(|account_header| account_header.id().is_public()); - let updated_onchain_accounts = self - .get_updated_onchain_accounts(&response.account_hash_updates, &onchain_accounts) + let updated_public_accounts = self + .get_updated_public_accounts(&response.account_hash_updates, &public_accounts) .await?; let mismatched_offchain_accounts = self @@ -283,7 +274,7 @@ impl Client { note_updates.new_input_notes().iter().map(|n| n.id()).collect(), note_updates.committed_note_ids().into_iter().collect(), note_updates.consumed_note_ids().into_iter().collect(), - updated_onchain_accounts.iter().map(|acc| acc.id()).collect(), + updated_public_accounts.iter().map(|acc| acc.id()).collect(), mismatched_offchain_accounts.iter().map(|(acc_id, _)| *acc_id).collect(), transactions_to_commit.iter().map(|tx| tx.transaction_id).collect(), ); @@ -295,7 +286,7 @@ impl Client { new_mmr_peaks: new_peaks, new_authentication_nodes, updated_accounts: AccountUpdates::new( - updated_onchain_accounts, + updated_public_accounts, mismatched_offchain_accounts, ), block_has_relevant_notes: incoming_block_has_relevant_notes, @@ -522,38 +513,15 @@ impl Client { } info!("Getting note details for notes that are not being tracked."); - let notes_data = self.rpc_api.get_notes_by_id(query_notes).await?; - let mut return_notes = Vec::with_capacity(query_notes.len()); - for note_data in notes_data { - match note_data { - NoteDetails::Private(id, ..) => { - // TODO: Is there any benefit to not ignoring these? In any case we do not have - // the recipient which is mandatory right now. - info!("Note {} is private but the client is not tracking it, ignoring.", id); - }, - NoteDetails::Public(note, inclusion_proof) => { - info!("Retrieved details for Note ID {}.", note.id()); - let inclusion_proof = NoteInclusionProof::new( - block_header.block_num(), - inclusion_proof.note_index, - inclusion_proof.merkle_path, - ) - .map_err(ClientError::NoteError)?; - let metadata = *note.metadata(); - - return_notes.push(InputNoteRecord::new( - note.into(), - self.store.get_current_timestamp(), - CommittedNoteState { - metadata, - inclusion_proof, - block_note_root: block_header.note_root(), - } - .into(), - )) - }, - } + let mut return_notes = self + .rpc_api + .get_public_note_records(query_notes, self.store.get_current_timestamp()) + .await?; + + for note in return_notes.iter_mut() { + note.block_header_received(*block_header)?; } + Ok(return_notes) } @@ -579,35 +547,27 @@ impl Client { Ok(transactions) } - async fn get_updated_onchain_accounts( + async fn get_updated_public_accounts( &mut self, account_updates: &[(AccountId, Digest)], - current_onchain_accounts: &[AccountHeader], + current_public_accounts: &[AccountHeader], ) -> Result, ClientError> { - let mut accounts_to_update: Vec = Vec::new(); - for (remote_account_id, remote_account_hash) in account_updates { + let mut mismatched_public_accounts = vec![]; + + for (id, hash) in account_updates { // check if this updated account is tracked by the client - let current_account = current_onchain_accounts + if let Some(account) = current_public_accounts .iter() - .find(|acc| *remote_account_id == acc.id() && *remote_account_hash != acc.hash()); - - if let Some(tracked_account) = current_account { - info!("Public account hash difference detected for account with ID: {}. Fetching node for updates...", tracked_account.id()); - let account_details = self.rpc_api.get_account_update(tracked_account.id()).await?; - if let AccountDetails::Public(account, _) = account_details { - // We should only do the update if it's newer, otherwise we ignore it - if account.nonce().as_int() > tracked_account.nonce().as_int() { - accounts_to_update.push(account); - } - } else { - return Err(RpcError::AccountUpdateForPrivateAccountReceived( - account_details.account_id(), - ) - .into()); - } + .find(|acc| *id == acc.id() && *hash != acc.hash()) + { + mismatched_public_accounts.push(account); } } - Ok(accounts_to_update) + + self.rpc_api + .get_updated_public_accounts(&mismatched_public_accounts) + .await + .map_err(ClientError::RpcError) } /// Validates account hash updates and returns a vector with all the offchain account diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 6f488051c..733a55cb0 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -27,14 +27,10 @@ use tracing::info; use super::{Client, FeltRng}; use crate::{ - notes::{NoteScreener, NoteUpdates}, - rpc::domain::accounts::AccountProof, - store::{ + notes::{NoteScreener, NoteUpdates}, rpc::domain::accounts::AccountProof, store::{ input_note_states::ExpectedNoteState, InputNoteRecord, InputNoteState, NoteFilter, OutputNoteRecord, StoreError, TransactionFilter, - }, - sync::NoteTagRecord, - ClientError, + }, sync::NoteTagRecord, ClientError }; mod request; @@ -743,7 +739,7 @@ impl Client { let account_ids = foreign_accounts.iter().map(|acc| acc.account_id()); let known_account_codes = - self.store.get_foreign_account_code(account_ids.clone().collect()).await?; + self.store.get_foreign_account_code(account_ids.collect()).await?; let known_account_codes: Vec = known_account_codes.into_values().collect();