Skip to content

Commit

Permalink
feat: check if coin already in cache (#1549)
Browse files Browse the repository at this point in the history
# Summary
Checks if any of the inputs is already cached before sending/submitting
the tx.
  • Loading branch information
hal3e authored Dec 6, 2024
1 parent 08771d9 commit 9393a5a
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 14 deletions.
26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
28 changes: 28 additions & 0 deletions e2e/tests/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
50 changes: 50 additions & 0 deletions packages/fuels-accounts/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ impl Provider {
&self,
tx: T,
) -> Result<TxStatus> {
#[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
Expand Down Expand Up @@ -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<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> 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<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> 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<T: Transaction>(&self, tx: T) -> Result<TxId> {
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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 9393a5a

Please sign in to comment.