diff --git a/crates/rust-client/src/store/account.rs b/crates/rust-client/src/store/account.rs index 9bfd4a330..bdabd21e5 100644 --- a/crates/rust-client/src/store/account.rs +++ b/crates/rust-client/src/store/account.rs @@ -13,6 +13,7 @@ use miden_objects::{ /// The account should be stored in the database with its parts normalized. Meaning that the /// account header, vault, storage and code are stored separately. This is done to avoid data /// duplication as the header can reference the same elements if they have equal roots. +#[derive(Debug)] pub struct AccountRecord { /// Full account object. account: Account, @@ -54,6 +55,7 @@ impl From for Account { /// Represents the status of an account tracked by the client. /// /// The status of an account may change by local or external factors. +#[derive(Debug)] pub enum AccountStatus { /// The account is new and hasn't been used yet. The seed used to create the account is /// stored in this state. diff --git a/crates/rust-client/src/store/mod.rs b/crates/rust-client/src/store/mod.rs index 484b6f37e..aeaac6e6e 100644 --- a/crates/rust-client/src/store/mod.rs +++ b/crates/rust-client/src/store/mod.rs @@ -345,6 +345,8 @@ pub enum TransactionFilter { /// Filter by transactions that haven't yet been committed to the blockchain as per the last /// sync. Uncomitted, + /// Return a list of the transaction that matches the provided [`TransactionId`]s. + Ids(Vec), } // NOTE FILTER diff --git a/crates/rust-client/src/store/sqlite_store/account.rs b/crates/rust-client/src/store/sqlite_store/account.rs index a09d784e8..937cf51ea 100644 --- a/crates/rust-client/src/store/sqlite_store/account.rs +++ b/crates/rust-client/src/store/sqlite_store/account.rs @@ -207,6 +207,17 @@ impl SqliteStore { }) .collect::, _>>() } + + pub fn delete_accounts( + tx: &Transaction<'_>, + account_hashes: &[Digest], + ) -> Result<(), StoreError> { + const QUERY: &str = "DELETE FROM accounts WHERE account_hash = ?"; + for account_id in account_hashes { + tx.execute(QUERY, params![account_id.to_hex()])?; + } + Ok(()) + } } // HELPERS diff --git a/crates/rust-client/src/store/sqlite_store/sync.rs b/crates/rust-client/src/store/sqlite_store/sync.rs index 129b860a2..2b25be180 100644 --- a/crates/rust-client/src/store/sqlite_store/sync.rs +++ b/crates/rust-client/src/store/sqlite_store/sync.rs @@ -14,7 +14,7 @@ use crate::{ account::{lock_account, update_account}, note::apply_note_updates_tx, }, - StoreError, + StoreError, TransactionFilter, }, sync::{NoteTagRecord, NoteTagSource, StateSyncUpdate}, }; @@ -159,12 +159,54 @@ impl SqliteStore { note_updates: &NoteUpdates, transactions_to_discard: &[TransactionId], ) -> Result<(), StoreError> { + // First we need the `transaction` entries from the `transactions` table that matches the + // `transactions_to_discard` + + let transactions_records_to_discard = Self::get_transactions( + conn, + &TransactionFilter::Ids(transactions_to_discard.to_vec()), + )?; + + // Get the outdated accounts, handling potential errors + let mut outdated_accounts = Vec::new(); + for transaction_record in &transactions_records_to_discard { + let account = Self::get_account(conn, transaction_record.account_id) + .map_err(|err| StoreError::QueryError(format!("Failed to get account: {err}")))? + .ok_or_else(|| StoreError::AccountDataNotFound(transaction_record.account_id))?; + outdated_accounts.push(account); + } + let tx = conn.transaction()?; apply_note_updates_tx(&tx, note_updates)?; Self::mark_transactions_as_discarded(&tx, transactions_to_discard)?; + // TODO: here we need to remove the `accounts` table entries that are originated from the + // discarded transactions + + // Transaction records have a final_account_state field, which is the hash of the account in + // the final state after the transaction is applied. We can use this field to + // identify the accounts that are originated from the discarded transactions. + + let mut accounts_to_remove = Vec::new(); + for tx_record in &transactions_records_to_discard { + let final_account_state = tx_record.final_account_state; + let account = outdated_accounts + .iter() + .find(|account| account.account().hash() == final_account_state) + .ok_or_else(|| { + StoreError::QueryError(format!( + "Could not find account with hash {final_account_state}" + )) + })?; + + accounts_to_remove.push(account.account().hash()); + } + + // Remove the accounts that are originated from the discarded transactions + Self::delete_accounts(&tx, &accounts_to_remove)?; + tx.commit()?; Ok(()) diff --git a/crates/rust-client/src/store/sqlite_store/transaction.rs b/crates/rust-client/src/store/sqlite_store/transaction.rs index 5fdcff601..c7410fa6d 100644 --- a/crates/rust-client/src/store/sqlite_store/transaction.rs +++ b/crates/rust-client/src/store/sqlite_store/transaction.rs @@ -49,6 +49,10 @@ impl TransactionFilter { match self { TransactionFilter::All => QUERY.to_string(), TransactionFilter::Uncomitted => format!("{QUERY} WHERE tx.commit_height IS NULL"), + TransactionFilter::Ids(ids) => { + let ids = ids.iter().map(|id| format!("'{id}'")).collect::>().join(", "); + format!("{QUERY} WHERE tx.id IN ({ids})") + }, } } } diff --git a/crates/rust-client/src/store/web_store/js/transactions.js b/crates/rust-client/src/store/web_store/js/transactions.js index e13dcf7b2..11bfc0626 100644 --- a/crates/rust-client/src/store/web_store/js/transactions.js +++ b/crates/rust-client/src/store/web_store/js/transactions.js @@ -10,6 +10,18 @@ export async function getTransactions(filter) { (tx) => tx.commitHeight === undefined || tx.commitHeight === null ) .toArray(); + } else if (filter.startsWith("Ids:")) { + const idsString = filter.substring(4); // Remove "Ids:" prefix + const ids = idsString.split(","); + + if (ids.length > 0) { + transactionRecords = await transactions + .where("id") + .anyOf(ids) + .toArray(); + } else { + transactionRecords = []; + } } else { transactionRecords = await transactions.toArray(); } @@ -81,7 +93,7 @@ export async function getTransactions(filter) { ); return processedTransactions; - } catch { + } catch (err) { console.error("Failed to get transactions: ", err); throw err; } diff --git a/crates/rust-client/src/store/web_store/transaction/mod.rs b/crates/rust-client/src/store/web_store/transaction/mod.rs index 1cfa82c19..587c8c578 100644 --- a/crates/rust-client/src/store/web_store/transaction/mod.rs +++ b/crates/rust-client/src/store/web_store/transaction/mod.rs @@ -1,4 +1,7 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use miden_objects::{ account::AccountId, @@ -33,6 +36,11 @@ impl WebStore { let filter_as_str = match filter { TransactionFilter::All => "All", TransactionFilter::Uncomitted => "Uncomitted", + TransactionFilter::Ids(ids) => &{ + let ids_str = + ids.iter().map(ToString::to_string).collect::>().join(","); + format!("Ids:{ids_str}") + }, }; let promise = idxdb_get_transactions(filter_as_str.to_string());