Skip to content

Commit

Permalink
feat!: check if coin already in cache
Browse files Browse the repository at this point in the history
  • Loading branch information
hal3e committed Nov 25, 2024
1 parent 1d06f47 commit 6bbe515
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 0 deletions.
26 changes: 26 additions & 0 deletions e2e/tests/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
17 changes: 17 additions & 0 deletions packages/fuels-accounts/src/coin_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ impl CoinsCache {
}
}

pub fn find_already_inserted<'a>(
&mut self,
coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> 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<CoinTypeId> {
self.remove_expired_entries(key);

Expand Down
28 changes: 28 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,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<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.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<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

0 comments on commit 6bbe515

Please sign in to comment.