use std::sync::Arc;
use tokio::task;
use zaru_core::crypto::ed25519::Keypair;
use zaru_core::settlement::types::SettlementStatus;
use zaru_core::settlement::traits::SettlementLayer;
use zaru_core::ledger::in_memory::InMemorySettlement;
use zaru_core::prelude::*;
#[tokio::test]
async fn test_concurrent_double_spend_attack() {
let settlement = Arc::new(InMemorySettlement::new());
let attacker_keys = Keypair::generate();
let attacker = WalletId::from(&attacker_keys.verifying);
let victim1 = WalletId::Bank("Victim1".into());
let victim2 = WalletId::Bank("Victim2".into());
settlement.deposit(attacker.clone(), Amount::new(100).unwrap());
let tx1 = Transaction::<Unsigned>::new(
TxId("tx-1".into()),
attacker.clone(),
victim1,
Amount::new(100).unwrap(),
1,
);
let tx2 = Transaction::<Unsigned>::new(
TxId("tx-2".into()),
attacker.clone(),
victim2,
Amount::new(100).unwrap(),
2,
);
let verified1 = tx1.sign(&attacker_keys).verify().unwrap();
let verified2 = tx2.sign(&attacker_keys).verify().unwrap();
let s1 = Arc::clone(&settlement);
let s2 = Arc::clone(&settlement);
let handle1 = task::spawn(async move { s1.submit(verified1).await });
let handle2 = task::spawn(async move { s2.submit(verified2).await });
let r1 = handle1.await.unwrap();
let r2 = handle2.await.unwrap();
let success_count = [r1.is_ok(), r2.is_ok()].iter().filter(|&&x| x).count();
assert_eq!(success_count, 1, "Double spend occurred");
}
#[tokio::test]
async fn test_replay_attack_same_nonce_different_txid() {
let settlement = InMemorySettlement::new();
let keypair = Keypair::generate();
let from = WalletId::from(&keypair.verifying);
let to = WalletId::Bank("Victim".into());
settlement.deposit(from.clone(), Amount::new(200).unwrap());
let amount = Amount::new(100).unwrap();
let tx1 = Transaction::<Unsigned>::new(
TxId("tx-1".into()),
from.clone(),
to.clone(),
amount.clone(),
42,
);
let tx2 = Transaction::<Unsigned>::new(TxId("tx-2".into()), from, to, amount, 42);
let verified1 = tx1.sign(&keypair).verify().unwrap();
let verified2 = tx2.sign(&keypair).verify().unwrap();
let _ = settlement.submit(verified1).await.unwrap();
let result = settlement.submit(verified2).await;
assert!(result.is_err(), "Replay attack via nonce was not prevented");
}
#[tokio::test]
async fn test_replay_attack_same_transaction() {
let settlement = InMemorySettlement::new();
let keypair = Keypair::generate();
let from = WalletId::from(&keypair.verifying);
let to = WalletId::Bank("Victim".into());
settlement.deposit(from.clone(), Amount::new(100).unwrap());
let tx = Transaction::<Unsigned>::new(
TxId("tx-replay".into()),
from,
to,
Amount::new(100).unwrap(),
1,
);
let verified = tx.sign(&keypair).verify().unwrap();
let id1 = settlement.submit(verified.clone()).await.unwrap();
let id2 = settlement.submit(verified.clone()).await.unwrap();
let id3 = settlement.submit(verified).await.unwrap();
assert_eq!(id1, id2);
assert_eq!(id2, id3);
let final_status = settlement.await_finality(&id1).await.unwrap();
assert_eq!(final_status, SettlementStatus::Finalized);
}
#[tokio::test]
async fn test_full_settlement_flow() {
let settlement = InMemorySettlement::new();
let keypair = Keypair::generate();
let from = WalletId::from(&keypair.verifying);
let to = WalletId::Bank("Linda".into());
settlement.deposit(from.clone(), Amount::new(100).unwrap());
let tx =
Transaction::<Unsigned>::new(TxId("tx-1".into()), from, to, Amount::new(100).unwrap(), 1);
let verified = tx.sign(&keypair).verify().unwrap();
let tx_id = settlement.submit(verified).await.unwrap();
let final_status = settlement.await_finality(&tx_id).await.unwrap();
assert_eq!(final_status, SettlementStatus::Finalized);
}
#[tokio::test]
async fn test_idempotent_submission() {
let settlement = InMemorySettlement::new();
let keypair = Keypair::generate();
let from = WalletId::from(&keypair.verifying);
let to = WalletId::Bank("Linda".into());
settlement.deposit(from.clone(), Amount::new(100).unwrap());
let tx = Transaction::<Unsigned>::new(
TxId("tx-dup".into()),
from,
to,
Amount::new(100).unwrap(),
1,
);
let verified = tx.sign(&keypair).verify().unwrap();
let id1 = settlement.submit(verified.clone()).await.unwrap();
let id2 = settlement.submit(verified).await.unwrap();
assert_eq!(id1, id2);
}