From 77fa2e0602bcdfc5608b4d4882022c6357109a80 Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Sun, 7 Jul 2024 22:05:19 -0300 Subject: [PATCH] Add TM01 - Sender Double Spends --- clients/tests/rust/src/main.rs | 2 + .../rust/src/tm01_sender_double_spends.rs | 117 ++++++++++++++++++ docs/test_cases.md | 21 ++++ 3 files changed, 140 insertions(+) create mode 100644 clients/tests/rust/src/tm01_sender_double_spends.rs diff --git a/clients/tests/rust/src/main.rs b/clients/tests/rust/src/main.rs index 1131bdfe..cc1f19c3 100644 --- a/clients/tests/rust/src/main.rs +++ b/clients/tests/rust/src/main.rs @@ -3,6 +3,7 @@ pub mod electrs; pub mod bitcoin_core; pub mod tb01_simple_transfer; pub mod tb02_transfer_address_reuse; +pub mod tm01_sender_double_spends; use anyhow::{Result, Ok}; #[tokio::main(flavor = "current_thread")] @@ -10,6 +11,7 @@ async fn main() -> Result<()> { tb01_simple_transfer::execute().await?; tb02_transfer_address_reuse::execute().await?; + tm01_sender_double_spends::execute().await?; Ok(()) } diff --git a/clients/tests/rust/src/tm01_sender_double_spends.rs b/clients/tests/rust/src/tm01_sender_double_spends.rs new file mode 100644 index 00000000..54e95524 --- /dev/null +++ b/clients/tests/rust/src/tm01_sender_double_spends.rs @@ -0,0 +1,117 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +async fn tm01(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet, wallet3: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let batch_id = None; + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, batch_id).await; + + assert!(result.is_ok()); + + let wallet3_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet3.name).await?; + + let batch_id = None; + + // this first "double spend" is legitimate, as it will overwrite the previous transaction + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet3_transfer_adress, &wallet1.name, &statechain_id, batch_id).await; + + assert!(result.is_ok()); + + let received_statechain_ids = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet3.name).await?; + + assert!(received_statechain_ids.len() == 1); + + assert!(received_statechain_ids[0] == statechain_id.to_string()); + + let batch_id = None; + + // this second "double spend" is not legitimate, as the statecoin has already been received by wallet3 + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, batch_id).await; + + assert!(result.is_err()); + + assert!(result.err().unwrap().to_string().contains("Signature does not match authentication key")); + + // If we update wallet1, the error will happen when we try to send the coin to wallet2 + // The step above tested that the sender can double spend the coin, but the server will not accept it + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let batch_id = None; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, batch_id).await; + + assert!(result.is_err()); + + assert!(result.err().unwrap().to_string().contains("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is TRANSFERRED")); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + let wallet3 = mercuryrustlib::wallet::create_wallet( + "wallet3", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet3).await?; + + tm01(&client_config, &wallet1, &wallet2, &wallet3).await?; + + println!("TM01 - Sender Double Spends Test completed successfully"); + + Ok(()) +} diff --git a/docs/test_cases.md b/docs/test_cases.md index 28e8459e..f51ce8d7 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -59,3 +59,24 @@ 24. Confirm that the coin status changed to `WITHDRAWING` status in wallet 1 25. Generate blocks enough to confirm the withdrawal (according to the client's Settings.toml) 26. Confirm that the coin status changed to `WITHDRAWN` status in wallet 1 + +## Malicious Workflow + +### TM01 - Sender Double Spends + +01. Create wallet 1, 2 and 3 +02. Create a token +03. Generate wallet 1 deposit address with this token +04. Generate blocks enough to confirm the coin (according to the client's Settings.toml) +05. Wait for Electrs to index this deposit transaction +06. Confirm that there is a coin in `CONFIRMED` status in wallet 1 +07. Wallet 2 generates a new transfer address. +08. Wallet 1 sends the coin to this wallet 2's address. +09. Wallet 3 generates a new transfer address. +10. Wallet 1 sends the coin to this wallet 3's address. +11. Wallet 3 executes `transfer-receive`. This must succeed. +12. Wallet 1 tries to send the coin to this wallet 2's address again. +13. This time, the server must not allow it because the wallet 3 has already received the coin. +14. Update wallet 1 +15. Wallet 1 tries to send the coin to this wallet 2's address again. +16. This time, the client must not allow it because coin is in the `TRANSFERRED` state.