From 3612c10155faee85e5961a84fcd8e1d579de112b Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 24 Jul 2024 18:40:20 +0200 Subject: [PATCH 01/26] refactor(transactions): add transaction validation before execution --- crates/rust-client/src/transactions/mod.rs | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index d85a879b2..1a7303e4f 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -4,6 +4,7 @@ use alloc::{ vec::Vec, }; use core::fmt; +use std::collections::BTreeMap; use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ @@ -297,6 +298,9 @@ impl Client }, }; + // Validates the transaction request before executing + self.validate_transaction(&transaction_request)?; + let tx_args = transaction_request.into_transaction_args(tx_script); // Execute the transaction and get the witness @@ -371,6 +375,65 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- + fn validate_transaction( + &self, + transaction_request: &TransactionRequest, + ) -> Result<(), ClientError> { + use miden_objects::{ + assets::Asset::{Fungible, NonFungible}, + AssetError, + }; + + // Get client assets + let (account, _) = self.get_account(transaction_request.account_id())?; + let account_assets = account.vault().assets(); + + // Get transaction output notes assets + let output_notes_assets = transaction_request + .expected_output_notes() + .flat_map(|notes| notes.assets().iter()); + + // Create a map of the fungible assets in the output notes + let fungible_balance_map: BTreeMap = + output_notes_assets.into_iter().fold(BTreeMap::new(), |mut acc, item| { + match item { + Fungible(asset) => { + *acc.entry(asset.faucet_id()).or_insert(0) += asset.amount(); + }, + NonFungible(asset) => { + *acc.entry(asset.faucet_id()).or_insert(0) += 1; + }, + }; + acc + }); + + // Check if the output notes assets are less than or equal to the account assets + for item in account_assets { + match item { + Fungible(asset) => { + if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { + if asset.amount() < *amount { + return Err(ClientError::AssetError( + AssetError::AssetAmountNotSufficient(asset.amount(), *amount), + )); + } + }; + }, + NonFungible(asset) => { + if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { + if *amount < 1 { + return Err(ClientError::AssetError( + AssetError::AssetAmountNotSufficient(1, *amount), + )); + } + }; + }, + } + } + + Ok(()) + } + /// Helper to build a [TransactionRequest] for P2ID-type transactions easily. /// /// - auth_info has to be from the executor account From 712c98d572037a9780709064264df3cfaada8139 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 29 Jul 2024 12:24:21 -0300 Subject: [PATCH 02/26] move validate_transaction function call --- crates/rust-client/src/transactions/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 1a7303e4f..ce95ed182 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -238,6 +238,9 @@ impl Client &mut self, transaction_request: TransactionRequest, ) -> Result { + // Validates the transaction request before executing + self.validate_transaction(&transaction_request)?; + let account_id = transaction_request.account_id(); maybe_await!(self.tx_executor.load_account(account_id)) .map_err(ClientError::TransactionExecutorError)?; @@ -298,9 +301,6 @@ impl Client }, }; - // Validates the transaction request before executing - self.validate_transaction(&transaction_request)?; - let tx_args = transaction_request.into_transaction_args(tx_script); // Execute the transaction and get the witness From a101b2bb9a70e420e0ef84a8972fcd6efc96355a Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 29 Jul 2024 15:54:42 -0300 Subject: [PATCH 03/26] rename function, add maybe_await --- crates/rust-client/src/transactions/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index ce95ed182..f50f421ed 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -1,10 +1,9 @@ use alloc::{ - collections::BTreeSet, + collections::{BTreeSet, BTreeMap}, string::{String, ToString}, vec::Vec, }; use core::fmt; -use std::collections::BTreeMap; use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ @@ -239,7 +238,7 @@ impl Client transaction_request: TransactionRequest, ) -> Result { // Validates the transaction request before executing - self.validate_transaction(&transaction_request)?; + self.validate_request(&transaction_request)?; let account_id = transaction_request.account_id(); maybe_await!(self.tx_executor.load_account(account_id)) @@ -374,8 +373,8 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - - fn validate_transaction( + #[maybe_async] + fn validate_request( &self, transaction_request: &TransactionRequest, ) -> Result<(), ClientError> { @@ -385,7 +384,7 @@ impl Client }; // Get client assets - let (account, _) = self.get_account(transaction_request.account_id())?; + let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; let account_assets = account.vault().assets(); // Get transaction output notes assets From f13eaa181d90deb3a84a369fc8f8d84a2152277d Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 15:16:56 -0300 Subject: [PATCH 04/26] WIP: own notes and vec for non fungibles --- crates/rust-client/src/transactions/mod.rs | 179 ++++++++++++++++----- 1 file changed, 143 insertions(+), 36 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index f50f421ed..9f09113c7 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -1,5 +1,5 @@ use alloc::{ - collections::{BTreeSet, BTreeMap}, + collections::{BTreeMap, BTreeSet}, string::{String, ToString}, vec::Vec, }; @@ -9,7 +9,7 @@ use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ accounts::{AccountDelta, AccountId, AccountType}, assembly::ProgramAst, - assets::FungibleAsset, + assets::{FungibleAsset, NonFungibleAsset}, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, Digest, Felt, FieldElement, NoteError, Word, @@ -373,6 +373,90 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- + // #[maybe_async] + // fn validate_request( + // &self, + // transaction_request: &TransactionRequest, + // ) -> Result<(), ClientError> { + // use miden_objects::{ + // assets::Asset::{Fungible, NonFungible}, + // AssetError, + // }; + + // // Get own notes assets + // let own_notes = match transaction_request.script_template().clone().unwrap() { + // TransactionScriptTemplate::SendNotes(notes) => notes, + // _ => vec![], + // }; + + // let own_notes_assets = own_notes + // .iter() + // .map(|note| (note.id(), note.assets())) + // .collect::>(); + + // // Get transaction output notes assets + // let mut output_notes_assets = transaction_request + // .expected_output_notes() + // // .flat_map(|notes| notes.assets().iter()); + // .map(|note| (note.id(), note.assets())).collect::>(); + + // // Merge own_notes_assets and output_notes_assets, and remove the repeted notes (notes that hold the same NoteId) + // for own_note in own_notes_assets { + // if !output_notes_assets.contains(&own_note) { + // output_notes_assets.push(own_note); + // } + // } + + // // Flat into an Asset vector + // let output_notes_assets_flatted: Vec<&Asset> = output_notes_assets + // .iter() + // .flat_map(|(_, note_asset)| note_asset.iter().collect::>()) + // .collect::>(); + + // let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + // let mut non_fungible_vec: Vec<&NonFungibleAsset> = Vec::new(); + + // // Create a map of the fungible assets in the output notes + // output_notes_assets_flatted.into_iter().for_each(|item| { + // match item { + // Fungible(asset) => { + // *fungible_balance_map.entry(asset.faucet_id()).or_insert(0) += asset.amount(); + // }, + // NonFungible(asset) => { + // non_fungible_vec.push(asset); + // }, + // }; + // }); + + // // Get client assets + // let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; + // let account_assets = account.vault().assets(); + + // // Check if the output notes assets are less than or equal to the account assets + // for item in account_assets { + // match item { + // Fungible(asset) => { + // if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { + // if asset.amount() < *amount { + // return Err(ClientError::AssetError( + // AssetError::AssetAmountNotSufficient(asset.amount(), *amount), + // )); + // } + // }; + // }, + // NonFungible(asset) => { + // if !non_fungible_vec.contains(&&asset) { + // return Err(ClientError::AssetError( + // AssetError::AssetAmountNotSufficient(1, 0), + // )); + // }; + // }, + // } + // } + + // Ok(()) + // } + #[maybe_async] fn validate_request( &self, @@ -382,50 +466,73 @@ impl Client assets::Asset::{Fungible, NonFungible}, AssetError, }; + // Get own notes assets + let own_notes_assets = if let TransactionScriptTemplate::SendNotes(notes) = + transaction_request.script_template().as_ref().unwrap() + { + notes.iter().map(|note| (note.id(), note.assets())).collect::>() + } else { + vec![] + }; + + // Get transaction output notes assets + let mut output_notes_assets = transaction_request + .expected_output_notes() + .map(|note| (note.id(), note.assets())) + .collect::>(); + + // Merge output with own notes, removing duplicates + let additional_notes: Vec<_> = own_notes_assets + .into_iter() + .filter(|own_note| !output_notes_assets.contains(own_note)) + .collect(); + + output_notes_assets.extend(additional_notes); + + // Flatten into an Asset vector + let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + let mut non_fungible_vec: Vec<&NonFungibleAsset> = Vec::new(); + + // Create a map of the fungible and non-fungible assets in the output notes + for asset in output_notes_assets.iter().flat_map(|(_, note_assets)| note_assets.iter()) { + match asset { + Fungible(fungible) => { + *fungible_balance_map.entry(fungible.faucet_id()).or_insert(0) += + fungible.amount(); + }, + NonFungible(non_fungible) => { + non_fungible_vec.push(non_fungible); + }, + } + } // Get client assets let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; let account_assets = account.vault().assets(); - // Get transaction output notes assets - let output_notes_assets = transaction_request - .expected_output_notes() - .flat_map(|notes| notes.assets().iter()); - - // Create a map of the fungible assets in the output notes - let fungible_balance_map: BTreeMap = - output_notes_assets.into_iter().fold(BTreeMap::new(), |mut acc, item| { - match item { - Fungible(asset) => { - *acc.entry(asset.faucet_id()).or_insert(0) += asset.amount(); - }, - NonFungible(asset) => { - *acc.entry(asset.faucet_id()).or_insert(0) += 1; - }, - }; - acc - }); - // Check if the output notes assets are less than or equal to the account assets - for item in account_assets { - match item { - Fungible(asset) => { - if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { - if asset.amount() < *amount { + for account_asset in account_assets { + match account_asset { + Fungible(account_fungible) => { + if let Some(&required_amount) = + fungible_balance_map.get(&account_fungible.faucet_id()) + { + if account_fungible.amount() < required_amount { return Err(ClientError::AssetError( - AssetError::AssetAmountNotSufficient(asset.amount(), *amount), + AssetError::AssetAmountNotSufficient( + account_fungible.amount(), + required_amount, + ), )); } - }; + } }, - NonFungible(asset) => { - if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { - if *amount < 1 { - return Err(ClientError::AssetError( - AssetError::AssetAmountNotSufficient(1, *amount), - )); - } - }; + NonFungible(account_non_fungible) => { + if !non_fungible_vec.contains(&&account_non_fungible) { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + 1, 0, + ))); + } }, } } From 207f95f8bb242ac0640af73de1834a7d241a26a0 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 15:42:23 -0300 Subject: [PATCH 05/26] handle script template None option --- crates/rust-client/src/transactions/mod.rs | 94 ++-------------------- 1 file changed, 5 insertions(+), 89 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 9f09113c7..751c9ae43 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -373,89 +373,6 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - // #[maybe_async] - // fn validate_request( - // &self, - // transaction_request: &TransactionRequest, - // ) -> Result<(), ClientError> { - // use miden_objects::{ - // assets::Asset::{Fungible, NonFungible}, - // AssetError, - // }; - - // // Get own notes assets - // let own_notes = match transaction_request.script_template().clone().unwrap() { - // TransactionScriptTemplate::SendNotes(notes) => notes, - // _ => vec![], - // }; - - // let own_notes_assets = own_notes - // .iter() - // .map(|note| (note.id(), note.assets())) - // .collect::>(); - - // // Get transaction output notes assets - // let mut output_notes_assets = transaction_request - // .expected_output_notes() - // // .flat_map(|notes| notes.assets().iter()); - // .map(|note| (note.id(), note.assets())).collect::>(); - - // // Merge own_notes_assets and output_notes_assets, and remove the repeted notes (notes that hold the same NoteId) - // for own_note in own_notes_assets { - // if !output_notes_assets.contains(&own_note) { - // output_notes_assets.push(own_note); - // } - // } - - // // Flat into an Asset vector - // let output_notes_assets_flatted: Vec<&Asset> = output_notes_assets - // .iter() - // .flat_map(|(_, note_asset)| note_asset.iter().collect::>()) - // .collect::>(); - - // let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - // let mut non_fungible_vec: Vec<&NonFungibleAsset> = Vec::new(); - - // // Create a map of the fungible assets in the output notes - // output_notes_assets_flatted.into_iter().for_each(|item| { - // match item { - // Fungible(asset) => { - // *fungible_balance_map.entry(asset.faucet_id()).or_insert(0) += asset.amount(); - // }, - // NonFungible(asset) => { - // non_fungible_vec.push(asset); - // }, - // }; - // }); - - // // Get client assets - // let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; - // let account_assets = account.vault().assets(); - - // // Check if the output notes assets are less than or equal to the account assets - // for item in account_assets { - // match item { - // Fungible(asset) => { - // if let Some(amount) = fungible_balance_map.get(&asset.faucet_id()) { - // if asset.amount() < *amount { - // return Err(ClientError::AssetError( - // AssetError::AssetAmountNotSufficient(asset.amount(), *amount), - // )); - // } - // }; - // }, - // NonFungible(asset) => { - // if !non_fungible_vec.contains(&&asset) { - // return Err(ClientError::AssetError( - // AssetError::AssetAmountNotSufficient(1, 0), - // )); - // }; - // }, - // } - // } - - // Ok(()) - // } #[maybe_async] fn validate_request( @@ -467,12 +384,11 @@ impl Client AssetError, }; // Get own notes assets - let own_notes_assets = if let TransactionScriptTemplate::SendNotes(notes) = - transaction_request.script_template().as_ref().unwrap() - { - notes.iter().map(|note| (note.id(), note.assets())).collect::>() - } else { - vec![] + let own_notes_assets = match transaction_request.script_template() { + Some(TransactionScriptTemplate::SendNotes(notes)) => { + notes.iter().map(|note| (note.id(), note.assets())).collect::>() + }, + _ => vec![], }; // Get transaction output notes assets From 1830957810b85ffcbafa5d890036fa53abf02b6e Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 15:45:53 -0300 Subject: [PATCH 06/26] reorder imports --- crates/rust-client/src/transactions/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 751c9ae43..67fd5f923 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -9,10 +9,13 @@ use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ accounts::{AccountDelta, AccountId, AccountType}, assembly::ProgramAst, - assets::{FungibleAsset, NonFungibleAsset}, + assets::{ + Asset::{Fungible, NonFungible}, + FungibleAsset, NonFungibleAsset + }, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, - Digest, Felt, FieldElement, NoteError, Word, + AssetError, Digest, Felt, FieldElement, NoteError, Word, }; use miden_tx::{auth::TransactionAuthenticator, ProvingOptions, TransactionProver}; use request::{TransactionRequestError, TransactionScriptTemplate}; @@ -379,10 +382,6 @@ impl Client &self, transaction_request: &TransactionRequest, ) -> Result<(), ClientError> { - use miden_objects::{ - assets::Asset::{Fungible, NonFungible}, - AssetError, - }; // Get own notes assets let own_notes_assets = match transaction_request.script_template() { Some(TransactionScriptTemplate::SendNotes(notes)) => { From 426385ef8b7f1b7a09000500f2c8c0bb093cf61f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 16:26:26 -0300 Subject: [PATCH 07/26] add missing maybe_await --- crates/rust-client/src/transactions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 67fd5f923..496aa0ceb 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -241,7 +241,7 @@ impl Client transaction_request: TransactionRequest, ) -> Result { // Validates the transaction request before executing - self.validate_request(&transaction_request)?; + maybe_await!(self.validate_request(&transaction_request))?; let account_id = transaction_request.account_id(); maybe_await!(self.tx_executor.load_account(account_id)) From 3de71ee544d1baa2e1efac1bf811111ce7aded9f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 16:43:49 -0300 Subject: [PATCH 08/26] add integration test for not enough balance tx, add helper for failing txs --- tests/integration/common.rs | 5 +++++ tests/integration/main.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 5d23fc90b..6d335d348 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -90,6 +90,11 @@ pub fn create_test_store_path() -> std::path::PathBuf { temp_file } +pub async fn execute_failing_tx(client: &mut TestClient, tx_request: TransactionRequest, expected_error: ClientError) { + println!("Executing transaction..."); + // We compare string since we can't compare the error directly + assert_eq!(client.new_transaction(tx_request).err().unwrap().to_string(), expected_error.to_string()); +} pub async fn execute_tx(client: &mut TestClient, tx_request: TransactionRequest) -> TransactionId { println!("Executing transaction..."); let transaction_execution_result = client.new_transaction(tx_request).unwrap(); diff --git a/tests/integration/main.rs b/tests/integration/main.rs index d8b7b4fad..47e68e6e8 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -116,6 +116,34 @@ async fn test_p2id_transfer() { assert_note_cannot_be_consumed_twice(&mut client, to_account_id, notes[0].id()).await; } +#[tokio::test] +async fn test_p2id_transfer_failing_not_enough_balance() { + let mut client = create_test_client(); + wait_for_node(&mut client).await; + + let (first_regular_account, second_regular_account, faucet_account_stub) = + setup(&mut client, AccountStorageType::OffChain).await; + + let from_account_id = first_regular_account.id(); + let to_account_id = second_regular_account.id(); + let faucet_account_id = faucet_account_stub.id(); + + // First Mint necesary token + let note = mint_note(&mut client, from_account_id, faucet_account_id, NoteType::Private).await; + consume_notes(&mut client, from_account_id, &[note]).await; + assert_account_has_single_asset(&client, from_account_id, faucet_account_id, MINT_AMOUNT).await; + + // Do a transfer from first account to second account + let asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT + 1).unwrap(); + let tx_template = TransactionTemplate::PayToId( + PaymentTransactionData::new(Asset::Fungible(asset), from_account_id, to_account_id), + NoteType::Private, + ); + println!("Running P2ID tx..."); + let tx_request = client.build_transaction_request(tx_template).unwrap(); + execute_failing_tx(&mut client, tx_request, ClientError::AssetError(miden_objects::AssetError::AssetAmountNotSufficient(MINT_AMOUNT, MINT_AMOUNT + 1))).await; +} + #[tokio::test] async fn test_p2idr_transfer_consumed_by_target() { let mut client = create_test_client(); From 9336ce4bd3f706ffbad2b284218365306579b207 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 16:44:33 -0300 Subject: [PATCH 09/26] lint & format --- tests/integration/common.rs | 11 +++++++++-- tests/integration/main.rs | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 6d335d348..1ff55c4fd 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -90,10 +90,17 @@ pub fn create_test_store_path() -> std::path::PathBuf { temp_file } -pub async fn execute_failing_tx(client: &mut TestClient, tx_request: TransactionRequest, expected_error: ClientError) { +pub async fn execute_failing_tx( + client: &mut TestClient, + tx_request: TransactionRequest, + expected_error: ClientError, +) { println!("Executing transaction..."); // We compare string since we can't compare the error directly - assert_eq!(client.new_transaction(tx_request).err().unwrap().to_string(), expected_error.to_string()); + assert_eq!( + client.new_transaction(tx_request).err().unwrap().to_string(), + expected_error.to_string() + ); } pub async fn execute_tx(client: &mut TestClient, tx_request: TransactionRequest) -> TransactionId { println!("Executing transaction..."); diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 47e68e6e8..609edcdc4 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -141,7 +141,15 @@ async fn test_p2id_transfer_failing_not_enough_balance() { ); println!("Running P2ID tx..."); let tx_request = client.build_transaction_request(tx_template).unwrap(); - execute_failing_tx(&mut client, tx_request, ClientError::AssetError(miden_objects::AssetError::AssetAmountNotSufficient(MINT_AMOUNT, MINT_AMOUNT + 1))).await; + execute_failing_tx( + &mut client, + tx_request, + ClientError::AssetError(miden_objects::AssetError::AssetAmountNotSufficient( + MINT_AMOUNT, + MINT_AMOUNT + 1, + )), + ) + .await; } #[tokio::test] From b47dabb451e33a9bfe20dba962b51cd0db6c6808 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 30 Jul 2024 17:39:43 -0300 Subject: [PATCH 10/26] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b6bc034..b787a21b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.5.0 (TBD) * Added the Web Client Crate +* Added validations in transaction requests (#447). * [BREAKING] Refactored `Client` to merge submit_transaction and prove_transaction (#445) * Tracked token symbols with config file (#441). * [BREAKING] Refactored `TransactionRequest` to represent a generalized transaction (#438). From b2bd7886f8d02b4e734724985bee032a2c6a1a76 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 31 Jul 2024 15:18:45 -0300 Subject: [PATCH 11/26] use matches --- tests/integration/common.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 1ff55c4fd..4e6f67b80 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -97,11 +97,12 @@ pub async fn execute_failing_tx( ) { println!("Executing transaction..."); // We compare string since we can't compare the error directly - assert_eq!( - client.new_transaction(tx_request).err().unwrap().to_string(), - expected_error.to_string() - ); + assert!(matches!( + client.new_transaction(tx_request).unwrap_err(), + expected_error + )); } + pub async fn execute_tx(client: &mut TestClient, tx_request: TransactionRequest) -> TransactionId { println!("Executing transaction..."); let transaction_execution_result = client.new_transaction(tx_request).unwrap(); From d50eca220076e8476dc6b3ebbfccceaeb2d49554 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 31 Jul 2024 17:18:34 -0300 Subject: [PATCH 12/26] wip --- crates/rust-client/src/transactions/mod.rs | 40 ++++++++++++++-------- tests/integration/common.rs | 5 +-- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 496aa0ceb..d8b47ff88 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -377,11 +377,10 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - #[maybe_async] - fn validate_request( + fn calculate_outgoing_assets( &self, transaction_request: &TransactionRequest, - ) -> Result<(), ClientError> { + ) -> (&BTreeMap, BTreeSet<&NonFungibleAsset>) { // Get own notes assets let own_notes_assets = match transaction_request.script_template() { Some(TransactionScriptTemplate::SendNotes(notes)) => { @@ -394,19 +393,19 @@ impl Client let mut output_notes_assets = transaction_request .expected_output_notes() .map(|note| (note.id(), note.assets())) - .collect::>(); + .collect::>(); + + // Merge with own notes assets and delete duplicates + output_notes_assets + .append( + &mut BTreeMap::from_iter( + own_notes_assets + .into_iter() + ) + ); - // Merge output with own notes, removing duplicates - let additional_notes: Vec<_> = own_notes_assets - .into_iter() - .filter(|own_note| !output_notes_assets.contains(own_note)) - .collect(); - - output_notes_assets.extend(additional_notes); - - // Flatten into an Asset vector let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_vec: Vec<&NonFungibleAsset> = Vec::new(); + let mut non_fungible_vec: BTreeSet<&NonFungibleAsset> = BTreeSet::new(); // Create a map of the fungible and non-fungible assets in the output notes for asset in output_notes_assets.iter().flat_map(|(_, note_assets)| note_assets.iter()) { @@ -416,11 +415,22 @@ impl Client fungible.amount(); }, NonFungible(non_fungible) => { - non_fungible_vec.push(non_fungible); + non_fungible_vec.insert(non_fungible); }, } } + (&fungible_balance_map, non_fungible_vec) + } + + #[maybe_async] + fn validate_request( + &self, + transaction_request: &TransactionRequest, + ) -> Result<(), ClientError> { + let (fungible_balance_map, non_fungible_vec) = + self.calculate_outgoing_assets(transaction_request); + // Get client assets let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; let account_assets = account.vault().assets(); diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 4e6f67b80..08b595346 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -97,10 +97,7 @@ pub async fn execute_failing_tx( ) { println!("Executing transaction..."); // We compare string since we can't compare the error directly - assert!(matches!( - client.new_transaction(tx_request).unwrap_err(), - expected_error - )); + assert!(matches!(client.new_transaction(tx_request).unwrap_err(), expected_error)); } pub async fn execute_tx(client: &mut TestClient, tx_request: TransactionRequest) -> TransactionId { From 850aae72fb4557eda9bb744ecec3cb9245a4cce8 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 31 Jul 2024 21:47:10 -0300 Subject: [PATCH 13/26] Use BTreeSet instead of Vec, functional approach --- crates/rust-client/src/transactions/mod.rs | 39 ++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index d8b47ff88..4ffc161b5 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -377,10 +377,10 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - fn calculate_outgoing_assets( - &self, - transaction_request: &TransactionRequest, - ) -> (&BTreeMap, BTreeSet<&NonFungibleAsset>) { + fn calculate_outgoing_assets<'a>( + &'a self, + transaction_request: &'a TransactionRequest, + ) -> (BTreeMap, BTreeSet<&NonFungibleAsset>) { // Get own notes assets let own_notes_assets = match transaction_request.script_template() { Some(TransactionScriptTemplate::SendNotes(notes)) => { @@ -394,33 +394,30 @@ impl Client .expected_output_notes() .map(|note| (note.id(), note.assets())) .collect::>(); - + // Merge with own notes assets and delete duplicates - output_notes_assets - .append( - &mut BTreeMap::from_iter( - own_notes_assets - .into_iter() - ) - ); + output_notes_assets.append(&mut BTreeMap::from_iter(own_notes_assets.into_iter())); let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_vec: BTreeSet<&NonFungibleAsset> = BTreeSet::new(); + let mut non_fungible_set: BTreeSet<&NonFungibleAsset> = BTreeSet::new(); // Create a map of the fungible and non-fungible assets in the output notes - for asset in output_notes_assets.iter().flat_map(|(_, note_assets)| note_assets.iter()) { - match asset { + output_notes_assets + .values() + .flat_map(|note_assets| note_assets.iter()) + .for_each(|asset| match asset { Fungible(fungible) => { - *fungible_balance_map.entry(fungible.faucet_id()).or_insert(0) += - fungible.amount(); + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); }, NonFungible(non_fungible) => { - non_fungible_vec.insert(non_fungible); + non_fungible_set.insert(non_fungible); }, - } - } + }); - (&fungible_balance_map, non_fungible_vec) + (fungible_balance_map, non_fungible_set) } #[maybe_async] From 6d41693dff632f5ec2da30d27ac3201de7997168 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 1 Aug 2024 12:50:02 -0300 Subject: [PATCH 14/26] remove lifetimes, refactor assets check --- crates/rust-client/src/transactions/mod.rs | 68 +++++++++++----------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 4ffc161b5..bddbf3714 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -377,10 +377,10 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - fn calculate_outgoing_assets<'a>( - &'a self, - transaction_request: &'a TransactionRequest, - ) -> (BTreeMap, BTreeSet<&NonFungibleAsset>) { + fn calculate_outgoing_assets( + &self, + transaction_request: &TransactionRequest, + ) -> (BTreeMap, BTreeSet) { // Get own notes assets let own_notes_assets = match transaction_request.script_template() { Some(TransactionScriptTemplate::SendNotes(notes)) => { @@ -388,7 +388,6 @@ impl Client }, _ => vec![], }; - // Get transaction output notes assets let mut output_notes_assets = transaction_request .expected_output_notes() @@ -396,10 +395,10 @@ impl Client .collect::>(); // Merge with own notes assets and delete duplicates - output_notes_assets.append(&mut BTreeMap::from_iter(own_notes_assets.into_iter())); + output_notes_assets.append(&mut BTreeMap::from_iter(own_notes_assets)); let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet<&NonFungibleAsset> = BTreeSet::new(); + let mut non_fungible_set: BTreeSet = BTreeSet::new(); // Create a map of the fungible and non-fungible assets in the output notes output_notes_assets @@ -413,7 +412,7 @@ impl Client .or_insert(fungible.amount()); }, NonFungible(non_fungible) => { - non_fungible_set.insert(non_fungible); + non_fungible_set.insert(*non_fungible); }, }); @@ -425,37 +424,36 @@ impl Client &self, transaction_request: &TransactionRequest, ) -> Result<(), ClientError> { - let (fungible_balance_map, non_fungible_vec) = + let (fungible_balance_map, non_fungible_set) = self.calculate_outgoing_assets(transaction_request); // Get client assets let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; - let account_assets = account.vault().assets(); - - // Check if the output notes assets are less than or equal to the account assets - for account_asset in account_assets { - match account_asset { - Fungible(account_fungible) => { - if let Some(&required_amount) = - fungible_balance_map.get(&account_fungible.faucet_id()) - { - if account_fungible.amount() < required_amount { - return Err(ClientError::AssetError( - AssetError::AssetAmountNotSufficient( - account_fungible.amount(), - required_amount, - ), - )); - } - } - }, - NonFungible(account_non_fungible) => { - if !non_fungible_vec.contains(&&account_non_fungible) { - return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( - 1, 0, - ))); - } - }, + let mut account_assets = account.vault().assets(); + + // Check if the fungible assets are less than or equal to the account assets + for (faucet_id, amount) in fungible_balance_map { + if let Some(account_asset) = account_assets.find(|account_asset| match account_asset { + Fungible(account_fungible) => account_fungible.faucet_id() == faucet_id, + NonFungible(_) => false, + }) { + let account_asset_amount = account_asset.unwrap_fungible().amount(); + if account_asset_amount < amount { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + account_asset_amount, + amount, + ))); + } + } + } + + // Check if the non fungible assets are less than or equal to the account assets + for non_fungible in non_fungible_set { + if !account_assets.any(|asset| match asset { + Fungible(_) => false, + NonFungible(account_non_fungible) => account_non_fungible == non_fungible, + }) { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient(1, 0))); } } From 29b7bcedea59e716522ff236f293621d43ae007f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 1 Aug 2024 12:50:23 -0300 Subject: [PATCH 15/26] rollback to .to_string in test --- tests/integration/common.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 08b595346..7c281fe7a 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -97,7 +97,10 @@ pub async fn execute_failing_tx( ) { println!("Executing transaction..."); // We compare string since we can't compare the error directly - assert!(matches!(client.new_transaction(tx_request).unwrap_err(), expected_error)); + assert_eq!( + client.new_transaction(tx_request).unwrap_err().to_string(), + expected_error.to_string() + ); } pub async fn execute_tx(client: &mut TestClient, tx_request: TransactionRequest) -> TransactionId { From 65679d5663878e337fe9338a91882bc02406351e Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 1 Aug 2024 16:20:26 -0300 Subject: [PATCH 16/26] avoid vec, use getters for assets --- crates/rust-client/src/transactions/mod.rs | 46 ++++++++++++---------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index bddbf3714..b08daf115 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -382,11 +382,11 @@ impl Client transaction_request: &TransactionRequest, ) -> (BTreeMap, BTreeSet) { // Get own notes assets - let own_notes_assets = match transaction_request.script_template() { + let mut own_notes_assets = match transaction_request.script_template() { Some(TransactionScriptTemplate::SendNotes(notes)) => { - notes.iter().map(|note| (note.id(), note.assets())).collect::>() + notes.iter().map(|note| (note.id(), note.assets())).collect::>() }, - _ => vec![], + _ => Default::default(), }; // Get transaction output notes assets let mut output_notes_assets = transaction_request @@ -395,7 +395,7 @@ impl Client .collect::>(); // Merge with own notes assets and delete duplicates - output_notes_assets.append(&mut BTreeMap::from_iter(own_notes_assets)); + output_notes_assets.append(&mut own_notes_assets); let mut fungible_balance_map: BTreeMap = BTreeMap::new(); let mut non_fungible_set: BTreeSet = BTreeSet::new(); @@ -429,31 +429,35 @@ impl Client // Get client assets let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; - let mut account_assets = account.vault().assets(); - // Check if the fungible assets are less than or equal to the account assets + // Check if the fungible assets are less than or equal to the account asset for (faucet_id, amount) in fungible_balance_map { - if let Some(account_asset) = account_assets.find(|account_asset| match account_asset { - Fungible(account_fungible) => account_fungible.faucet_id() == faucet_id, - NonFungible(_) => false, - }) { - let account_asset_amount = account_asset.unwrap_fungible().amount(); - if account_asset_amount < amount { + match account.vault().get_balance(faucet_id) { + Ok(account_asset_amount) => { + if account_asset_amount < amount { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + account_asset_amount, + amount, + ))); + } + }, + _ => { return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( - account_asset_amount, - amount, - ))); - } + 0, amount, + ))) + }, } } // Check if the non fungible assets are less than or equal to the account assets for non_fungible in non_fungible_set { - if !account_assets.any(|asset| match asset { - Fungible(_) => false, - NonFungible(account_non_fungible) => account_non_fungible == non_fungible, - }) { - return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient(1, 0))); + match account.vault().has_non_fungible_asset(non_fungible.into()) { + Ok(true) => (), + _ => { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + 0, 1, + ))); + }, } } From 1e9ea152bc1108fe4d2af4120746665d29cc81ac Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 1 Aug 2024 17:59:00 -0300 Subject: [PATCH 17/26] add faucet account validations --- crates/rust-client/src/transactions/mod.rs | 64 +++++++++++++++++-- .../rust-client/src/transactions/request.rs | 2 + 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index b08daf115..c7fbc3bba 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -7,7 +7,7 @@ use core::fmt; use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ - accounts::{AccountDelta, AccountId, AccountType}, + accounts::{Account, AccountDelta, AccountId, AccountType}, assembly::ProgramAst, assets::{ Asset::{Fungible, NonFungible}, @@ -419,16 +419,15 @@ impl Client (fungible_balance_map, non_fungible_set) } - #[maybe_async] - fn validate_request( + fn validate_basic_account_request( &self, transaction_request: &TransactionRequest, + account: &Account, ) -> Result<(), ClientError> { let (fungible_balance_map, non_fungible_set) = self.calculate_outgoing_assets(transaction_request); // Get client assets - let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; // Check if the fungible assets are less than or equal to the account asset for (faucet_id, amount) in fungible_balance_map { @@ -464,6 +463,63 @@ impl Client Ok(()) } + fn validate_faucet_account_request( + &self, + transaction_request: &TransactionRequest, + account: &Account, + ) -> Result<(), ClientError> { + let own_notes_assets = match transaction_request.script_template() { + Some(TransactionScriptTemplate::SendNotes(notes)) => notes + .iter() + .map(|note| note.assets()) + .flat_map(|assets| assets.iter()) + .collect::>(), + _ => Default::default(), + }; + + // Faucet account can only mint one asset (itself) + if own_notes_assets.len() > 1 { + return Err(ClientError::TransactionRequestError( + TransactionRequestError::InvalidFaucetMint( + "Faucet can only mint one asset".to_string(), + ), + )); + } + + // If there are no assets to mint, return + if own_notes_assets.is_empty() { + return Ok(()); + }; + + // Faucet account can only mint assets of its own type + let asset_to_mint = own_notes_assets[0]; + + if asset_to_mint.faucet_id() != account.id() { + return Err(ClientError::TransactionRequestError( + TransactionRequestError::InvalidFaucetMint(format!( + "Faucet account can only mint assets of its own type. Expected: {}, got: {}", + account.id(), + asset_to_mint.faucet_id() + )), + )); + }; + + Ok(()) + } + + #[maybe_async] + fn validate_request( + &self, + transaction_request: &TransactionRequest, + ) -> Result<(), ClientError> { + let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; + if account.is_faucet() { + self.validate_faucet_account_request(transaction_request, &account) + } else { + self.validate_basic_account_request(transaction_request, &account) + } + } + /// Helper to build a [TransactionRequest] for P2ID-type transactions easily. /// /// - auth_info has to be from the executor account diff --git a/crates/rust-client/src/transactions/request.rs b/crates/rust-client/src/transactions/request.rs index 5fa43c36b..150f22bd6 100644 --- a/crates/rust-client/src/transactions/request.rs +++ b/crates/rust-client/src/transactions/request.rs @@ -268,6 +268,7 @@ pub enum TransactionRequestError { InvalidTransactionScript(AssemblyError), NoInputNotes, ScriptTemplateError(String), + InvalidFaucetMint(String), } impl fmt::Display for TransactionRequestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -279,6 +280,7 @@ impl fmt::Display for TransactionRequestError { Self::InvalidTransactionScript(err) => write!(f, "Invalid transaction script: {}", err), Self::NoInputNotes => write!(f, "A transaction without output notes must have at least one input note"), Self::ScriptTemplateError(err) => write!(f, "Transaction script template error: {}", err), + Self::InvalidFaucetMint(err) => write!(f, "Faucet mint error: {}", err), } } } From e41888696f722967b21a326f43e9fadb1b95b126 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 1 Aug 2024 18:54:37 -0300 Subject: [PATCH 18/26] fix format --- crates/rust-client/src/transactions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index c7fbc3bba..ecb3e6a6f 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -11,7 +11,7 @@ use miden_objects::{ assembly::ProgramAst, assets::{ Asset::{Fungible, NonFungible}, - FungibleAsset, NonFungibleAsset + FungibleAsset, NonFungibleAsset, }, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, From 238e0b496bd7932a093b8cd39267404ff9c3f51f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Fri, 2 Aug 2024 13:51:05 -0300 Subject: [PATCH 19/26] move get_{outgoing,incoming}_assets methods to tx request --- crates/rust-client/src/transactions/mod.rs | 112 ++++-------------- .../rust-client/src/transactions/request.rs | 99 +++++++++++++++- 2 files changed, 120 insertions(+), 91 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index ecb3e6a6f..312f97c57 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -1,5 +1,5 @@ use alloc::{ - collections::{BTreeMap, BTreeSet}, + collections::BTreeSet, string::{String, ToString}, vec::Vec, }; @@ -9,10 +9,7 @@ use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ accounts::{Account, AccountDelta, AccountId, AccountType}, assembly::ProgramAst, - assets::{ - Asset::{Fungible, NonFungible}, - FungibleAsset, NonFungibleAsset, - }, + assets::FungibleAsset, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, AssetError, Digest, Felt, FieldElement, NoteError, Word, @@ -377,63 +374,26 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - fn calculate_outgoing_assets( - &self, - transaction_request: &TransactionRequest, - ) -> (BTreeMap, BTreeSet) { - // Get own notes assets - let mut own_notes_assets = match transaction_request.script_template() { - Some(TransactionScriptTemplate::SendNotes(notes)) => { - notes.iter().map(|note| (note.id(), note.assets())).collect::>() - }, - _ => Default::default(), - }; - // Get transaction output notes assets - let mut output_notes_assets = transaction_request - .expected_output_notes() - .map(|note| (note.id(), note.assets())) - .collect::>(); - - // Merge with own notes assets and delete duplicates - output_notes_assets.append(&mut own_notes_assets); - - let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet = BTreeSet::new(); - - // Create a map of the fungible and non-fungible assets in the output notes - output_notes_assets - .values() - .flat_map(|note_assets| note_assets.iter()) - .for_each(|asset| match asset { - Fungible(fungible) => { - fungible_balance_map - .entry(fungible.faucet_id()) - .and_modify(|balance| *balance += fungible.amount()) - .or_insert(fungible.amount()); - }, - NonFungible(non_fungible) => { - non_fungible_set.insert(*non_fungible); - }, - }); - - (fungible_balance_map, non_fungible_set) - } - + #[maybe_async] fn validate_basic_account_request( &self, transaction_request: &TransactionRequest, account: &Account, ) -> Result<(), ClientError> { - let (fungible_balance_map, non_fungible_set) = - self.calculate_outgoing_assets(transaction_request); + // Get outgoing assets + let (fungible_balance_map, non_fungible_set) = transaction_request.get_outgoing_assets(); - // Get client assets + // Get incoming assets + let (incoming_fungible_balance_map, incoming_non_fungible_balance_set) = + maybe_await!(transaction_request.get_incoming_assets(self))?; // Check if the fungible assets are less than or equal to the account asset for (faucet_id, amount) in fungible_balance_map { match account.vault().get_balance(faucet_id) { Ok(account_asset_amount) => { - if account_asset_amount < amount { + let incoming_balance = + incoming_fungible_balance_map.get(&faucet_id).unwrap_or(&0); + if account_asset_amount + incoming_balance < amount { return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( account_asset_amount, amount, @@ -452,6 +412,14 @@ impl Client for non_fungible in non_fungible_set { match account.vault().has_non_fungible_asset(non_fungible.into()) { Ok(true) => (), + Ok(false) => { + // Check if the non fungible asset is in the incoming assets + if !incoming_non_fungible_balance_set.contains(&non_fungible) { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + 0, 1, + ))); + } + }, _ => { return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( 0, 1, @@ -465,44 +433,10 @@ impl Client fn validate_faucet_account_request( &self, - transaction_request: &TransactionRequest, - account: &Account, + _transaction_request: &TransactionRequest, + _account: &Account, ) -> Result<(), ClientError> { - let own_notes_assets = match transaction_request.script_template() { - Some(TransactionScriptTemplate::SendNotes(notes)) => notes - .iter() - .map(|note| note.assets()) - .flat_map(|assets| assets.iter()) - .collect::>(), - _ => Default::default(), - }; - - // Faucet account can only mint one asset (itself) - if own_notes_assets.len() > 1 { - return Err(ClientError::TransactionRequestError( - TransactionRequestError::InvalidFaucetMint( - "Faucet can only mint one asset".to_string(), - ), - )); - } - - // If there are no assets to mint, return - if own_notes_assets.is_empty() { - return Ok(()); - }; - - // Faucet account can only mint assets of its own type - let asset_to_mint = own_notes_assets[0]; - - if asset_to_mint.faucet_id() != account.id() { - return Err(ClientError::TransactionRequestError( - TransactionRequestError::InvalidFaucetMint(format!( - "Faucet account can only mint assets of its own type. Expected: {}, got: {}", - account.id(), - asset_to_mint.faucet_id() - )), - )); - }; + // TODO(SantiagoPittella): Add faucet account validations. Ok(()) } @@ -516,7 +450,7 @@ impl Client if account.is_faucet() { self.validate_faucet_account_request(transaction_request, &account) } else { - self.validate_basic_account_request(transaction_request, &account) + maybe_await!(self.validate_basic_account_request(transaction_request, &account)) } } diff --git a/crates/rust-client/src/transactions/request.rs b/crates/rust-client/src/transactions/request.rs index 150f22bd6..7a283099d 100644 --- a/crates/rust-client/src/transactions/request.rs +++ b/crates/rust-client/src/transactions/request.rs @@ -8,13 +8,28 @@ use core::fmt; use miden_objects::{ accounts::AccountId, assembly::AssemblyError, - assets::{Asset, FungibleAsset}, - crypto::merkle::{InnerNodeInfo, MerkleStore}, + assets::{ + Asset, + Asset::{Fungible, NonFungible}, + FungibleAsset, NonFungibleAsset, + }, + crypto::{ + merkle::{InnerNodeInfo, MerkleStore}, + rand::FeltRng, + }, notes::{Note, NoteDetails, NoteId, NoteType, PartialNote}, transaction::{OutputNote, TransactionArgs, TransactionScript}, vm::AdviceMap, Digest, Felt, Word, }; +use miden_tx::auth::TransactionAuthenticator; +use winter_maybe_async::{maybe_async, maybe_await}; + +use crate::{ + rpc::NodeRpcClient, + store::{NoteFilter, Store}, + Client, +}; // MASM SCRIPTS // ================================================================================================ @@ -254,6 +269,84 @@ impl TransactionRequest { tx_args } + + pub fn get_outgoing_assets(&self) -> (BTreeMap, BTreeSet) { + // Get own notes assets + let mut own_notes_assets = match self.script_template() { + Some(TransactionScriptTemplate::SendNotes(notes)) => { + notes.iter().map(|note| (note.id(), note.assets())).collect::>() + }, + _ => Default::default(), + }; + // Get transaction output notes assets + let mut output_notes_assets = self + .expected_output_notes() + .map(|note| (note.id(), note.assets())) + .collect::>(); + + // Merge with own notes assets and delete duplicates + output_notes_assets.append(&mut own_notes_assets); + + let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + let mut non_fungible_set: BTreeSet = BTreeSet::new(); + + // Create a map of the fungible and non-fungible assets in the output notes + output_notes_assets + .values() + .flat_map(|note_assets| note_assets.iter()) + .for_each(|asset| match asset { + Fungible(fungible) => { + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); + }, + NonFungible(non_fungible) => { + non_fungible_set.insert(*non_fungible); + }, + }); + + (fungible_balance_map, non_fungible_set) + } + + #[maybe_async] + pub fn get_incoming_assets( + &self, + client: &Client< + impl NodeRpcClient, + impl FeltRng, + impl Store, + impl TransactionAuthenticator, + >, + ) -> Result<(BTreeMap, BTreeSet), TransactionRequestError> + { + // Get incoming assets + let incoming_notes_ids = + self.input_notes().iter().map(|(note_id, _)| *note_id).collect::>(); + + let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + let mut non_fungible_set: BTreeSet = BTreeSet::new(); + + maybe_await!(client.get_input_notes(NoteFilter::List(&incoming_notes_ids))) + .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))? + .iter() + .flat_map(|input_note_record| input_note_record.assets().iter()) + .collect::>() + .iter() + .for_each(|asset| match asset { + Fungible(fungible) => { + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); + }, + NonFungible(non_fungible) => { + non_fungible_set.insert(*non_fungible); + }, + }); + + Ok((fungible_balance_map, non_fungible_set)) + } } // TRANSACTION REQUEST ERROR @@ -269,6 +362,7 @@ pub enum TransactionRequestError { NoInputNotes, ScriptTemplateError(String), InvalidFaucetMint(String), + NoteNotFound(String), } impl fmt::Display for TransactionRequestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -281,6 +375,7 @@ impl fmt::Display for TransactionRequestError { Self::NoInputNotes => write!(f, "A transaction without output notes must have at least one input note"), Self::ScriptTemplateError(err) => write!(f, "Transaction script template error: {}", err), Self::InvalidFaucetMint(err) => write!(f, "Faucet mint error: {}", err), + Self::NoteNotFound(err) => write!(f, "Note not found: {}", err), } } } From 7d53c2b96f4bdcd73daf7e98a4947590572c78fc Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 5 Aug 2024 10:38:06 -0300 Subject: [PATCH 20/26] move get_{outgoing,incoming}_assets methods to transaction module --- crates/rust-client/src/transactions/mod.rs | 91 ++++++++++++++++- .../rust-client/src/transactions/request.rs | 97 +------------------ 2 files changed, 89 insertions(+), 99 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 312f97c57..379537812 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -1,5 +1,5 @@ use alloc::{ - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, string::{String, ToString}, vec::Vec, }; @@ -9,7 +9,10 @@ use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ accounts::{Account, AccountDelta, AccountId, AccountType}, assembly::ProgramAst, - assets::FungibleAsset, + assets::{ + Asset::{self, Fungible, NonFungible}, + FungibleAsset, NonFungibleAsset, + }, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, AssetError, Digest, Felt, FieldElement, NoteError, Word, @@ -374,6 +377,85 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- + pub fn get_outgoing_assets( + &self, + transaction_request: &TransactionRequest, + ) -> (BTreeMap, BTreeSet) { + // Get own notes assets + let mut own_notes_assets = match transaction_request.script_template() { + Some(TransactionScriptTemplate::SendNotes(notes)) => { + notes.iter().map(|note| (note.id(), note.assets())).collect::>() + }, + _ => Default::default(), + }; + // Get transaction output notes assets + let mut output_notes_assets = transaction_request + .expected_output_notes() + .map(|note| (note.id(), note.assets())) + .collect::>(); + + // Merge with own notes assets and delete duplicates + output_notes_assets.append(&mut own_notes_assets); + + let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + let mut non_fungible_set: BTreeSet = BTreeSet::new(); + + // Create a map of the fungible and non-fungible assets in the output notes + output_notes_assets + .values() + .flat_map(|note_assets| note_assets.iter()) + .for_each(|asset| match asset { + Fungible(fungible) => { + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); + }, + NonFungible(non_fungible) => { + non_fungible_set.insert(*non_fungible); + }, + }); + + (fungible_balance_map, non_fungible_set) + } + + #[maybe_async] + pub fn get_incoming_assets( + &self, + transaction_request: &TransactionRequest, + ) -> Result<(BTreeMap, BTreeSet), TransactionRequestError> + { + // Get incoming assets + let incoming_notes_ids = transaction_request + .input_notes() + .iter() + .map(|(note_id, _)| *note_id) + .collect::>(); + + let mut fungible_balance_map: BTreeMap = BTreeMap::new(); + let mut non_fungible_set: BTreeSet = BTreeSet::new(); + + maybe_await!(self.get_input_notes(NoteFilter::List(&incoming_notes_ids))) + .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))? + .iter() + .flat_map(|input_note_record| input_note_record.assets().iter()) + .collect::>() + .iter() + .for_each(|asset| match asset { + Fungible(fungible) => { + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); + }, + NonFungible(non_fungible) => { + non_fungible_set.insert(*non_fungible); + }, + }); + + Ok((fungible_balance_map, non_fungible_set)) + } + #[maybe_async] fn validate_basic_account_request( &self, @@ -381,11 +463,12 @@ impl Client account: &Account, ) -> Result<(), ClientError> { // Get outgoing assets - let (fungible_balance_map, non_fungible_set) = transaction_request.get_outgoing_assets(); + let (fungible_balance_map, non_fungible_set) = + self.get_outgoing_assets(transaction_request); // Get incoming assets let (incoming_fungible_balance_map, incoming_non_fungible_balance_set) = - maybe_await!(transaction_request.get_incoming_assets(self))?; + maybe_await!(self.get_incoming_assets(transaction_request))?; // Check if the fungible assets are less than or equal to the account asset for (faucet_id, amount) in fungible_balance_map { diff --git a/crates/rust-client/src/transactions/request.rs b/crates/rust-client/src/transactions/request.rs index 7a283099d..0f8768dc0 100644 --- a/crates/rust-client/src/transactions/request.rs +++ b/crates/rust-client/src/transactions/request.rs @@ -8,28 +8,13 @@ use core::fmt; use miden_objects::{ accounts::AccountId, assembly::AssemblyError, - assets::{ - Asset, - Asset::{Fungible, NonFungible}, - FungibleAsset, NonFungibleAsset, - }, - crypto::{ - merkle::{InnerNodeInfo, MerkleStore}, - rand::FeltRng, - }, + assets::{Asset, FungibleAsset}, + crypto::merkle::{InnerNodeInfo, MerkleStore}, notes::{Note, NoteDetails, NoteId, NoteType, PartialNote}, transaction::{OutputNote, TransactionArgs, TransactionScript}, vm::AdviceMap, Digest, Felt, Word, }; -use miden_tx::auth::TransactionAuthenticator; -use winter_maybe_async::{maybe_async, maybe_await}; - -use crate::{ - rpc::NodeRpcClient, - store::{NoteFilter, Store}, - Client, -}; // MASM SCRIPTS // ================================================================================================ @@ -269,84 +254,6 @@ impl TransactionRequest { tx_args } - - pub fn get_outgoing_assets(&self) -> (BTreeMap, BTreeSet) { - // Get own notes assets - let mut own_notes_assets = match self.script_template() { - Some(TransactionScriptTemplate::SendNotes(notes)) => { - notes.iter().map(|note| (note.id(), note.assets())).collect::>() - }, - _ => Default::default(), - }; - // Get transaction output notes assets - let mut output_notes_assets = self - .expected_output_notes() - .map(|note| (note.id(), note.assets())) - .collect::>(); - - // Merge with own notes assets and delete duplicates - output_notes_assets.append(&mut own_notes_assets); - - let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet = BTreeSet::new(); - - // Create a map of the fungible and non-fungible assets in the output notes - output_notes_assets - .values() - .flat_map(|note_assets| note_assets.iter()) - .for_each(|asset| match asset { - Fungible(fungible) => { - fungible_balance_map - .entry(fungible.faucet_id()) - .and_modify(|balance| *balance += fungible.amount()) - .or_insert(fungible.amount()); - }, - NonFungible(non_fungible) => { - non_fungible_set.insert(*non_fungible); - }, - }); - - (fungible_balance_map, non_fungible_set) - } - - #[maybe_async] - pub fn get_incoming_assets( - &self, - client: &Client< - impl NodeRpcClient, - impl FeltRng, - impl Store, - impl TransactionAuthenticator, - >, - ) -> Result<(BTreeMap, BTreeSet), TransactionRequestError> - { - // Get incoming assets - let incoming_notes_ids = - self.input_notes().iter().map(|(note_id, _)| *note_id).collect::>(); - - let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet = BTreeSet::new(); - - maybe_await!(client.get_input_notes(NoteFilter::List(&incoming_notes_ids))) - .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))? - .iter() - .flat_map(|input_note_record| input_note_record.assets().iter()) - .collect::>() - .iter() - .for_each(|asset| match asset { - Fungible(fungible) => { - fungible_balance_map - .entry(fungible.faucet_id()) - .and_modify(|balance| *balance += fungible.amount()) - .or_insert(fungible.amount()); - }, - NonFungible(non_fungible) => { - non_fungible_set.insert(*non_fungible); - }, - }); - - Ok((fungible_balance_map, non_fungible_set)) - } } // TRANSACTION REQUEST ERROR From 02c956aa0327bdb5cefb181d84e728833ca503d8 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 5 Aug 2024 19:10:37 -0300 Subject: [PATCH 21/26] fix: include unauthenticated notes in validation --- crates/rust-client/src/transactions/mod.rs | 39 ++++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 379537812..bdcd4d3c6 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -425,30 +425,47 @@ impl Client transaction_request: &TransactionRequest, ) -> Result<(BTreeMap, BTreeSet), TransactionRequestError> { - // Get incoming assets - let incoming_notes_ids = transaction_request + // Get incoming asset notes excluding unauthenticated ones + let incoming_notes_ids: Vec<_> = transaction_request .input_notes() .iter() - .map(|(note_id, _)| *note_id) - .collect::>(); + .filter_map(|(note_id, _)| { + if transaction_request + .unauthenticated_input_notes() + .iter() + .any(|note| note.id() == *note_id) + { + None + } else { + Some(*note_id) + } + }) + .collect(); let mut fungible_balance_map: BTreeMap = BTreeMap::new(); let mut non_fungible_set: BTreeSet = BTreeSet::new(); - maybe_await!(self.get_input_notes(NoteFilter::List(&incoming_notes_ids))) - .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))? - .iter() - .flat_map(|input_note_record| input_note_record.assets().iter()) - .collect::>() + let store_input_notes = + maybe_await!(self.get_input_notes(NoteFilter::List(&incoming_notes_ids))) + .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))?; + + store_input_notes .iter() + .flat_map(|note| note.assets().iter()) + .chain( + transaction_request + .unauthenticated_input_notes() + .iter() + .flat_map(|note| note.assets().iter()), + ) .for_each(|asset| match asset { - Fungible(fungible) => { + Asset::Fungible(fungible) => { fungible_balance_map .entry(fungible.faucet_id()) .and_modify(|balance| *balance += fungible.amount()) .or_insert(fungible.amount()); }, - NonFungible(non_fungible) => { + Asset::NonFungible(non_fungible) => { non_fungible_set.insert(*non_fungible); }, }); From bae3f474fb9ab1406d7d1e701b8bcf4da759871f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 6 Aug 2024 09:55:22 -0300 Subject: [PATCH 22/26] remove unused error --- crates/rust-client/src/transactions/request.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/rust-client/src/transactions/request.rs b/crates/rust-client/src/transactions/request.rs index 0f8768dc0..cc3e5b5cd 100644 --- a/crates/rust-client/src/transactions/request.rs +++ b/crates/rust-client/src/transactions/request.rs @@ -268,7 +268,6 @@ pub enum TransactionRequestError { InvalidTransactionScript(AssemblyError), NoInputNotes, ScriptTemplateError(String), - InvalidFaucetMint(String), NoteNotFound(String), } impl fmt::Display for TransactionRequestError { @@ -281,7 +280,6 @@ impl fmt::Display for TransactionRequestError { Self::InvalidTransactionScript(err) => write!(f, "Invalid transaction script: {}", err), Self::NoInputNotes => write!(f, "A transaction without output notes must have at least one input note"), Self::ScriptTemplateError(err) => write!(f, "Transaction script template error: {}", err), - Self::InvalidFaucetMint(err) => write!(f, "Faucet mint error: {}", err), Self::NoteNotFound(err) => write!(f, "Note not found: {}", err), } } From 303bface03f4194e9e86eb9d4716b90d012e2914 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 6 Aug 2024 09:55:57 -0300 Subject: [PATCH 23/26] change validation methods visibility, remove validate_faucet method, add to do --- crates/rust-client/src/transactions/mod.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index bdcd4d3c6..0de77b1d5 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -377,7 +377,7 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- - pub fn get_outgoing_assets( + fn get_outgoing_assets( &self, transaction_request: &TransactionRequest, ) -> (BTreeMap, BTreeSet) { @@ -420,7 +420,7 @@ impl Client } #[maybe_async] - pub fn get_incoming_assets( + fn get_incoming_assets( &self, transaction_request: &TransactionRequest, ) -> Result<(BTreeMap, BTreeSet), TransactionRequestError> @@ -531,16 +531,6 @@ impl Client Ok(()) } - fn validate_faucet_account_request( - &self, - _transaction_request: &TransactionRequest, - _account: &Account, - ) -> Result<(), ClientError> { - // TODO(SantiagoPittella): Add faucet account validations. - - Ok(()) - } - #[maybe_async] fn validate_request( &self, @@ -548,7 +538,7 @@ impl Client ) -> Result<(), ClientError> { let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; if account.is_faucet() { - self.validate_faucet_account_request(transaction_request, &account) + // TODO(SantiagoPittella): Add faucet validations. } else { maybe_await!(self.validate_basic_account_request(transaction_request, &account)) } From f63c6122fe96730b519f0bec320d1800be6d974f Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 6 Aug 2024 11:03:49 -0300 Subject: [PATCH 24/26] abstract asset collection in function, add comments --- crates/rust-client/src/transactions/mod.rs | 83 ++++++++++------------ 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 0de77b1d5..86a38b722 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -9,10 +9,7 @@ use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ accounts::{Account, AccountDelta, AccountId, AccountType}, assembly::ProgramAst, - assets::{ - Asset::{self, Fungible, NonFungible}, - FungibleAsset, NonFungibleAsset, - }, + assets::{Asset, FungibleAsset, NonFungibleAsset}, notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType}, transaction::{InputNotes, TransactionArgs}, AssetError, Digest, Felt, FieldElement, NoteError, Word, @@ -377,6 +374,10 @@ impl Client // HELPERS // -------------------------------------------------------------------------------------------- + /// Helper to get the account outgoing assets. + /// + /// Any outgoing assets resulting from executing note scripts but not present in expected output + /// notes would not be included. fn get_outgoing_assets( &self, transaction_request: &TransactionRequest, @@ -397,28 +398,14 @@ impl Client // Merge with own notes assets and delete duplicates output_notes_assets.append(&mut own_notes_assets); - let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet = BTreeSet::new(); - // Create a map of the fungible and non-fungible assets in the output notes - output_notes_assets - .values() - .flat_map(|note_assets| note_assets.iter()) - .for_each(|asset| match asset { - Fungible(fungible) => { - fungible_balance_map - .entry(fungible.faucet_id()) - .and_modify(|balance| *balance += fungible.amount()) - .or_insert(fungible.amount()); - }, - NonFungible(non_fungible) => { - non_fungible_set.insert(*non_fungible); - }, - }); + let outgoing_assets = + output_notes_assets.values().flat_map(|note_assets| note_assets.iter()); - (fungible_balance_map, non_fungible_set) + collect_assets(outgoing_assets) } + /// Helper to get the account incoming assets. #[maybe_async] fn get_incoming_assets( &self, @@ -442,35 +429,19 @@ impl Client }) .collect(); - let mut fungible_balance_map: BTreeMap = BTreeMap::new(); - let mut non_fungible_set: BTreeSet = BTreeSet::new(); - let store_input_notes = maybe_await!(self.get_input_notes(NoteFilter::List(&incoming_notes_ids))) .map_err(|err| TransactionRequestError::NoteNotFound(err.to_string()))?; - store_input_notes - .iter() - .flat_map(|note| note.assets().iter()) - .chain( + let all_incoming_assets = + store_input_notes.iter().flat_map(|note| note.assets().iter()).chain( transaction_request .unauthenticated_input_notes() .iter() .flat_map(|note| note.assets().iter()), - ) - .for_each(|asset| match asset { - Asset::Fungible(fungible) => { - fungible_balance_map - .entry(fungible.faucet_id()) - .and_modify(|balance| *balance += fungible.amount()) - .or_insert(fungible.amount()); - }, - Asset::NonFungible(non_fungible) => { - non_fungible_set.insert(*non_fungible); - }, - }); + ); - Ok((fungible_balance_map, non_fungible_set)) + Ok(collect_assets(all_incoming_assets)) } #[maybe_async] @@ -487,7 +458,8 @@ impl Client let (incoming_fungible_balance_map, incoming_non_fungible_balance_set) = maybe_await!(self.get_incoming_assets(transaction_request))?; - // Check if the fungible assets are less than or equal to the account asset + // Check if the account balance plus incoming assets is greater than or equal to the outgoing + // fungible assets for (faucet_id, amount) in fungible_balance_map { match account.vault().get_balance(faucet_id) { Ok(account_asset_amount) => { @@ -508,7 +480,8 @@ impl Client } } - // Check if the non fungible assets are less than or equal to the account assets + // Check if the account balance plus incoming assets is greater than or equal to the outgoing + // non fungible assets for non_fungible in non_fungible_set { match account.vault().has_non_fungible_asset(non_fungible.into()) { Ok(true) => (), @@ -539,6 +512,7 @@ impl Client let (account, _) = maybe_await!(self.get_account(transaction_request.account_id()))?; if account.is_faucet() { // TODO(SantiagoPittella): Add faucet validations. + Ok(()) } else { maybe_await!(self.validate_basic_account_request(transaction_request, &account)) } @@ -654,6 +628,27 @@ impl Client // HELPERS // ================================================================================================ +fn collect_assets<'a>( + assets: impl Iterator, +) -> (BTreeMap, BTreeSet) { + let mut fungible_balance_map = BTreeMap::new(); + let mut non_fungible_set = BTreeSet::new(); + + assets.for_each(|asset| match asset { + Asset::Fungible(fungible) => { + fungible_balance_map + .entry(fungible.faucet_id()) + .and_modify(|balance| *balance += fungible.amount()) + .or_insert(fungible.amount()); + }, + Asset::NonFungible(non_fungible) => { + non_fungible_set.insert(*non_fungible); + }, + }); + + (fungible_balance_map, non_fungible_set) +} + pub(crate) fn prepare_word(word: &Word) -> String { word.iter().map(|x| x.as_int().to_string()).collect::>().join(".") } From 0537f21d5fea43a9eae15ebb9e9d797688a33787 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Tue, 6 Aug 2024 12:09:12 -0300 Subject: [PATCH 25/26] Set build script to use miden-node's next --- crates/rust-client/Cargo.toml | 2 +- .../rpc/tonic_client/generated/requests.rs | 56 +++++++++++--- .../rpc/tonic_client/generated/responses.rs | 35 +++++++++ .../src/rpc/tonic_client/generated/rpc.rs | 76 +++++++++++++++++++ .../web_tonic_client/generated/requests.rs | 56 +++++++++++--- .../web_tonic_client/generated/responses.rs | 35 +++++++++ .../src/rpc/web_tonic_client/generated/rpc.rs | 76 +++++++++++++++++++ 7 files changed, 315 insertions(+), 21 deletions(-) diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index e43e9d075..f369bc6be 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -58,7 +58,7 @@ miden-objects = {default-features = false, features = ["serde", "testing"], git uuid = { version = "1.9", features = ["serde", "v4"] } [build-dependencies] -miden-rpc-proto = { version = "0.4" } +miden-rpc-proto = { git = "https://github.com/0xPolygonMiden/miden-node/", branch = "next" } miette = { version = "7.2", features = ["fancy"] } prost = { version = "0.12", default-features = false, features = ["derive"] } prost-build = { version = "0.12", default-features = false } diff --git a/crates/rust-client/src/rpc/tonic_client/generated/requests.rs b/crates/rust-client/src/rpc/tonic_client/generated/requests.rs index 7ce1e69ac..a75da6f50 100644 --- a/crates/rust-client/src/rpc/tonic_client/generated/requests.rs +++ b/crates/rust-client/src/rpc/tonic_client/generated/requests.rs @@ -5,6 +5,18 @@ pub struct ApplyBlockRequest { #[prost(bytes = "vec", tag = "1")] pub block: ::prost::alloc::vec::Vec, } +/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckNullifiersByPrefixRequest { + /// Number of bits used for nullifier prefix. Currently the only supported value is 16. + #[prost(uint32, tag = "1")] + pub prefix_len: u32, + /// List of nullifiers to check. Each nullifier is specified by its prefix with length equal + /// to prefix_len + #[prost(uint32, repeated, tag = "2")] + pub nullifiers: ::prost::alloc::vec::Vec, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CheckNullifiersRequest { @@ -47,20 +59,29 @@ pub struct SyncStateRequest { /// it won't be included in the response. #[prost(message, repeated, tag = "2")] pub account_ids: ::prost::alloc::vec::Vec, - /// Determines the tags which the client is interested in. These are only the 16high bits of the - /// note's complete tag. - /// - /// The above means it is not possible to request an specific note, but only a "note family", - /// this is done to increase the privacy of the client, by hiding the note's the client is - /// intereted on. - #[prost(uint32, repeated, tag = "3")] + /// Specifies the tags which the client is interested in. + #[prost(fixed32, repeated, tag = "3")] pub note_tags: ::prost::alloc::vec::Vec, - /// Determines the nullifiers the client is interested in. - /// - /// Similarly to the note_tags, this determins only the 16high bits of the target nullifier. + /// Determines the nullifiers the client is interested in by specifying the 16high bits of the + /// target nullifier. #[prost(uint32, repeated, tag = "4")] pub nullifiers: ::prost::alloc::vec::Vec, } +/// Note synchronization request. +/// +/// Specifies note tags that client is intersted in. The server will return the first block which +/// contains a note matching `note_tags` or the chain tip. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SyncNoteRequest { + /// Last block known by the client. The response will contain data starting from the next block, + /// until the first block which contains a note of matching the requested tag. + #[prost(fixed32, tag = "1")] + pub block_num: u32, + /// Specifies the tags which the client is interested in. + #[prost(fixed32, repeated, tag = "2")] + pub note_tags: ::prost::alloc::vec::Vec, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockInputsRequest { @@ -122,3 +143,18 @@ pub struct GetBlockByNumberRequest { #[prost(fixed32, tag = "1")] pub block_num: u32, } +/// Returns delta of the account states in the range from `from_block_num` (exclusive) to +/// `to_block_num` (inclusive). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAccountStateDeltaRequest { + /// ID of the account for which the delta is requested. + #[prost(message, optional, tag = "1")] + pub account_id: ::core::option::Option, + /// Block number from which the delta is requested (exclusive). + #[prost(fixed32, tag = "2")] + pub from_block_num: u32, + /// Block number up to which the delta is requested (inclusive). + #[prost(fixed32, tag = "3")] + pub to_block_num: u32, +} diff --git a/crates/rust-client/src/rpc/tonic_client/generated/responses.rs b/crates/rust-client/src/rpc/tonic_client/generated/responses.rs index fce40c797..e5cc8d2f9 100644 --- a/crates/rust-client/src/rpc/tonic_client/generated/responses.rs +++ b/crates/rust-client/src/rpc/tonic_client/generated/responses.rs @@ -11,6 +11,13 @@ pub struct CheckNullifiersResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckNullifiersByPrefixResponse { + /// List of nullifiers matching the prefixes specified in the request. + #[prost(message, repeated, tag = "1")] + pub nullifiers: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockHeaderByNumberResponse { /// The requested block header #[prost(message, optional, tag = "1")] @@ -56,6 +63,27 @@ pub struct SyncStateResponse { #[prost(message, repeated, tag = "8")] pub nullifiers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SyncNoteResponse { + /// Number of the latest block in the chain + #[prost(fixed32, tag = "1")] + pub chain_tip: u32, + /// Block header of the block with the first note matching the specified criteria + #[prost(message, optional, tag = "2")] + pub block_header: ::core::option::Option, + /// Proof for block header's MMR with respect to the chain tip. + /// + /// More specifically, the full proof consists of `forest`, `position` and `path` components. This + /// value constitutes the `path`. The other two components can be obtained as follows: + /// - `position` is simply `resopnse.block_header.block_num` + /// - `forest` is the same as `response.chain_tip + 1` + #[prost(message, optional, tag = "3")] + pub mmr_path: ::core::option::Option, + /// List of all notes together with the Merkle paths from `response.block_header.note_root` + #[prost(message, repeated, tag = "4")] + pub notes: ::prost::alloc::vec::Vec, +} /// An account returned as a response to the GetBlockInputs #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -170,3 +198,10 @@ pub struct GetBlockByNumberResponse { #[prost(bytes = "vec", optional, tag = "1")] pub block: ::core::option::Option<::prost::alloc::vec::Vec>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAccountStateDeltaResponse { + /// The calculated `AccountStateDelta` encoded using miden native format + #[prost(bytes = "vec", optional, tag = "1")] + pub delta: ::core::option::Option<::prost::alloc::vec::Vec>, +} diff --git a/crates/rust-client/src/rpc/tonic_client/generated/rpc.rs b/crates/rust-client/src/rpc/tonic_client/generated/rpc.rs index 70e5a6627..a5c25f2fe 100644 --- a/crates/rust-client/src/rpc/tonic_client/generated/rpc.rs +++ b/crates/rust-client/src/rpc/tonic_client/generated/rpc.rs @@ -108,6 +108,33 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "CheckNullifiers")); self.inner.unary(req, path, codec).await } + pub async fn check_nullifiers_by_prefix( + &mut self, + request: impl tonic::IntoRequest< + super::super::requests::CheckNullifiersByPrefixRequest, + >, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/rpc.Api/CheckNullifiersByPrefix", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("rpc.Api", "CheckNullifiersByPrefix")); + self.inner.unary(req, path, codec).await + } pub async fn get_account_details( &mut self, request: impl tonic::IntoRequest< @@ -134,6 +161,33 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetAccountDetails")); self.inner.unary(req, path, codec).await } + pub async fn get_account_state_delta( + &mut self, + request: impl tonic::IntoRequest< + super::super::requests::GetAccountStateDeltaRequest, + >, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/rpc.Api/GetAccountStateDelta", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("rpc.Api", "GetAccountStateDelta")); + self.inner.unary(req, path, codec).await + } pub async fn get_block_by_number( &mut self, request: impl tonic::IntoRequest< @@ -234,6 +288,28 @@ pub mod api_client { .insert(GrpcMethod::new("rpc.Api", "SubmitProvenTransaction")); self.inner.unary(req, path, codec).await } + pub async fn sync_notes( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/rpc.Api/SyncNotes"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SyncNotes")); + self.inner.unary(req, path, codec).await + } pub async fn sync_state( &mut self, request: impl tonic::IntoRequest, diff --git a/crates/rust-client/src/rpc/web_tonic_client/generated/requests.rs b/crates/rust-client/src/rpc/web_tonic_client/generated/requests.rs index 7ce1e69ac..a75da6f50 100644 --- a/crates/rust-client/src/rpc/web_tonic_client/generated/requests.rs +++ b/crates/rust-client/src/rpc/web_tonic_client/generated/requests.rs @@ -5,6 +5,18 @@ pub struct ApplyBlockRequest { #[prost(bytes = "vec", tag = "1")] pub block: ::prost::alloc::vec::Vec, } +/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckNullifiersByPrefixRequest { + /// Number of bits used for nullifier prefix. Currently the only supported value is 16. + #[prost(uint32, tag = "1")] + pub prefix_len: u32, + /// List of nullifiers to check. Each nullifier is specified by its prefix with length equal + /// to prefix_len + #[prost(uint32, repeated, tag = "2")] + pub nullifiers: ::prost::alloc::vec::Vec, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CheckNullifiersRequest { @@ -47,20 +59,29 @@ pub struct SyncStateRequest { /// it won't be included in the response. #[prost(message, repeated, tag = "2")] pub account_ids: ::prost::alloc::vec::Vec, - /// Determines the tags which the client is interested in. These are only the 16high bits of the - /// note's complete tag. - /// - /// The above means it is not possible to request an specific note, but only a "note family", - /// this is done to increase the privacy of the client, by hiding the note's the client is - /// intereted on. - #[prost(uint32, repeated, tag = "3")] + /// Specifies the tags which the client is interested in. + #[prost(fixed32, repeated, tag = "3")] pub note_tags: ::prost::alloc::vec::Vec, - /// Determines the nullifiers the client is interested in. - /// - /// Similarly to the note_tags, this determins only the 16high bits of the target nullifier. + /// Determines the nullifiers the client is interested in by specifying the 16high bits of the + /// target nullifier. #[prost(uint32, repeated, tag = "4")] pub nullifiers: ::prost::alloc::vec::Vec, } +/// Note synchronization request. +/// +/// Specifies note tags that client is intersted in. The server will return the first block which +/// contains a note matching `note_tags` or the chain tip. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SyncNoteRequest { + /// Last block known by the client. The response will contain data starting from the next block, + /// until the first block which contains a note of matching the requested tag. + #[prost(fixed32, tag = "1")] + pub block_num: u32, + /// Specifies the tags which the client is interested in. + #[prost(fixed32, repeated, tag = "2")] + pub note_tags: ::prost::alloc::vec::Vec, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockInputsRequest { @@ -122,3 +143,18 @@ pub struct GetBlockByNumberRequest { #[prost(fixed32, tag = "1")] pub block_num: u32, } +/// Returns delta of the account states in the range from `from_block_num` (exclusive) to +/// `to_block_num` (inclusive). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAccountStateDeltaRequest { + /// ID of the account for which the delta is requested. + #[prost(message, optional, tag = "1")] + pub account_id: ::core::option::Option, + /// Block number from which the delta is requested (exclusive). + #[prost(fixed32, tag = "2")] + pub from_block_num: u32, + /// Block number up to which the delta is requested (inclusive). + #[prost(fixed32, tag = "3")] + pub to_block_num: u32, +} diff --git a/crates/rust-client/src/rpc/web_tonic_client/generated/responses.rs b/crates/rust-client/src/rpc/web_tonic_client/generated/responses.rs index fce40c797..e5cc8d2f9 100644 --- a/crates/rust-client/src/rpc/web_tonic_client/generated/responses.rs +++ b/crates/rust-client/src/rpc/web_tonic_client/generated/responses.rs @@ -11,6 +11,13 @@ pub struct CheckNullifiersResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckNullifiersByPrefixResponse { + /// List of nullifiers matching the prefixes specified in the request. + #[prost(message, repeated, tag = "1")] + pub nullifiers: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockHeaderByNumberResponse { /// The requested block header #[prost(message, optional, tag = "1")] @@ -56,6 +63,27 @@ pub struct SyncStateResponse { #[prost(message, repeated, tag = "8")] pub nullifiers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SyncNoteResponse { + /// Number of the latest block in the chain + #[prost(fixed32, tag = "1")] + pub chain_tip: u32, + /// Block header of the block with the first note matching the specified criteria + #[prost(message, optional, tag = "2")] + pub block_header: ::core::option::Option, + /// Proof for block header's MMR with respect to the chain tip. + /// + /// More specifically, the full proof consists of `forest`, `position` and `path` components. This + /// value constitutes the `path`. The other two components can be obtained as follows: + /// - `position` is simply `resopnse.block_header.block_num` + /// - `forest` is the same as `response.chain_tip + 1` + #[prost(message, optional, tag = "3")] + pub mmr_path: ::core::option::Option, + /// List of all notes together with the Merkle paths from `response.block_header.note_root` + #[prost(message, repeated, tag = "4")] + pub notes: ::prost::alloc::vec::Vec, +} /// An account returned as a response to the GetBlockInputs #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -170,3 +198,10 @@ pub struct GetBlockByNumberResponse { #[prost(bytes = "vec", optional, tag = "1")] pub block: ::core::option::Option<::prost::alloc::vec::Vec>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAccountStateDeltaResponse { + /// The calculated `AccountStateDelta` encoded using miden native format + #[prost(bytes = "vec", optional, tag = "1")] + pub delta: ::core::option::Option<::prost::alloc::vec::Vec>, +} diff --git a/crates/rust-client/src/rpc/web_tonic_client/generated/rpc.rs b/crates/rust-client/src/rpc/web_tonic_client/generated/rpc.rs index 46bd6cfdd..a38717e63 100644 --- a/crates/rust-client/src/rpc/web_tonic_client/generated/rpc.rs +++ b/crates/rust-client/src/rpc/web_tonic_client/generated/rpc.rs @@ -97,6 +97,33 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "CheckNullifiers")); self.inner.unary(req, path, codec).await } + pub async fn check_nullifiers_by_prefix( + &mut self, + request: impl tonic::IntoRequest< + super::super::requests::CheckNullifiersByPrefixRequest, + >, + ) -> core::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/rpc.Api/CheckNullifiersByPrefix", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("rpc.Api", "CheckNullifiersByPrefix")); + self.inner.unary(req, path, codec).await + } pub async fn get_account_details( &mut self, request: impl tonic::IntoRequest< @@ -123,6 +150,33 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetAccountDetails")); self.inner.unary(req, path, codec).await } + pub async fn get_account_state_delta( + &mut self, + request: impl tonic::IntoRequest< + super::super::requests::GetAccountStateDeltaRequest, + >, + ) -> core::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/rpc.Api/GetAccountStateDelta", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("rpc.Api", "GetAccountStateDelta")); + self.inner.unary(req, path, codec).await + } pub async fn get_block_by_number( &mut self, request: impl tonic::IntoRequest< @@ -223,6 +277,28 @@ pub mod api_client { .insert(GrpcMethod::new("rpc.Api", "SubmitProvenTransaction")); self.inner.unary(req, path, codec).await } + pub async fn sync_notes( + &mut self, + request: impl tonic::IntoRequest, + ) -> core::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/rpc.Api/SyncNotes"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SyncNotes")); + self.inner.unary(req, path, codec).await + } pub async fn sync_state( &mut self, request: impl tonic::IntoRequest, From 88ecd56eb3d6aa9df6c7699eb788232950e41ebb Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Tue, 6 Aug 2024 12:34:53 -0300 Subject: [PATCH 26/26] use unwrap_or instead of match --- crates/rust-client/src/transactions/mod.rs | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/crates/rust-client/src/transactions/mod.rs b/crates/rust-client/src/transactions/mod.rs index 86a38b722..7d117cf67 100644 --- a/crates/rust-client/src/transactions/mod.rs +++ b/crates/rust-client/src/transactions/mod.rs @@ -461,22 +461,13 @@ impl Client // Check if the account balance plus incoming assets is greater than or equal to the outgoing // fungible assets for (faucet_id, amount) in fungible_balance_map { - match account.vault().get_balance(faucet_id) { - Ok(account_asset_amount) => { - let incoming_balance = - incoming_fungible_balance_map.get(&faucet_id).unwrap_or(&0); - if account_asset_amount + incoming_balance < amount { - return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( - account_asset_amount, - amount, - ))); - } - }, - _ => { - return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( - 0, amount, - ))) - }, + let account_asset_amount = account.vault().get_balance(faucet_id).unwrap_or(0); + let incoming_balance = incoming_fungible_balance_map.get(&faucet_id).unwrap_or(&0); + if account_asset_amount + incoming_balance < amount { + return Err(ClientError::AssetError(AssetError::AssetAmountNotSufficient( + account_asset_amount, + amount, + ))); } }