From 6bbe515a45f30d6b697e809a3f42bd27eab49b5b Mon Sep 17 00:00:00 2001 From: hal3e Date: Mon, 25 Nov 2024 18:16:43 +0100 Subject: [PATCH 1/6] feat!: check if coin already in cache --- e2e/tests/providers.rs | 26 +++++++++++++++++++++ packages/fuels-accounts/src/coin_cache.rs | 17 ++++++++++++++ packages/fuels-accounts/src/provider.rs | 28 +++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/e2e/tests/providers.rs b/e2e/tests/providers.rs index 74d47cc8ce..a964e08483 100644 --- a/e2e/tests/providers.rs +++ b/e2e/tests/providers.rs @@ -776,6 +776,32 @@ async fn create_transfer( tb.build(wallet.try_provider()?).await } +#[cfg(feature = "coin-cache")] +#[tokio::test] +async fn transactions_with_the_same_utxo() -> Result<()> { + use fuels::types::errors::transaction; + + let wallet_1 = launch_provider_and_get_wallet().await?; + let provider = wallet_1.provider().unwrap(); + let wallet_2 = WalletUnlocked::new_random(Some(provider.clone())); + + let tx_1 = create_transfer(&wallet_1, 100, wallet_2.address()).await?; + let tx_2 = create_transfer(&wallet_1, 101, wallet_2.address()).await?; + + let _tx_id = provider.send_transaction(tx_1).await?; + let res = provider.send_transaction(tx_2).await; + + let err = res.expect_err("is error"); + + assert!(matches!( + err, + Error::Transaction(transaction::Reason::Validation(..)) + )); + assert!(err.to_string().contains("already in cache")); + + Ok(()) +} + #[cfg(feature = "coin-cache")] #[tokio::test] async fn test_caching() -> Result<()> { diff --git a/packages/fuels-accounts/src/coin_cache.rs b/packages/fuels-accounts/src/coin_cache.rs index 5b2040bebb..0b69e4249c 100644 --- a/packages/fuels-accounts/src/coin_cache.rs +++ b/packages/fuels-accounts/src/coin_cache.rs @@ -41,6 +41,23 @@ impl CoinsCache { } } + pub fn find_already_inserted<'a>( + &mut self, + coin_ids: impl IntoIterator)>, + ) -> Option<(CoinCacheKey, CoinTypeId)> { + for (key, ids) in coin_ids { + if let Some(items) = self.items.get(key) { + for id in ids { + if items.contains(&CoinCacheItem::new(id.clone())) { + return Some((key.clone(), id.clone())); + } + } + } + } + + None + } + pub fn get_active(&mut self, key: &CoinCacheKey) -> HashSet { self.remove_expired_entries(key); diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 862b3414fd..bfc306bdd6 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -165,6 +165,10 @@ impl Provider { &self, tx: T, ) -> Result { + #[cfg(feature = "coin-cache")] + self.check_inputs_already_in_cache(&tx.used_coins(self.base_asset_id())) + .await?; + let tx = self.prepare_transaction_for_sending(tx).await?; let tx_status = self .client @@ -233,9 +237,33 @@ impl Provider { Ok(self.client.submit(&tx.into()).await?) } + #[cfg(feature = "coin-cache")] + async fn check_inputs_already_in_cache<'a>( + &self, + coin_ids: impl IntoIterator)>, + ) -> Result<()> { + use fuels_core::types::errors::{transaction, Error}; + + if let Some(((addr, asset_id), coin_type_id)) = + self.cache.lock().await.find_already_inserted(coin_ids) + { + let msg = match coin_type_id { + CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"), + CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"), + }; + Err(Error::Transaction(transaction::Reason::Validation( + format!("{msg} already in cache. Wallet address: `{addr}`, asset id: `{asset_id}`"), + ))) + } else { + Ok(()) + } + } + #[cfg(feature = "coin-cache")] async fn submit(&self, tx: T) -> Result { let used_utxos = tx.used_coins(self.base_asset_id()); + self.check_inputs_already_in_cache(&used_utxos).await?; + let tx_id = self.client.submit(&tx.into()).await?; self.cache.lock().await.insert_multiple(used_utxos); From 74b532a59326ef80d67476dfd373be749dfbe9cc Mon Sep 17 00:00:00 2001 From: hal3e Date: Tue, 26 Nov 2024 16:10:58 +0100 Subject: [PATCH 2/6] pr comment --- packages/fuels-accounts/src/coin_cache.rs | 17 --------------- packages/fuels-accounts/src/provider.rs | 26 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/fuels-accounts/src/coin_cache.rs b/packages/fuels-accounts/src/coin_cache.rs index 0b69e4249c..5b2040bebb 100644 --- a/packages/fuels-accounts/src/coin_cache.rs +++ b/packages/fuels-accounts/src/coin_cache.rs @@ -41,23 +41,6 @@ impl CoinsCache { } } - pub fn find_already_inserted<'a>( - &mut self, - coin_ids: impl IntoIterator)>, - ) -> Option<(CoinCacheKey, CoinTypeId)> { - for (key, ids) in coin_ids { - if let Some(items) = self.items.get(key) { - for id in ids { - if items.contains(&CoinCacheItem::new(id.clone())) { - return Some((key.clone(), id.clone())); - } - } - } - } - - None - } - pub fn get_active(&mut self, key: &CoinCacheKey) -> HashSet { self.remove_expired_entries(key); diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index bfc306bdd6..70204a9476 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -237,6 +237,28 @@ impl Provider { Ok(self.client.submit(&tx.into()).await?) } + #[cfg(feature = "coin-cache")] + async fn find_in_cache<'a>( + &self, + coin_ids: impl IntoIterator)>, + ) -> Option<((Bech32Address, AssetId), CoinTypeId)> { + for (key, ids) in coin_ids { + let items = self.cache.lock().await.get_active(key); + + if items.is_empty() { + continue; + } + + for id in ids { + if items.contains(id) { + return Some((key.clone(), id.clone())); + } + } + } + + None + } + #[cfg(feature = "coin-cache")] async fn check_inputs_already_in_cache<'a>( &self, @@ -244,9 +266,7 @@ impl Provider { ) -> Result<()> { use fuels_core::types::errors::{transaction, Error}; - if let Some(((addr, asset_id), coin_type_id)) = - self.cache.lock().await.find_already_inserted(coin_ids) - { + if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await { let msg = match coin_type_id { CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"), CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"), From cc3f2a220a6311368ecfef9c1fa4bf118f49b944 Mon Sep 17 00:00:00 2001 From: hal3e Date: Tue, 26 Nov 2024 17:08:15 +0100 Subject: [PATCH 3/6] bump fuel-core --- Cargo.toml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5360aeb629..ef791eb655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,23 +86,23 @@ octocrab = { version = "0.39", default-features = false } dotenv = { version = "0.15", default-features = false } # Dependencies from the `fuel-core` repository: -fuel-core = { version = "0.40.0", default-features = false, features = [ +fuel-core = { version = "0.40.1", default-features = false, features = [ "wasm-executor", ] } -fuel-core-chain-config = { version = "0.40.0", default-features = false } -fuel-core-client = { version = "0.40.0", default-features = false } -fuel-core-poa = { version = "0.40.0", default-features = false } -fuel-core-services = { version = "0.40.0", default-features = false } -fuel-core-types = { version = "0.40.0", default-features = false } +fuel-core-chain-config = { version = "0.40.1", default-features = false } +fuel-core-client = { version = "0.40.1", default-features = false } +fuel-core-poa = { version = "0.40.1", default-features = false } +fuel-core-services = { version = "0.40.1", default-features = false } +fuel-core-types = { version = "0.40.1", default-features = false } # Dependencies from the `fuel-vm` repository: -fuel-asm = { version = "0.58.0" } -fuel-crypto = { version = "0.58.0" } -fuel-merkle = { version = "0.58.0" } -fuel-storage = { version = "0.58.0" } -fuel-tx = { version = "0.58.0" } -fuel-types = { version = "0.58.0" } -fuel-vm = { version = "0.58.0" } +fuel-asm = { version = "0.58.2" } +fuel-crypto = { version = "0.58.2" } +fuel-merkle = { version = "0.58.2" } +fuel-storage = { version = "0.58.2" } +fuel-tx = { version = "0.58.2" } +fuel-types = { version = "0.58.2" } +fuel-vm = { version = "0.58.2" } # Workspace projects fuels = { version = "0.66.10", path = "./packages/fuels", default-features = false } From ea53a86f713dd6610cace2cc2f2109896fe87df1 Mon Sep 17 00:00:00 2001 From: hal3e Date: Tue, 26 Nov 2024 17:17:42 +0100 Subject: [PATCH 4/6] versions --- .../fuels-accounts/src/provider/supported_fuel_core_version.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs b/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs index 6a370d0462..1a8bf5243d 100644 --- a/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs +++ b/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs @@ -1 +1 @@ -pub const SUPPORTED_FUEL_CORE_VERSION: semver::Version = semver::Version::new(0, 40, 0); +pub const SUPPORTED_FUEL_CORE_VERSION: semver::Version = semver::Version::new(0, 40, 1); From 446873000bbf41d3736c8287c02ce17f0c015f08 Mon Sep 17 00:00:00 2001 From: hal3e Date: Thu, 28 Nov 2024 11:23:08 +0100 Subject: [PATCH 5/6] pr comments --- packages/fuels-accounts/src/provider.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 70204a9476..bc0345129f 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -242,8 +242,10 @@ impl Provider { &self, coin_ids: impl IntoIterator)>, ) -> Option<((Bech32Address, AssetId), CoinTypeId)> { + let mut locked_cache = self.cache.lock().await; + for (key, ids) in coin_ids { - let items = self.cache.lock().await.get_active(key); + let items = locked_cache.get_active(key); if items.is_empty() { continue; From 99f4f608e49be6901166bf6f80999e0e15aed90e Mon Sep 17 00:00:00 2001 From: hal3e Date: Tue, 3 Dec 2024 12:49:30 +0100 Subject: [PATCH 6/6] pr comments --- e2e/tests/providers.rs | 4 +++- packages/fuels-accounts/src/provider.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e/tests/providers.rs b/e2e/tests/providers.rs index a964e08483..a56b6d7666 100644 --- a/e2e/tests/providers.rs +++ b/e2e/tests/providers.rs @@ -797,7 +797,9 @@ async fn transactions_with_the_same_utxo() -> Result<()> { err, Error::Transaction(transaction::Reason::Validation(..)) )); - assert!(err.to_string().contains("already in cache")); + assert!(err + .to_string() + .contains("was submitted recently in a transaction ")); Ok(()) } diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index bc0345129f..eea4cb2ddb 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -274,7 +274,7 @@ impl Provider { CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"), }; Err(Error::Transaction(transaction::Reason::Validation( - format!("{msg} already in cache. Wallet address: `{addr}`, asset id: `{asset_id}`"), + format!("{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`"), ))) } else { Ok(())