diff --git a/clients/apps/rust/src/main.rs b/clients/apps/rust/src/main.rs index 05e72264..737be815 100644 --- a/clients/apps/rust/src/main.rs +++ b/clients/apps/rust/src/main.rs @@ -58,6 +58,7 @@ enum Commands { force_send: Option, /// Batch id for atomic transfers batch_id: Option, + duplicated_indexes: Option>, }, /// Send a statechain coin to a transfer address TransferReceive { wallet_name: String }, @@ -158,12 +159,12 @@ async fn main() -> Result<()> { println!("{}", serde_json::to_string_pretty(&obj).unwrap()); }, - Commands::TransferSend { wallet_name, statechain_id, to_address, force_send, batch_id } => { + Commands::TransferSend { wallet_name, statechain_id, to_address, force_send, batch_id, duplicated_indexes } => { mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; let force_send = force_send.unwrap_or(false); - mercuryrustlib::transfer_sender::execute(&client_config, &to_address, &wallet_name, &statechain_id, None, force_send, batch_id).await?; + mercuryrustlib::transfer_sender::execute(&client_config, &to_address, &wallet_name, &statechain_id, duplicated_indexes, force_send, batch_id).await?; let obj = json!({"Transfer": "sent"}); diff --git a/clients/libs/rust/src/coin_status.rs b/clients/libs/rust/src/coin_status.rs index b050e058..d7b68811 100644 --- a/clients/libs/rust/src/coin_status.rs +++ b/clients/libs/rust/src/coin_status.rs @@ -73,7 +73,6 @@ async fn check_deposit(client_config: &ClientConfig, coin: &mut Coin, wallet_net let activity_utxo = format!("{}:{}", utxo.tx_hash.to_string(), utxo.tx_pos); let activity = Some(create_activity(&activity_utxo, utxo.value as u32, "deposit")); - // return Ok(Some(activity)); deposit_result = Some(DepositResult { activity: activity.unwrap(), diff --git a/clients/libs/rust/src/transfer_sender.rs b/clients/libs/rust/src/transfer_sender.rs index 5fafba39..e413b5a2 100644 --- a/clients/libs/rust/src/transfer_sender.rs +++ b/clients/libs/rust/src/transfer_sender.rs @@ -1,4 +1,4 @@ -use std::cmp::Ordering; +use std::{cmp::Ordering, str::FromStr}; use crate::{client_config::ClientConfig, deposit::create_tx1, sqlite_manager::{get_backup_txs, get_wallet, update_backup_txs, update_wallet}, transaction::new_transaction, utils::info_config}; use anyhow::{anyhow, Result}; @@ -88,14 +88,35 @@ pub async fn create_backup_transactions( return Err(anyhow!("There must be at least one coin with duplicate_index == 0")); } - // Move the coin with CONFIRMED status to the last position - // coin_list.sort_by(|a, b| { - // match (&a.status, &b.status) { - // (CoinStatus::CONFIRMED, _) => Ordering::Greater, - // (_, CoinStatus::CONFIRMED) => Ordering::Less, - // _ => Ordering::Equal, - // } - // }); + for coin in coin_list.iter_mut() { + if coin.status == CoinStatus::DUPLICATED { + let address = bitcoin::Address::from_str(&coin.aggregated_address.as_ref().unwrap())?.require_network(client_config.network)?; + let utxo_list = client_config.electrum_client.script_list_unspent(&address.script_pubkey())?; + + for unspent in utxo_list { + if coin.utxo_txid == Some(unspent.tx_hash.to_string()) && coin.utxo_vout == Some(unspent.tx_pos as u32) { + let mut is_confirmed = false; + + if unspent.height > 0 { + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + let blockheight = block_header.height; + + let confirmations = blockheight - unspent.height + 1; + + if confirmations as u32 >= client_config.confirmation_target { + is_confirmed = true; + } + } + + if !is_confirmed { + return Err(anyhow!("The coin with duplicated index {} has not yet been confirmed. This transfer cannot be performed.", coin.duplicate_index)); + } + + break; + } + } + } + } // Move the coin with CONFIRMED status to the first position coin_list.sort_by(|a, b| { diff --git a/clients/tests/rust/src/ta03_multiple_deposits.rs b/clients/tests/rust/src/ta03_multiple_deposits.rs index 01174ded..0ea08a24 100644 --- a/clients/tests/rust/src/ta03_multiple_deposits.rs +++ b/clients/tests/rust/src/ta03_multiple_deposits.rs @@ -17,7 +17,7 @@ async fn deposit(amount_in_sats: u32, client_config: &ClientConfig, deposit_addr let mut is_tx_indexed = false; while !is_tx_indexed { - is_tx_indexed = electrs::check_address(client_config, &deposit_address, 1000).await?; + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount_in_sats).await?; thread::sleep(Duration::from_secs(1)); } @@ -461,7 +461,7 @@ async fn multiple_sends_workflow(client_config: &ClientConfig, wallet1: &Wallet, Ok(()) } -async fn send_to_itself_workflow(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { +async fn send_to_itself_workflow(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { let amount = 1000; @@ -616,6 +616,82 @@ async fn send_to_itself_workflow(client_config: &ClientConfig, wallet1: &Wallet, } +async fn send_unconfirmed_duplicated_workflow(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let deposit_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + deposit(amount, &client_config, &deposit_address).await?; + + let amount = 1000; + + deposit(amount, &client_config, &deposit_address).await?; + + let amount = 2000; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_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(deposit_address.clone()) && coin.duplicate_index == 0 && coin.status == CoinStatus::CONFIRMED); + let confirmed_duplicated_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::DUPLICATED && coin.amount == Some(1000)); + let unconfirmed_duplicated_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::DUPLICATED && coin.amount == Some(2000)); + + assert!(new_coin.is_some()); + assert!(confirmed_duplicated_coin.is_some()); + assert!(unconfirmed_duplicated_coin.is_some()); + + let unconfirmed_duplicated_coin = unconfirmed_duplicated_coin.unwrap(); + + let new_coin = new_coin.unwrap(); + + 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 batch_id = None; + + let force_send = true; + + let duplicated_indexes = vec![1, 2]; + + // try to send the unconfirmed duplicated coin + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, Some(duplicated_indexes), force_send, batch_id).await; + + assert!(result.is_err()); + + let error_message = result.err().unwrap().to_string(); + + let expected_error_message = format!("The coin with duplicated index {} has not yet been confirmed. This transfer cannot be performed.", unconfirmed_duplicated_coin.duplicate_index); + + assert!(error_message.contains(expected_error_message.as_str())); + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + let batch_id = None; + + let duplicated_indexes = vec![1, 2]; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, Some(duplicated_indexes), force_send, batch_id).await; + + assert!(result.is_ok()); + + 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"); @@ -650,6 +726,8 @@ pub async fn execute() -> Result<()> { send_to_itself_workflow(&client_config, &wallet1, &wallet2).await?; + send_unconfirmed_duplicated_workflow(&client_config, &wallet1, &wallet2).await?; + println!("TA03 - Test \"Multiple Deposits in the Same Adress\" completed successfully"); Ok(())