diff --git a/Cargo.toml b/Cargo.toml index 7bfe275ba..b4d4f750d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,23 +87,23 @@ dotenv = { version = "0.15", default-features = false } toml = { version = "0.8", 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 } diff --git a/e2e/tests/providers.rs b/e2e/tests/providers.rs index 74d47cc8c..a56b6d766 100644 --- a/e2e/tests/providers.rs +++ b/e2e/tests/providers.rs @@ -776,6 +776,34 @@ 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("was submitted recently in a transaction ")); + + Ok(()) +} + #[cfg(feature = "coin-cache")] #[tokio::test] async fn test_caching() -> Result<()> { diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 862b3414f..eea4cb2dd 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,55 @@ 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)> { + let mut locked_cache = self.cache.lock().await; + + for (key, ids) in coin_ids { + let items = locked_cache.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, + coin_ids: impl IntoIterator)>, + ) -> Result<()> { + use fuels_core::types::errors::{transaction, Error}; + + 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}`"), + }; + Err(Error::Transaction(transaction::Reason::Validation( + 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(()) + } + } + #[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); 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 6a370d046..1a8bf5243 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);