Skip to content

Commit

Permalink
Add TM01 - Sender Double Spends
Browse files Browse the repository at this point in the history
  • Loading branch information
ssantos21 committed Jul 8, 2024
1 parent 1cf2134 commit 77fa2e0
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 0 deletions.
2 changes: 2 additions & 0 deletions clients/tests/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ 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")]
async fn main() -> Result<()> {

tb01_simple_transfer::execute().await?;
tb02_transfer_address_reuse::execute().await?;
tm01_sender_double_spends::execute().await?;

Ok(())
}
117 changes: 117 additions & 0 deletions clients/tests/rust/src/tm01_sender_double_spends.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
21 changes: 21 additions & 0 deletions docs/test_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

0 comments on commit 77fa2e0

Please sign in to comment.