diff --git a/Makefile.toml b/Makefile.toml index 697e30ffe..5e87e546c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -74,7 +74,8 @@ args = ["-rf", "miden-node"] description = "Clone or update miden-node repository and clean up files" script_runner = "bash" script = [ - 'if [ -d miden-node ]; then cd miden-node && git checkout next; else git clone https://github.com/0xPolygonMiden/miden-node.git && cd miden-node && git checkout next; fi', + 'if [ -d miden-node ]; then cd miden-node ; else git clone https://github.com/0xPolygonMiden/miden-node.git && cd miden-node; fi', + 'git checkout next && git pull origin next && cargo update', 'rm -rf miden-store.sqlite3 miden-store.sqlite3-wal miden-store.sqlite3-shm', 'cd bin/node', 'cargo run --features $NODE_FEATURES_TESTING -- make-genesis --force' diff --git a/src/cli/import.rs b/src/cli/import.rs index 2de667f76..3a1ef6be6 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -200,7 +200,7 @@ mod tests { let transaction_request = client.build_transaction_request(transaction_template).unwrap(); let transaction = client.new_transaction(transaction_request).unwrap(); - let created_note = transaction.created_notes()[0].clone(); + let created_note = transaction.created_notes().get_note(0).clone(); client.submit_transaction(transaction).await.unwrap(); // Ensure client has no input notes and one output note diff --git a/src/cli/notes.rs b/src/cli/notes.rs index 5cde68e0b..fc7eb2568 100644 --- a/src/cli/notes.rs +++ b/src/cli/notes.rs @@ -596,7 +596,7 @@ mod tests { let transaction_request = client.build_transaction_request(transaction_template).unwrap(); let transaction = client.new_transaction(transaction_request).unwrap(); - let created_note = transaction.created_notes()[0].clone(); + let created_note = transaction.created_notes().get_note(0).clone(); client.submit_transaction(transaction).await.unwrap(); // Ensure client has no input notes and one output note diff --git a/src/client/note_screener.rs b/src/client/note_screener.rs index 73ea22fa3..c5618cac5 100644 --- a/src/client/note_screener.rs +++ b/src/client/note_screener.rs @@ -1,4 +1,4 @@ -use alloc::collections::BTreeSet; +use alloc::{collections::BTreeSet, rc::Rc}; use core::fmt; use miden_objects::{accounts::AccountId, assets::Asset, notes::Note, Word}; @@ -26,12 +26,12 @@ impl fmt::Display for NoteRelevance { } } -pub struct NoteScreener<'a, S: Store> { - store: &'a S, +pub struct NoteScreener { + store: Rc, } -impl<'a, S: Store> NoteScreener<'a, S> { - pub fn new(store: &'a S) -> Self { +impl NoteScreener { + pub fn new(store: Rc) -> Self { Self { store } } diff --git a/src/client/notes.rs b/src/client/notes.rs index a18531e93..eebd8bd21 100644 --- a/src/client/notes.rs +++ b/src/client/notes.rs @@ -45,7 +45,7 @@ impl Client ) -> Result, ClientError> { let commited_notes = self.store.get_input_notes(NoteFilter::Committed)?; - let note_screener = NoteScreener::new(self.store.as_ref()); + let note_screener = NoteScreener::new(self.store.clone()); let mut relevant_notes = Vec::new(); for input_note in commited_notes { diff --git a/src/client/sync.rs b/src/client/sync.rs index c039d475f..12c523d71 100644 --- a/src/client/sync.rs +++ b/src/client/sync.rs @@ -531,7 +531,7 @@ impl Client // We'll only do the check for either incoming public notes or pending input notes as // output notes are not really candidates to be consumed here. - let note_screener = NoteScreener::new(self.store.as_ref()); + 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() { diff --git a/src/client/transactions/mod.rs b/src/client/transactions/mod.rs index b8616657d..7cc3fb136 100644 --- a/src/client/transactions/mod.rs +++ b/src/client/transactions/mod.rs @@ -8,8 +8,8 @@ use miden_objects::{ crypto::rand::RpoRandomCoin, notes::{Note, NoteId, NoteType}, transaction::{ - ExecutedTransaction, InputNotes, OutputNotes, ProvenTransaction, TransactionArgs, - TransactionId, TransactionScript, + ExecutedTransaction, InputNotes, OutputNote, OutputNotes, ProvenTransaction, + TransactionArgs, TransactionId, TransactionScript, }, Digest, Felt, Word, }; @@ -20,11 +20,11 @@ use rand::Rng; use tracing::info; use self::transaction_request::{PaymentTransactionData, TransactionRequest, TransactionTemplate}; -use super::{note_screener::NoteRelevance, rpc::NodeRpcClient, Client, FeltRng}; +use super::{rpc::NodeRpcClient, Client, FeltRng}; use crate::{ client::NoteScreener, errors::ClientError, - store::{Store, TransactionFilter}, + store::{InputNoteRecord, Store, TransactionFilter}, }; pub mod transaction_request; @@ -32,66 +32,63 @@ pub mod transaction_request; // TRANSACTION RESULT // -------------------------------------------------------------------------------------------- -/// Represents the result of executing a transaction by the client +/// Represents the result of executing a transaction by the client. /// -/// It contains an [ExecutedTransaction], a list of [Note] that describe the details of the notes -/// created by the transaction execution, and a list of `usize` `relevant_notes` that contain the -/// indices of `output_notes` that are relevant to the client +/// It contains an [ExecutedTransaction], and a list of `relevant_notes` that contains the +/// `output_notes` that the client has to store as input notes, based on the NoteScreener +/// output from filtering the transaction's output notes. pub struct TransactionResult { - executed_transaction: ExecutedTransaction, - output_notes: Vec, - relevant_notes: Option>>, + transaction: ExecutedTransaction, + relevant_notes: Vec, } impl TransactionResult { - pub fn new(executed_transaction: ExecutedTransaction, created_notes: Vec) -> Self { - Self { - executed_transaction, - output_notes: created_notes, - relevant_notes: None, + /// Screens the output notes to store and track the relevant ones, and instantiates a [TransactionResult] + pub fn new( + transaction: ExecutedTransaction, + note_screener: NoteScreener, + ) -> Result { + let mut relevant_notes = vec![]; + + for note in notes_from_output(transaction.output_notes()) { + let account_relevance = note_screener.check_relevance(note)?; + + if !account_relevance.is_empty() { + relevant_notes.push(note.clone().into()); + } } - } - pub fn executed_transaction(&self) -> &ExecutedTransaction { - &self.executed_transaction + let tx_result = Self { transaction, relevant_notes }; + + Ok(tx_result) } - pub fn created_notes(&self) -> &Vec { - &self.output_notes + pub fn executed_transaction(&self) -> &ExecutedTransaction { + &self.transaction } - pub fn relevant_notes(&self) -> Vec<&Note> { - if let Some(relevant_notes) = &self.relevant_notes { - relevant_notes - .keys() - .map(|note_index| &self.output_notes[*note_index]) - .collect() - } else { - self.created_notes().iter().collect() - } + pub fn created_notes(&self) -> &OutputNotes { + self.transaction.output_notes() } - pub fn set_relevant_notes( - &mut self, - relevant_notes: BTreeMap>, - ) { - self.relevant_notes = Some(relevant_notes); + pub fn relevant_notes(&self) -> &[InputNoteRecord] { + &self.relevant_notes } pub fn block_num(&self) -> u32 { - self.executed_transaction.block_header().block_num() + self.transaction.block_header().block_num() } pub fn transaction_arguments(&self) -> &TransactionArgs { - self.executed_transaction.tx_args() + self.transaction.tx_args() } pub fn account_delta(&self) -> &AccountDelta { - self.executed_transaction.account_delta() + self.transaction.account_delta() } pub fn consumed_notes(&self) -> &InputNotes { - self.executed_transaction.tx_inputs().input_notes() + self.transaction.tx_inputs().input_notes() } } @@ -231,7 +228,6 @@ impl Client let block_num = self.store.get_sync_height()?; let note_ids = transaction_request.get_input_note_ids(); - let output_notes = transaction_request.expected_output_notes().to_vec(); // Execute the transaction and get the witness @@ -242,20 +238,28 @@ impl Client transaction_request.into(), )?; - // Check that the expected output notes is a subset of the transaction's output notes - let tx_note_ids: BTreeSet = - executed_transaction.output_notes().iter().map(|n| n.id()).collect(); + // Check that the expected output notes matches the transaction outcome. + // We comprae authentication hashes where possible since that involves note IDs + metadata + // (as opposed to just note ID which remains the same regardless of metadata) + let tx_note_auth_hashes: BTreeSet = + notes_from_output(executed_transaction.output_notes()) + .map(Note::authentication_hash) + .collect(); let missing_note_ids: Vec = output_notes .iter() - .filter_map(|n| (!tx_note_ids.contains(&n.id())).then_some(n.id())) + .filter_map(|n| { + (!tx_note_auth_hashes.contains(&n.authentication_hash())).then_some(n.id()) + }) .collect(); if !missing_note_ids.is_empty() { return Err(ClientError::MissingOutputNotes(missing_note_ids)); } - Ok(TransactionResult::new(executed_transaction, output_notes)) + let screener = NoteScreener::new(self.store.clone()); + + TransactionResult::new(executed_transaction, screener) } /// Proves the specified transaction witness, submits it to the node, and stores the transaction in @@ -273,19 +277,6 @@ impl Client self.submit_proven_transaction_request(proven_transaction.clone()).await?; - let note_screener = NoteScreener::new(self.store.as_ref()); - let mut relevant_notes = BTreeMap::new(); - - for (idx, note) in tx_result.created_notes().iter().enumerate() { - let account_relevance = note_screener.check_relevance(note)?; - if !account_relevance.is_empty() { - relevant_notes.insert(idx, account_relevance); - } - } - - let mut tx_result = tx_result; - tx_result.set_relevant_notes(relevant_notes); - // Transaction was proven and submitted to the node correctly, persist note details and update account self.store.apply_transaction(tx_result)?; @@ -405,7 +396,7 @@ impl Client note_type, random_coin, )?; - println!("note tag {}", created_note.metadata().tag()); + let recipient = created_note .recipient() .digest() @@ -442,3 +433,16 @@ impl Client pub(crate) fn prepare_word(word: &Word) -> String { word.iter().map(|x| x.as_int().to_string()).collect::>().join(".") } + +/// Extracts notes from [OutputNotes] +/// Used for: +/// - checking the relevance of notes to save them as input notes +/// - validate hashes versus expected output notes after a transaction is executed +pub(crate) fn notes_from_output(output_notes: &OutputNotes) -> impl Iterator { + output_notes.iter().map(|n| match n { + OutputNote::Full(n) => n, + // The following todo!() applies until we have a way to support flows where we have + // partial details of the note + OutputNote::Header(_) => todo!("For now, all details should be held in OutputNote::Fulls"), + }) +} diff --git a/src/client/transactions/transaction_request.rs b/src/client/transactions/transaction_request.rs index cdcb00d4c..5957029af 100644 --- a/src/client/transactions/transaction_request.rs +++ b/src/client/transactions/transaction_request.rs @@ -173,9 +173,9 @@ impl PaymentTransactionData { // -------------------------------------------------------------------------------------------- pub mod known_script_roots { - pub const P2ID: &str = "0xcdfd70344b952980272119bc02b837d14c07bbfc54f86a254422f39391b77b35"; - pub const P2IDR: &str = "0x41e5727b99a12b36066c09854d39d64dd09d9265c442a9be3626897572bf1745"; - pub const SWAP: &str = "0x5852920f88985b651cf7ef5e48623f898b6c292f4a2c25dd788ff8b46dd90417"; + pub const P2ID: &str = "0x0007b2229f7c8e3205a485a9879f1906798a2e27abd1706eaf58536e7cc3868b"; + pub const P2IDR: &str = "0x418ae31e80b53ddc99179d3cacbc4140c7b36ab04ddb26908b3a6ed2e40061d5"; + pub const SWAP: &str = "0xebbc82ad1688925175599bee2fb56bde649ebb9986fbce957ebee3eb4be5f140"; } #[cfg(test)] diff --git a/src/errors.rs b/src/errors.rs index e8f223639..30753227f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -48,7 +48,7 @@ impl fmt::Display for ClientError { ClientError::MissingOutputNotes(note_ids) => { write!( f, - "transaction error: The transaction did not produce expected Note IDs: {}", + "transaction error: The transaction did not produce the expected notes corresponding to Note IDs: {}", note_ids.iter().map(|&id| id.to_hex()).collect::>().join(", ") ) }, diff --git a/src/store/note_record/output_note_record.rs b/src/store/note_record/output_note_record.rs index 9e306eb54..5b562d0de 100644 --- a/src/store/note_record/output_note_record.rs +++ b/src/store/note_record/output_note_record.rs @@ -91,6 +91,10 @@ impl OutputNoteRecord { } } +// CONVERSIONS +// ================================================================================================ + +// TODO: Improve conversions by implementing into_parts() impl From for OutputNoteRecord { fn from(note: Note) -> Self { OutputNoteRecord { diff --git a/src/store/sqlite_store/transactions.rs b/src/store/sqlite_store/transactions.rs index a261e2800..fe7c74cb0 100644 --- a/src/store/sqlite_store/transactions.rs +++ b/src/store/sqlite_store/transactions.rs @@ -16,9 +16,11 @@ use super::{ SqliteStore, }; use crate::{ - client::transactions::{TransactionRecord, TransactionResult, TransactionStatus}, + client::transactions::{ + notes_from_output, TransactionRecord, TransactionResult, TransactionStatus, + }, errors::StoreError, - store::{InputNoteRecord, OutputNoteRecord, TransactionFilter}, + store::{OutputNoteRecord, TransactionFilter}, }; pub(crate) const INSERT_TRANSACTION_QUERY: &str = @@ -87,16 +89,13 @@ impl SqliteStore { account.apply_delta(account_delta).map_err(StoreError::AccountError)?; - let created_input_notes = tx_result - .relevant_notes() - .into_iter() - .map(|note| InputNoteRecord::from(note.clone())) - .collect::>(); + // Save only input notes that we care for (based on the note screener assessment) + let created_input_notes = tx_result.relevant_notes().to_vec(); - let created_output_notes = tx_result - .created_notes() - .iter() - .map(|note| OutputNoteRecord::from(note.clone())) + // Save all output notes + let created_output_notes = notes_from_output(tx_result.created_notes()) + .cloned() + .map(OutputNoteRecord::from) .collect::>(); let consumed_note_ids = @@ -112,11 +111,8 @@ impl SqliteStore { update_account(&tx, &account)?; // Updates for notes - - // TODO: see if we should filter the input notes we store to keep notes we can consume with - // existing accounts - for note in &created_input_notes { - insert_input_note_tx(&tx, note)?; + for note in created_input_notes { + insert_input_note_tx(&tx, ¬e)?; } for note in &created_output_notes { diff --git a/tests/integration/asm/custom_p2id.masm b/tests/integration/asm/custom_p2id.masm index 1a3a54a33..827b6aa68 100644 --- a/tests/integration/asm/custom_p2id.masm +++ b/tests/integration/asm/custom_p2id.masm @@ -47,6 +47,9 @@ proc.add_note_assets_to_account end begin + # drop the note script root + dropw + # => [NOTE_ARG] push.{expected_note_arg} assert_eqw # drop the note script root diff --git a/tests/integration/custom_transactions_tests.rs b/tests/integration/custom_transactions_tests.rs index e4edbfcdf..0a539685b 100644 --- a/tests/integration/custom_transactions_tests.rs +++ b/tests/integration/custom_transactions_tests.rs @@ -58,7 +58,6 @@ async fn test_transaction_request() { // Execute mint transaction in order to create custom note let note = mint_custom_note(&mut client, fungible_faucet.id(), regular_account.id()).await; - println!("sda"); client.sync_state().await.unwrap(); // Prepare transaction @@ -199,7 +198,7 @@ fn create_custom_note( ) -> Note { let expected_note_arg = [Felt::new(9), Felt::new(12), Felt::new(18), Felt::new(3)] .iter() - .map(|x| x.to_string()) + .map(|x| x.as_int().to_string()) .collect::>() .join(".");