From 3dc16d5c17a362666e81f5d9518d19035f647f04 Mon Sep 17 00:00:00 2001 From: tomyrd Date: Wed, 5 Mar 2025 16:29:59 -0300 Subject: [PATCH] feat: add check nullifiers request --- crates/rust-client/src/store/mod.rs | 12 -- .../rust-client/src/store/sqlite_store/mod.rs | 13 -- .../src/store/sqlite_store/sync.rs | 19 +-- crates/rust-client/src/store/web_store/mod.rs | 10 -- .../src/store/web_store/sync/mod.rs | 10 -- crates/rust-client/src/sync/state_sync.rs | 153 +++++++++--------- 6 files changed, 81 insertions(+), 136 deletions(-) diff --git a/crates/rust-client/src/store/mod.rs b/crates/rust-client/src/store/mod.rs index 14198e9ea..c7d46ee9a 100644 --- a/crates/rust-client/src/store/mod.rs +++ b/crates/rust-client/src/store/mod.rs @@ -33,12 +33,10 @@ use miden_objects::{ block::{BlockHeader, BlockNumber}, crypto::merkle::{InOrderIndex, MmrPeaks}, note::{NoteId, NoteTag, Nullifier}, - transaction::TransactionId, Digest, Word, }; use crate::{ - note::NoteUpdates, sync::{NoteTagRecord, StateSyncUpdate}, transaction::{TransactionRecord, TransactionStoreUpdate}, }; @@ -313,16 +311,6 @@ pub trait Store: Send + Sync { /// - Storing new MMR authentication nodes. /// - Updating the tracked public accounts. async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError>; - - /// Applies nullifier updates to database. - /// Nullifiers are retrieved after completing a `StateSync`. - /// - /// This operation is temporary, to be removed as part of miden-client/650. - async fn apply_nullifiers( - &self, - note_updates: NoteUpdates, - transactions_to_discard: Vec, - ) -> Result<(), StoreError>; } // CHAIN MMR NODE FILTER diff --git a/crates/rust-client/src/store/sqlite_store/mod.rs b/crates/rust-client/src/store/sqlite_store/mod.rs index 1f94b3835..da555e80a 100644 --- a/crates/rust-client/src/store/sqlite_store/mod.rs +++ b/crates/rust-client/src/store/sqlite_store/mod.rs @@ -17,7 +17,6 @@ use miden_objects::{ block::{BlockHeader, BlockNumber}, crypto::merkle::{InOrderIndex, MmrPeaks}, note::{NoteTag, Nullifier}, - transaction::TransactionId, Digest, Word, }; use rusqlite::{types::Value, vtab::array, Connection}; @@ -28,7 +27,6 @@ use super::{ OutputNoteRecord, Store, TransactionFilter, }; use crate::{ - note::NoteUpdates, store::StoreError, sync::{NoteTagRecord, StateSyncUpdate}, transaction::{TransactionRecord, TransactionStoreUpdate}, @@ -329,17 +327,6 @@ impl Store for SqliteStore { self.interact_with_connection(SqliteStore::get_unspent_input_note_nullifiers) .await } - - async fn apply_nullifiers( - &self, - note_updates: NoteUpdates, - transactions_to_discard: Vec, - ) -> Result<(), StoreError> { - self.interact_with_connection(move |conn| { - SqliteStore::apply_nullifiers(conn, ¬e_updates, &transactions_to_discard) - }) - .await - } } // UTILS diff --git a/crates/rust-client/src/store/sqlite_store/sync.rs b/crates/rust-client/src/store/sqlite_store/sync.rs index fa35f60c5..cc1f5c674 100644 --- a/crates/rust-client/src/store/sqlite_store/sync.rs +++ b/crates/rust-client/src/store/sqlite_store/sync.rs @@ -2,13 +2,12 @@ use alloc::{collections::BTreeSet, vec::Vec}; -use miden_objects::{block::BlockNumber, note::NoteTag, transaction::TransactionId}; +use miden_objects::{block::BlockNumber, note::NoteTag}; use miden_tx::utils::{Deserializable, Serializable}; use rusqlite::{params, Connection, Transaction}; use super::SqliteStore; use crate::{ - note::NoteUpdates, store::{ sqlite_store::{ account::{lock_account, update_account}, @@ -172,22 +171,6 @@ impl SqliteStore { Ok(()) } - - pub(super) fn apply_nullifiers( - conn: &mut Connection, - note_updates: &NoteUpdates, - transactions_to_discard: &[TransactionId], - ) -> Result<(), StoreError> { - let tx = conn.transaction()?; - - apply_note_updates_tx(&tx, note_updates)?; - - Self::mark_transactions_as_discarded(&tx, transactions_to_discard)?; - - tx.commit()?; - - Ok(()) - } } pub(super) fn add_note_tag_tx(tx: &Transaction<'_>, tag: &NoteTagRecord) -> Result<(), StoreError> { diff --git a/crates/rust-client/src/store/web_store/mod.rs b/crates/rust-client/src/store/web_store/mod.rs index 451c19cfb..0fb9443db 100644 --- a/crates/rust-client/src/store/web_store/mod.rs +++ b/crates/rust-client/src/store/web_store/mod.rs @@ -14,7 +14,6 @@ use miden_objects::{ block::{BlockHeader, BlockNumber}, crypto::merkle::{InOrderIndex, MmrPeaks}, note::Nullifier, - transaction::TransactionId, Digest, Word, }; use tonic::async_trait; @@ -26,7 +25,6 @@ use super::{ OutputNoteRecord, Store, StoreError, TransactionFilter, }; use crate::{ - note::NoteUpdates, sync::{NoteTagRecord, StateSyncUpdate}, transaction::{TransactionRecord, TransactionStoreUpdate}, }; @@ -87,14 +85,6 @@ impl Store for WebStore { self.apply_state_sync(state_sync_update).await } - async fn apply_nullifiers( - &self, - note_updates: NoteUpdates, - transactions_to_discard: Vec, - ) -> Result<(), StoreError> { - self.apply_nullifiers(note_updates, transactions_to_discard).await - } - // TRANSACTIONS // -------------------------------------------------------------------------------------------- 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 c01553d06..12c7764c5 100644 --- a/crates/rust-client/src/store/web_store/sync/mod.rs +++ b/crates/rust-client/src/store/web_store/sync/mod.rs @@ -7,7 +7,6 @@ use miden_objects::{ account::AccountId, block::BlockNumber, note::{NoteId, NoteTag}, - transaction::TransactionId, }; use miden_tx::utils::{Deserializable, Serializable}; use serde_wasm_bindgen::from_value; @@ -20,7 +19,6 @@ use super::{ WebStore, }; use crate::{ - note::NoteUpdates, store::StoreError, sync::{NoteTagRecord, NoteTagSource, StateSyncUpdate}, }; @@ -195,12 +193,4 @@ impl WebStore { Ok(()) } - - pub(super) async fn apply_nullifiers( - &self, - note_updates: NoteUpdates, - _transactions_to_discard: Vec, - ) -> Result<(), StoreError> { - apply_note_updates_tx(¬e_updates).await - } } diff --git a/crates/rust-client/src/sync/state_sync.rs b/crates/rust-client/src/sync/state_sync.rs index 56e207877..d21d19c95 100644 --- a/crates/rust-client/src/sync/state_sync.rs +++ b/crates/rust-client/src/sync/state_sync.rs @@ -3,7 +3,7 @@ use core::{future::Future, pin::Pin}; use miden_objects::{ account::{Account, AccountHeader, AccountId}, - block::BlockHeader, + block::{BlockHeader, BlockNumber}, crypto::merkle::{InOrderIndex, MmrDelta, MmrPeaks, PartialMmr}, note::{NoteId, NoteTag}, Digest, @@ -13,10 +13,7 @@ use tracing::info; use super::{AccountUpdates, BlockUpdates, StateSyncUpdate, TransactionUpdates}; use crate::{ note::{NoteScreener, NoteUpdates}, - rpc::{ - domain::{note::CommittedNote, nullifier::NullifierUpdate, transaction::TransactionUpdate}, - NodeRpcClient, - }, + rpc::{domain::note::CommittedNote, NodeRpcClient}, store::{InputNoteRecord, NoteFilter, OutputNoteRecord, Store, StoreError}, ClientError, }; @@ -104,51 +101,32 @@ impl StateSync { unspent_input_notes: Vec, unspent_output_notes: Vec, ) -> Result { - let unspent_nullifiers = unspent_input_notes - .iter() - .map(InputNoteRecord::nullifier) - .chain(unspent_output_notes.iter().filter_map(OutputNoteRecord::nullifier)); - - // To receive information about added nullifiers, we reduce them to the higher 16 bits - // Note that besides filtering by nullifier prefixes, the node also filters by block number - // (it only returns nullifiers from current_block_num until - // response.block_header.block_num()) - let mut nullifiers_tags: Vec = - unspent_nullifiers.map(|nullifier| nullifier.prefix()).collect(); + let block_num = (u32::try_from(current_partial_mmr.num_leaves() - 1) + .expect("The number of leaves in the MMR should be greater than 0 and less than 2^32")) + .into(); let mut state_sync_update = StateSyncUpdate { - block_num: (u32::try_from(current_partial_mmr.num_leaves() - 1).expect( - "The number of leaves in the MMR should be greater than 0 and less than 2^32", - )) - .into(), + block_num, note_updates: NoteUpdates::new(unspent_input_notes, unspent_output_notes), ..Default::default() }; - while self - .sync_state_step( - &mut state_sync_update, - &mut current_partial_mmr, - &accounts, - ¬e_tags, - &nullifiers_tags, - ) - .await? - { - // New nullfiers should be added for new untracked notes that were added in previous - // steps - nullifiers_tags.append( - &mut state_sync_update - .note_updates - .updated_input_notes() - .filter(|note| { - note.is_committed() && !nullifiers_tags.contains(¬e.nullifier().prefix()) - }) - .map(|note| note.nullifier().prefix()) - .collect::>(), - ); + loop { + if !self + .sync_state_step( + &mut state_sync_update, + &mut current_partial_mmr, + &accounts, + ¬e_tags, + ) + .await? + { + break; + } } + self.sync_nullifiers(&mut state_sync_update, block_num).await?; + Ok(state_sync_update) } @@ -167,7 +145,6 @@ impl StateSync { current_partial_mmr: &mut PartialMmr, accounts: &[AccountHeader], note_tags: &[NoteTag], - _nullifiers_tags: &[u16], ) -> Result { let account_ids: Vec = accounts.iter().map(AccountHeader::id).collect(); @@ -189,18 +166,20 @@ impl StateSync { state_sync_update.account_updates.extend(account_updates); - let (found_relevant_note, transaction_updates) = self + // Track the transaction updates for transactions that were committed. Some of these might + // be tracked by the client and need to be marked as committed. + state_sync_update + .transaction_updates + .extend(TransactionUpdates::new(response.transactions, vec![])); + + let found_relevant_note = self .note_state_sync( &mut state_sync_update.note_updates, response.note_inclusions, - response.transactions, - vec![], &response.block_header, ) .await?; - state_sync_update.transaction_updates.extend(transaction_updates); - let (new_mmr_peaks, new_authentication_nodes) = apply_mmr_changes( &response.block_header, found_relevant_note, @@ -282,7 +261,7 @@ impl StateSync { } /// Applies the changes received from the sync response to the notes and transactions tracked - /// by the client and updates the `state_sync_update` accordingly. + /// by the client and updates the `note_updates` accordingly. /// /// This method uses the callbacks provided to the [`StateSync`] component to check if the /// updates received are relevant to the client. @@ -292,27 +271,18 @@ impl StateSync { /// * Tracked expected notes that were committed in the block. /// * Tracked notes that were being processed by a transaction that got committed. /// * Tracked notes that were nullified by an external transaction. - /// - /// The transaction updates might include: - /// * Transactions that were committed in the block. Some of these might me tracked by the - /// client and need to be marked as committed. - /// * Local tracked transactions that were discarded because the notes that they were processing - /// were nullified by an another transaction. async fn note_state_sync( &mut self, note_updates: &mut NoteUpdates, note_inclusions: Vec, - transactions: Vec, - nullifiers: Vec, block_header: &BlockHeader, - ) -> Result<(bool, TransactionUpdates), ClientError> { + ) -> Result { let public_note_ids: Vec = note_inclusions .iter() .filter_map(|note| (!note.metadata().is_private()).then_some(*note.note_id())) .collect(); let mut found_relevant_note = false; - let mut discarded_transactions = vec![]; // Process note inclusions let new_public_notes = @@ -334,20 +304,7 @@ impl StateSync { } } - // Process nullifiers - for nullifier_update in nullifiers { - let discarded_transaction = note_updates - .apply_nullifiers_state_transitions(&nullifier_update, &transactions)?; - - if let Some(transaction_id) = discarded_transaction { - discarded_transactions.push(transaction_id); - } - } - - Ok(( - found_relevant_note, - TransactionUpdates::new(transactions, discarded_transactions), - )) + Ok(found_relevant_note) } /// Queries the node for all received notes that aren't being locally tracked in the client. @@ -372,6 +329,56 @@ impl StateSync { Ok(return_notes) } + + /// Collects the nullifier tags for the notes that were updated in the sync response and uses + /// the `check_nullifiers_by_prefix` endpoint to check if there are new nullifiers for these + /// notes. It then processes the nullifiers to apply the state transitions on the note updates. + /// + /// The `state_sync_update` field will be updated to track the new discarded transactions. + async fn sync_nullifiers( + &self, + state_sync_update: &mut StateSyncUpdate, + current_block_num: BlockNumber, + ) -> Result<(), ClientError> { + // To receive information about added nullifiers, we reduce them to the higher 16 bits + // Note that besides filtering by nullifier prefixes, the node also filters by block number + // (it only returns nullifiers from current_block_num until + // response.block_header.block_num()) + + // Check for new nullifiers for input notes that were updated + let nullifiers_tags: Vec = state_sync_update + .note_updates + .updated_input_notes() + .map(|note| note.nullifier().prefix()) + .collect(); + + let new_nullifiers = self + .rpc_api + .check_nullifiers_by_prefix(&nullifiers_tags, current_block_num) + .await?; + + // Process nullifiers and track the updates of local tracked transactions that were + // discarded because the notes that they were processing were nullified by an + // another transaction. + let mut discarded_transactions = vec![]; + + for nullifier_update in new_nullifiers { + let discarded_transaction = + state_sync_update.note_updates.apply_nullifiers_state_transitions( + &nullifier_update, + state_sync_update.transaction_updates.committed_transactions(), + )?; + + if let Some(transaction_id) = discarded_transaction { + discarded_transactions.push(transaction_id); + } + } + + let transaction_updates = TransactionUpdates::new(vec![], discarded_transactions); + state_sync_update.transaction_updates.extend(transaction_updates); + + Ok(()) + } } // HELPERS