diff --git a/src/maker/api.rs b/src/maker/api.rs index f487214a..7cd679c8 100644 --- a/src/maker/api.rs +++ b/src/maker/api.rs @@ -352,7 +352,7 @@ impl Maker { ) .map_err(WalletError::Rpc)? { - if txout.confirmations < (REQUIRED_CONFIRMS as u32) { + if txout.confirmations < REQUIRED_CONFIRMS { return Err(MakerError::General( "funding tx not confirmed to required depth", )); diff --git a/src/maker/config.rs b/src/maker/config.rs index 6a48ad12..6612c5b5 100644 --- a/src/maker/config.rs +++ b/src/maker/config.rs @@ -173,7 +173,7 @@ mod tests { rpc_port = 6103 required_confirms = 1 min_contract_reaction_time = 48 - min_swap_amount = 10000 + min_swap_amount = 100000 socks_port = 19050 "#; let config_path = create_temp_config(contents, "valid_maker_config.toml"); diff --git a/src/maker/handlers.rs b/src/maker/handlers.rs index 0c24f927..ecf30a7b 100644 --- a/src/maker/handlers.rs +++ b/src/maker/handlers.rs @@ -30,7 +30,6 @@ use crate::{ contract::{ calculate_coinswap_fee, create_receivers_contract_tx, find_funding_output_index, read_hashvalue_from_contract, read_pubkeys_from_multisig_redeemscript, - FUNDING_TX_VBYTE_SIZE, }, error::ProtocolError, messages::{ @@ -363,10 +362,12 @@ impl Maker { TIME_RELATIVE_FEE_PCT, ); - let calc_funding_tx_fees = (FUNDING_TX_VBYTE_SIZE - * message.contract_feerate - * (message.next_coinswap_info.len() as u64)) - / 1000; + // NOTE: The `contract_feerate` currently represents the hardcoded `MINER_FEE` of a transaction, not the fee rate. + // This will remain unchanged to avoid modifying the structure of the [ProofOfFunding] message. + // Once issue https://github.com/citadel-tech/coinswap/issues/309 is resolved, + //`contract_feerate` will represent the actual fee rate instead of the `MINER_FEE`. + let calc_funding_tx_fees = + message.contract_feerate * (message.next_coinswap_info.len() as u64); // Check for overflow. If happens hard error. // This can happen if the fee_rate for funding tx is very high and incoming_amount is very low. @@ -401,11 +402,6 @@ impl Maker { )? }; - log::info!( - "cal coinswap fee ______________ : {:?}", - calc_coinswap_fees - ); - let act_coinswap_fees = incoming_amount .checked_sub(outgoing_amount + act_funding_txs_fees.to_sat()) .expect("This should not overflow as we just above."); @@ -420,10 +416,7 @@ impl Maker { ); log::info!( - "[{}] Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Swap Revenue = {} - /n - Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | - ", + "[{}] Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Coinswap Fee = {} | Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} |", self.config.port, Amount::from_sat(incoming_amount), Amount::from_sat(outgoing_amount), diff --git a/src/market/directory.rs b/src/market/directory.rs index cb9eb9c5..7d951fd5 100644 --- a/src/market/directory.rs +++ b/src/market/directory.rs @@ -9,7 +9,7 @@ use crate::{ market::rpc::start_rpc_server_thread, utill::{ get_dns_dir, parse_field, parse_toml, read_message, send_message, verify_fidelity_checks, - ConnectionType, HEART_BEAT_INTERVAL, DnsRequest, + ConnectionType, DnsRequest, HEART_BEAT_INTERVAL, }, wallet::{RPCConfig, WalletError}, }; diff --git a/src/market/rpc/server.rs b/src/market/rpc/server.rs index 44b3f45a..4850a183 100644 --- a/src/market/rpc/server.rs +++ b/src/market/rpc/server.rs @@ -2,7 +2,7 @@ use super::{RpcMsgReq, RpcMsgResp}; use crate::{ error::NetError, market::directory::{AddressEntry, DirectoryServer, DirectoryServerError}, - utill::{read_message, send_message}, + utill::{read_message, send_message, HEART_BEAT_INTERVAL}, }; use std::{ collections::BTreeSet, @@ -12,7 +12,6 @@ use std::{ thread::sleep, time::Duration, }; -use crate::utill::HEART_BEAT_INTERVAL; fn handle_request( socket: &mut TcpStream, address: Arc>>, diff --git a/src/taker/routines.rs b/src/taker/routines.rs index 79769583..3beed557 100644 --- a/src/taker/routines.rs +++ b/src/taker/routines.rs @@ -9,13 +9,13 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "tor")] use socks::Socks5Stream; -use std::{io::ErrorKind, net::TcpStream, thread::sleep, time::Duration, u64::MIN}; +use std::{io::ErrorKind, net::TcpStream, thread::sleep, time::Duration}; use crate::{ protocol::{ contract::{ calculate_coinswap_fee, create_contract_redeemscript, find_funding_output_index, - validate_contract_tx, FUNDING_TX_VBYTE_SIZE, + validate_contract_tx, }, error::ProtocolError, messages::{ @@ -346,9 +346,9 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( tmi.this_maker.offer.time_relative_fee_pct, ); - let miner_fees_paid_by_taker = - (FUNDING_TX_VBYTE_SIZE * MINER_FEE * (npi.next_peer_multisig_pubkeys.len() as u64)) / 1000; + let miner_fees_paid_by_taker = (tmi.funding_tx_infos.len() as u64) * MINER_FEE; let calculated_next_amount = this_amount - coinswap_fees - miner_fees_paid_by_taker; + if Amount::from_sat(calculated_next_amount) != next_amount { return Err((ProtocolError::IncorrectFundingAmount { expected: Amount::from_sat(calculated_next_amount), @@ -356,10 +356,12 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( }) .into()); } + log::info!( - "This Maker is forwarding = {} to next Maker | Next maker's fees = {} | Miner fees covered by us = {}", + "Maker Received ={} | Maker is Forwarding = {} | Coinswap Fees = {} | Miner Fees paid by us={} ", + Amount::from_sat(this_amount), next_amount, - coinswap_fees, // These are not in Amount.. + Amount::from_sat(coinswap_fees), miner_fees_paid_by_taker, ); diff --git a/src/utill.rs b/src/utill.rs index fa6b8e78..d06d2993 100644 --- a/src/utill.rs +++ b/src/utill.rs @@ -94,6 +94,7 @@ pub fn get_tor_addrs(hs_dir: &Path) -> io::Result { let mut hostname_file = File::open(hostname_file_path).unwrap(); let mut tor_addrs: String = String::new(); hostname_file.read_to_string(&mut tor_addrs)?; + tor_addrs.pop(); // Remove `\n` at the end. Ok(tor_addrs) } diff --git a/src/wallet/rpc.rs b/src/wallet/rpc.rs index b4e63e5e..d0a1a7e8 100644 --- a/src/wallet/rpc.rs +++ b/src/wallet/rpc.rs @@ -4,7 +4,7 @@ use crate::utill::HEART_BEAT_INTERVAL; use bitcoin::Network; use bitcoind::bitcoincore_rpc::{Auth, Client, RpcApi}; use serde_json::{json, Value}; -use std::{convert::TryFrom, thread, time::Duration}; +use std::{convert::TryFrom, thread}; use crate::wallet::api::KeychainKind; diff --git a/tests/abort1.rs b/tests/abort1.rs index f0a38393..1de93f07 100644 --- a/tests/abort1.rs +++ b/tests/abort1.rs @@ -7,7 +7,12 @@ use coinswap::{ }; mod test_framework; use log::{info, warn}; -use std::{assert_eq, sync::atomic::Ordering::Relaxed, thread, time::Duration}; +use std::{ + assert_eq, + sync::{atomic::Ordering::Relaxed, Arc}, + thread, + time::Duration, +}; use test_framework::*; /// Abort 1: TAKER Drops After Full Setup. @@ -30,7 +35,7 @@ fn test_stop_taker_after_setup() { // Initiate test framework, Makers. // Taker has a special behavior DropConnectionAfterFullSetup. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::DropConnectionAfterFullSetup, @@ -39,79 +44,26 @@ fn test_stop_taker_after_setup() { warn!("Running Test: Taker Cheats on Everybody."); - let bitcoind = &test_framework.bitcoind; - - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - let mut all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); - // Get the original balances - let org_taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - let org_taker_balance = org_taker_balance_descriptor_utxo + org_taker_balance_swap_coins; + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); - // ---- Start Servers and attempt Swap ---- + // Start the Maker Server threads + log::info!("Initiating Maker..."); - info!("Initiating Maker..."); - // Start the Maker server threads let maker_threads = makers .iter() .map(|maker| { @@ -122,228 +74,101 @@ fn test_stop_taker_after_setup() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - let swap_params = SwapParams { - send_amount: Amount::from_sat(500000), - maker_count: 2, - tx_count: 3, - required_confirms: 1, - }; + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); - info!("Initiating coinswap protocol"); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); - // Calculate Original balance excluding fidelity bonds. - // Bonds are created automatically after spawning the maker server. - let org_maker_balances = makers - .iter() - .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - maker_balance_descriptor_utxo + maker_balance_swap_coins + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance }) .collect::>(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); - taker_thread.join().unwrap(); + // Swap params for coinswap. + let swap_params = SwapParams { + send_amount: Amount::from_sat(500000), + maker_count: 2, + tx_count: 3, + required_confirms: 1, + }; + taker.do_coinswap(swap_params).unwrap(); - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // Wait for Taker swap thread to conclude. - - // Taker still has 6 swapcoins in its list - assert_eq!(taker.read().unwrap().get_wallet().get_swapcoins_count(), 6); - //Run Recovery script + // TODO: do something about this? warn!("Starting Taker recovery process"); - taker.write().unwrap().recover_from_swap().unwrap(); - - // All pending swapcoins are cleared now. - assert_eq!(taker.read().unwrap().get_wallet().get_swapcoins_count(), 0); + taker.recover_from_swap().unwrap(); - all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - //-------- Fee Tracking and Workflow:------------ + // ## Fee Tracking and Workflow: // - // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | - // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| - // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | - // | **Maker16102** | 500,000 | 465,384 | 31,616 | 3,000 | 34,616 | - // | **Maker6102** | 465,384 | 442,325 | 20,059 | 3,000 | 23,059 | + // ### Fee Breakdown: // - // ## 3. Final Outcome for Taker (Successful Coinswap): + // +------------------+-------------------------+--------------------------+------------+----------------------------+-------------------+ + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // +------------------+-------------------------+--------------------------+------------+----------------------------+-------------------+ + // | Taker | _ | 500,000 | _ | 3,000 | 3,000 | + // | Maker16102 | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // | Maker6102 | 463,500 | 438,642 | 21,858 | 3,000 | 24,858 | + // +------------------+-------------------------+--------------------------+------------+----------------------------+-------------------+ // - // | Participant | Coinswap Outcome (Sats) | - // |---------------|--------------------------------------------------------------------| - // | **Taker** | 442,325 = 500,000 - (Total Fees for Maker16102 + Total Fees for Maker6102) | // - // ## Regaining Funds After a Failed Coinswap: + // **Taker** => DropConnectionAfterFullSetup // - // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Total Recovery Fees (Sats) | Total Loss (Sats) | - // |----------------|------------------------------------|---------------------|----------------------------|-------------------| - // | **Taker** | 3,000 | 768 | 3,768 | 6,768 | - // | **Maker16102** | 3,000 | 768 | 3,768 | 6,768 | - // | **Maker6102** | 3,000 | 768 | 3,768 | 6,768 | + // Participants regain their initial funding amounts but incur a total loss of **6,768 sats** + // due to mining fees (recovery + initial transaction fees). // - // - Participants regain their initial funding amounts but incur a total loss of **6,768 sats** due to mining fees (recovery + initial transaction fees). - - // Check everybody looses mining fees of contract txs. - let taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - let taker_balance = taker_balance_descriptor_utxo + taker_balance_swap_coins; - // This Maker is forwarding = 0.00465384 BTC to next Maker | Next maker's fees = 33500 | Miner fees covered by us = 1116 - assert_eq!(org_taker_balance - taker_balance, Amount::from_sat(6768)); - assert_eq!(org_taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - org_taker_balance_descriptor_utxo, - Amount::from_btc(0.15).unwrap() - ); - assert_eq!(org_taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!( - org_taker_balance_live_contract, - Amount::from_btc(0.0).unwrap() - ); - assert_eq!(taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - taker_balance_descriptor_utxo, - Amount::from_btc(0.14993232).unwrap() + // ### Recovery Fees Breakdown: + // + // +------------------+------------------------------------+---------------------+--------------------+----------------------------+ + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // +------------------+------------------------------------+---------------------+--------------------+----------------------------+ + // | Taker | 3,000 | 768 | 3,000 | 6,768 | + // | Maker16102 | 3,000 | 768 | 3,000 | 6,768 | + // | Maker6102 | 3,000 | 768 | 3,000 | 6,768 | + // +------------------+------------------------------------+---------------------+--------------------+----------------------------+ + // + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, ); - assert_eq!(taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(taker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - - makers - .iter() - .zip(org_maker_balances.iter()) - .for_each(|(maker, org_balance)| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - let new_balance = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap() - + maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - - assert_eq!(*org_balance - new_balance, Amount::from_sat(6768)); - - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14992232).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - }); - info!("All checks successful. Terminating integration test case"); test_framework.stop(); diff --git a/tests/abort2_case1.rs b/tests/abort2_case1.rs index b6cd4d95..9a207e74 100644 --- a/tests/abort2_case1.rs +++ b/tests/abort2_case1.rs @@ -1,16 +1,17 @@ #![cfg(feature = "integration-test")] use bitcoin::Amount; +use bitcoind::bitcoincore_rpc::RpcApi; use coinswap::{ maker::{start_maker_server, MakerBehavior}, taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; - +use std::sync::Arc; mod test_framework; -use test_framework::*; - +use coinswap::wallet::{Destination, SendAmount}; use log::{info, warn}; use std::{sync::atomic::Ordering::Relaxed, thread, time::Duration}; +use test_framework::*; /// ABORT 2: Maker Drops Before Setup /// This test demonstrates the situation where a Maker prematurely drops connections after doing @@ -25,14 +26,17 @@ fn test_abort_case_2_move_on_with_other_makers() { // 6102 is naughty. But theres enough good ones. let makers_config_map = [ - ((6102, None), MakerBehavior::CloseAtReqContractSigsForSender), - ((16102, None), MakerBehavior::Normal), + ((6102, None), MakerBehavior::Normal), + ( + (16102, None), + MakerBehavior::CloseAtReqContractSigsForSender, + ), ((26102, None), MakerBehavior::Normal), ]; // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, @@ -45,49 +49,18 @@ fn test_abort_case_2_move_on_with_other_makers() { let bitcoind = &test_framework.bitcoind; - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = + fund_and_verify_taker(&mut taker, bitcoind, 3, Amount::from_btc(0.05).unwrap()); - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); + fund_and_verify_maker(makers_ref, bitcoind, 4, Amount::from_btc(0.05).unwrap()); - // confirm balances - generate_blocks(bitcoind, 1); - - // ---- Start Servers and attempt Swap ---- + // Start the Maker Server threads + log::info!("Initiating Maker..."); - info!("Initiating Maker..."); - // Start the Maker server threads let maker_threads = makers .iter() .map(|maker| { @@ -98,90 +71,164 @@ fn test_abort_case_2_move_on_with_other_makers() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } + + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - info!("Initiating coinswap protocol"); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - //-------- Fee Tracking and Workflow:------------ - // - // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | - // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| - // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | - // | **Maker16102** | 500,000 | 465,384 | 31,616 | 3,000 | 34,616 | - // | **Maker6102** | 465,384 | 442,325 | 20,059 | 3,000 | 23,059 | - // - // ## 3. Final Outcome for Taker (Successful Coinswap): - // - // | Participant | Coinswap Outcome (Sats) | - // |---------------|--------------------------------------------------------------------| - // | **Taker** | 442,325 = 500,000 - (Total Fees for Maker16102 + Total Fees for Maker6102) | - // - // ## 4. Final Outcome for Makers: - // - // | Participant | Coinswap Outcome (Sats) | - // |----------------|-------------------------------------------------------------------| - // | **Maker16102** | 500,000 - 465,384 - 3,000 = +31,616 | - // | **Maker6102** | 465,384 - 442,325 - 3,000 = +20,059 | - - - - // TODO: Do balance assertions. - - - // TODO: Think that whether this is good? - // Maker might not get banned as Taker may not try 6102 for swap. If it does then check its 6102. - if !taker.read().unwrap().get_bad_makers().is_empty() { + // ----------------------Swap Completed Successfully----------------------------------------------------------- + + // +------------------------------------------------------------------------------------------------------+ + // | ## Fee Tracking and Workflow | + // +------------------------------------------------------------------------------------------------------+ + // | | + // | ### Assumptions: | + // | 1. **Taker connects to Maker16102 as the first Maker.** | + // | 2. **Workflow:** Taker → Maker16102 (`CloseAtReqContractSigsForSender`) → Maker6102 → Maker26102 → | + // | Taker. | + // | | + // | ### Fee Breakdown: | + // | | + // | +------------------+-------------------------+--------------------------+------------+----------------+| + // | | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining || + // | | | | | | Fees (Sats) || + // | +------------------+-------------------------+--------------------------+------------+----------------+| + // | | Taker | _ | 500,000 | _ | 3,000 || + // | | Maker16102 | _ | _ | _ | _ || + // | | Maker6102 | 500,000 | 463,500 | 33,500 | 3,000 || + // | | Maker26102 | 463,500 | 438,642 | 21,858 | 3,000 || + // | +------------------+-------------------------+--------------------------+------------+----------------+| + // | | + // | ### Final Outcomes | + // | | + // | #### Taker (Successful Coinswap): | + // | +-------------+------------------------------------------------------------------------------------+ | + // | | Participant | Coinswap Outcome (Sats) | | + // | +-------------+------------------------------------------------------------------------------------+ | + // | | Taker | 438,642 = 500,000 - (Total Fees for Maker16102 + Total Fees for Maker6102) | | + // | +-------------+------------------------------------------------------------------------------------+ | + // | | + // | #### Makers: | + // | +---------------+-----------------------------------------------------------------------------------+| + // | | Participant | Coinswap Outcome (Sats) | | + // | +---------------+-----------------------------------------------------------------------------------+| + // | | Maker16102 | 0 (Marked as a bad Maker by Taker) | | + // | | Maker6102 | 500,000 - 463,500 - 3,000 = +33,500 | | + // | | Maker26102 | 463,500 - 438,642 - 3,000 = +21,858 | | + // | +---------------+-----------------------------------------------------------------------------------+| + // | | + // +------------------------------------------------------------------------------------------------------+ + + // Maker might not get banned as Taker may not try 16102 for swap. If it does then check its 16102. + if !taker.get_bad_makers().is_empty() { assert_eq!( - format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + format!("127.0.0.1:{}", 16102), + taker.get_bad_makers()[0].address.to_string() ); } + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); + + info!("Balance check successful."); + + // Check spending from swapcoins. + info!("Checking Spend from Swapcoin"); + + let taker_wallet_mut = taker.get_wallet_mut(); + + let swap_coins = taker_wallet_mut + .list_swap_coin_utxo_spend_info(None) + .unwrap(); + + let tx = taker_wallet_mut + .spend_from_wallet( + Amount::from_sat(1000), + SendAmount::Max, + Destination::Wallet, + &swap_coins, + ) + .unwrap(); + + assert_eq!( + tx.input.len(), + 3, + "Not all swap coin utxos got included in the spend transaction" + ); + + bitcoind.client.send_raw_transaction(&tx).unwrap(); + generate_blocks(bitcoind, 1); + + taker_wallet_mut.sync().unwrap(); + + let swap_coin_bal = taker_wallet_mut.balance_swap_coins(None).unwrap(); + let descriptor_bal = taker_wallet_mut.balance_descriptor_utxo(None).unwrap(); + + assert_eq!(swap_coin_bal, Amount::ZERO); + assert_eq!(descriptor_bal, Amount::from_btc(0.14934642).unwrap()); + + info!("All checks successful. Terminating integration test case"); + test_framework.stop(); block_generation_handle.join().unwrap(); diff --git a/tests/abort2_case2.rs b/tests/abort2_case2.rs index ad395e8e..29a71323 100644 --- a/tests/abort2_case2.rs +++ b/tests/abort2_case2.rs @@ -5,6 +5,7 @@ use coinswap::{ taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; +use std::sync::Arc; mod test_framework; use test_framework::*; @@ -39,71 +40,33 @@ fn test_abort_case_2_recover_if_no_makers_found() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, ConnectionType::CLEARNET, ); - let bitcoind = &test_framework.bitcoind; - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - let mut all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - // Get the original balances - let org_taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); - let org_taker_balance = org_taker_balance_descriptor_utxo + org_taker_balance_swap_coins; + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); - // ---- Start Servers and attempt Swap ---- + // Start the Maker Server threads + log::info!("Initiating Maker..."); - // Start the Maker server threads let maker_threads = makers .iter() .map(|maker| { @@ -114,108 +77,116 @@ fn test_abort_case_2_recover_if_no_makers_found() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); - - let swap_params = SwapParams { - send_amount: Amount::from_sat(500000), - maker_count: 2, - tx_count: 3, - required_confirms: 1, - }; - - // Calculate Original balance excluding fidelity bonds. - // Bonds are created automatically after spawning the maker server. - let org_maker_balances = makers + let org_maker_spend_balances = makers .iter() .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); - ( - maker_balance_fidelity, - maker_balance_descriptor_utxo, - maker_balance_swap_coins, - maker_balance_live_contract, - maker_balance_descriptor_utxo + maker_balance_swap_coins, - ) + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance }) .collect::>(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || taker_clone.write().unwrap().do_coinswap(swap_params)); + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); - // Wait for Taker swap thread to conclude. - // The whole swap can fail if 6102 happens to be the first peer. - // In that the swap isn't feasible, and user should modify SwapParams::maker_count. - if let Err(e) = taker_thread.join().unwrap() { + // Swap params for coinswap. + let swap_params = SwapParams { + send_amount: Amount::from_sat(500000), + maker_count: 2, + tx_count: 3, + required_confirms: 1, + }; + + if let Err(e) = taker.do_coinswap(swap_params) { assert_eq!(format!("{:?}", e), "NotEnoughMakersInOfferBook".to_string()); info!("Coinswap failed because the first maker rejected for signature"); } - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); + // -------- Fee Tracking and Workflow -------- + // + // Case 1: Maker6102 is the second maker, and the Taker recovers from an initiated swap. + // Workflow: Taker -> Maker16102 -> Maker6102 (CloseAtReqContractSigsForSender) + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // + // - Taker sends [ProofOfFunding] to Maker16102. + // - Maker16102 responds with [ReqContractSigsAsRecvrAndSender] to the Taker. + // - Taker forwards [ReqContractSigsForSender] to Maker6102, but Maker6102 does not respond, and the Taker recovers from the swap. + // + // Final Outcome for Taker (Recover from Swap): + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // + // - The Taker regains their initial funding amounts but incurs a total loss of **6,768 sats** due to mining fees. + // + // Case 2: Maker6102 is the first maker. + // Workflow: Taker -> Maker6102 (CloseAtReqContractSigsForSender) + // + // - Taker creates unbroadcasted funding transactions and sends [ReqContractSigsForSender] to Maker6102. + // - Maker6102 does not respond, and the swap fails. + // + // Final Outcome for Taker: + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|--------------------------| + // | **Taker** | 0 | + // + // Final Outcome for Makers (In both cases): + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker6102** | 0 (Marked as a bad maker by the Taker) | + // | **Maker16102** | 0 | + // Maker gets banned for being naughty. - match taker.read().unwrap().config.connection_type { + match taker.config.connection_type { ConnectionType::CLEARNET => { assert_eq!( format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } #[cfg(feature = "tor")] @@ -228,85 +199,18 @@ fn test_abort_case_2_recover_if_no_makers_found() { onion_addr.pop(); assert_eq!( format!("{}:{}", onion_addr, 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } } - all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - // Assert that Taker burned the mining fees, - // Makers are fine. - - let new_taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let new_taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - - let new_taker_balance = new_taker_balance_descriptor_utxo + new_taker_balance_swap_coins; - - // Balance will not differ if the first maker drops and swap doesn't take place. - // The recovery will happen only if the 2nd maker drops, which has 50% probabiltiy. - // Only do this assert if the balance differs, implying that the swap took place. - if new_taker_balance != org_taker_balance { - assert_eq!( - org_taker_balance - new_taker_balance, - Amount::from_sat(6768) - ); - } - makers - .iter() - .zip(org_maker_balances.iter()) - .for_each(|(maker, org_balance)| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - let new_balance = maker_balance_descriptor_utxo + maker_balance_swap_coins; - - assert_eq!(org_balance.4 - new_balance, Amount::from_sat(0)); - - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - }); - + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); test_framework.stop(); block_generation_handle.join().unwrap(); } diff --git a/tests/abort2_case3.rs b/tests/abort2_case3.rs index 50137e84..05d85f36 100644 --- a/tests/abort2_case3.rs +++ b/tests/abort2_case3.rs @@ -5,7 +5,7 @@ use coinswap::{ taker::SwapParams, utill::ConnectionType, }; - +use std::sync::Arc; mod test_framework; use coinswap::taker::TakerBehavior; use log::{info, warn}; @@ -33,7 +33,7 @@ fn maker_drops_after_sending_senders_sigs() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, @@ -44,49 +44,26 @@ fn maker_drops_after_sending_senders_sigs() { "Running Test: Maker 6102 Closes after sending sender's signature. This is really bad. Recovery is the only option." ); - let bitcoind = &test_framework.bitcoind; - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); - // ---- Start Servers and attempt Swap ---- + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); + // Start the Maker Server threads info!("Initiating Maker..."); - // Start the Maker server threads + let maker_threads = makers .iter() .map(|maker| { @@ -97,62 +74,126 @@ fn maker_drops_after_sending_senders_sigs() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } + + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - info!("Initiating coinswap protocol"); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // TODO: Do balance asserts - // Maker gets banned for being naughty. - match taker.read().unwrap().config.connection_type { + // -------- Fee Tracking and Workflow -------- + // Case 1: Taker recovers from initiated swap with Maker6102 (CloseAtProofOfFunding) + + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + + // - Taker sends [ReqContractSigsForSender] to Maker6102, Maker6102 responds with signatures. + // - Taker forwards [ProofOfFunding], but Maker6102 doesn't respond, leading to swap recovery. + + // + // Final Outcome for Taker (Recover from Swap): + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + + // Taker recovers initial funding but incurs 6,768 sats in mining fees. + + // + // Final Outcome for Makers (Case 1): + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker6102** | 0 (Bad maker marked by Taker) | + // | **Maker16102** | 0 + + //------------------------------------------------------------------------------------------------------------------------------------------------------- + + // Case 2: Taker -> Maker16102 -> Maker6102 (CloseAtProofOfFunding) + + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + + // Maker6102 reaches CloseAtProofOfFunding state, Maker16102 and Taker regain funding but incur total loss of 6,768 sats. + + // + // Final Outcome for Maker6102: + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker6102** | 0 (Bad maker marked by Taker) | + + // Final Outcome for Maker16102 and Taker: + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker16102** | 3,000 | 768 | 3,000 | 6,768 | + + // Maker6102 gets banned for being naughty. + match taker.config.connection_type { ConnectionType::CLEARNET => { assert_eq!( format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } #[cfg(feature = "tor")] @@ -165,13 +206,21 @@ fn maker_drops_after_sending_senders_sigs() { onion_addr.pop(); assert_eq!( format!("{}:{}", onion_addr, 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } } + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); + + info!("All checks successful. Terminating integration test case"); + test_framework.stop(); block_generation_handle.join().unwrap(); } diff --git a/tests/abort3_case1.rs b/tests/abort3_case1.rs index de41d4b3..fc9558f9 100644 --- a/tests/abort3_case1.rs +++ b/tests/abort3_case1.rs @@ -7,17 +7,21 @@ use coinswap::{ }; mod test_framework; -use test_framework::*; - use log::{info, warn}; use std::{ - fs::File, io::Read, path::PathBuf, sync::atomic::Ordering::Relaxed, thread, time::Duration, + fs::File, + io::Read, + path::PathBuf, + sync::{atomic::Ordering::Relaxed, Arc}, + thread, + time::Duration, }; +use test_framework::*; /// ABORT 3: Maker Drops After Setup /// Case 1: CloseAtContractSigsForRecvrAndSender /// -/// Maker closes connection after receiving a `ContractSigsForRecvrAndSender` and doesn't broadcasts it's funding txs. +/// Maker closes connection after receiving a `RespContractSigsForRecvrAndSender` and doesn't broadcasts it's funding txs. /// Taker wait until a timeout (10ses for test, 5mins for prod) and starts recovery after that. // This is problematic. Needs more detailed thought. #[test] @@ -35,7 +39,7 @@ fn abort3_case1_close_at_contract_sigs_for_recvr_and_sender() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, @@ -44,48 +48,26 @@ fn abort3_case1_close_at_contract_sigs_for_recvr_and_sender() { warn!("Running Test: Maker closes connection after receiving a ContractSigsForRecvrAndSender"); - let bitcoind = &test_framework.bitcoind; - - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - // ---- Start Servers and attempt Swap ---- - + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); + + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); + + // Start the Maker Server threads info!("Initiating Maker..."); - // Start the Maker server threads + let maker_threads = makers .iter() .map(|maker| { @@ -96,63 +78,126 @@ fn abort3_case1_close_at_contract_sigs_for_recvr_and_sender() { }) .collect::>(); - info!("Initiating coinswap protocol"); + // Makers take time to fully setup. + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - // Start swap + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // TODO: Do balance asserts - // Maker gets banned for being naughty. - match taker.read().unwrap().config.connection_type { + // -------- Fee Tracking and Workflow -------- + // + // Case 1: Maker6102 is the First Maker, and the Taker recovers from an initiated swap. + // Workflow: Taker -> Maker6102 (CloseAtContractSigsForRecvrAndSender) + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // + // - Taker forwards [ProofOfFunding] to Maker6102, receives [ReqContractSigsAsRecvrAndSender]. + // - Maker6102 reaches CloseAtContractSigsForRecvrAndSender and doesn’t broadcast funding tx. + // - Taker recovers from the swap. + // + // Final Outcome for Taker (Recover from Swap): + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // + // - Taker recovers funds but loses **6,768 sats** in mining fees. + // + // Final Outcome for Makers (Case 1): + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker6102** | 0 (Marked as a bad maker by the Taker) | + // | **Maker16102** | 0 | + // + // Case 2: Maker6102 is the Second Maker. + // Workflow: Taker -> Maker16102 -> Maker6102 (CloseAtContractSigsForRecvrAndSender) + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // + // - Maker6102 receives [ProofOfFunding] of Maker16102, sends [ReqContractSigsAsRecvrAndSender]. + // - Maker6102 reaches CloseAtContractSigsForRecvrAndSender and doesn’t broadcast funding tx. + // + // - After timeout, Taker and Maker16102 recover funds but lose **6,768 sats** each in fees. + // + // Final Outcome for Maker6102: + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker6102** | 0 (Marked as a bad maker by the Taker) | + // + // Final Outcome for Maker16102 and Taker: + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker16102** | 3,000 | 768 | 3,000 | 6,768 | + + // Maker6102 gets banned for being naughty. + match taker.config.connection_type { ConnectionType::CLEARNET => { assert_eq!( format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } #[cfg(feature = "tor")] @@ -165,13 +210,21 @@ fn abort3_case1_close_at_contract_sigs_for_recvr_and_sender() { onion_addr.pop(); assert_eq!( format!("{}:{}", onion_addr, 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } } + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); + + info!("All checks successful. Terminating integration test case"); + test_framework.stop(); block_generation_handle.join().unwrap(); } diff --git a/tests/abort3_case2.rs b/tests/abort3_case2.rs index e657d588..a99f60f2 100644 --- a/tests/abort3_case2.rs +++ b/tests/abort3_case2.rs @@ -5,6 +5,7 @@ use coinswap::{ taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; +use std::sync::Arc; mod test_framework; use test_framework::*; @@ -32,7 +33,7 @@ fn abort3_case2_close_at_contract_sigs_for_recvr() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, @@ -40,49 +41,27 @@ fn abort3_case2_close_at_contract_sigs_for_recvr() { ); warn!("Running Test: Maker closes connection after sending a ContractSigsForRecvr"); - let bitcoind = &test_framework.bitcoind; - - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - // ---- Start Servers and attempt Swap ---- + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); + + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); + + // Start the Maker Server threads info!("Initiating Maker..."); - // Start the Maker server threads + let maker_threads = makers .iter() .map(|maker| { @@ -93,63 +72,110 @@ fn abort3_case2_close_at_contract_sigs_for_recvr() { }) .collect::>(); - info!("Initiating coinswap protocol"); + // Makers take time to fully setup. + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - // Start swap + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // TODO: Do balance asserts + // -------- Fee Tracking and Workflow -------- + // + // Case 1: Maker6102 is the First Maker. + // Workflow: Taker -> Maker6102 (CloseAtContractSigsForRecvr) -----> Maker16102 + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker6102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // + // - Taker sends [ProofOfFunding] of Maker6102 to Maker16102, who replies with [ReqContractSigsForRecvrAndSender]. + // - Taker forwards [ReqContractSigsForRecvr] to Maker6102, but Maker6102 doesn't respond. + // - After a timeout, both Taker and Maker6102 recover from the swap, incurring losses. + // + // Final Outcome for Taker & Maker6102 (Recover from Swap): + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |-----------------------------------------------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker6102** (Marked as a bad maker by the Taker) | 3,000 | 768 | 3,000 | 6,768 | + // + // - Both **Taker** and **Maker6102** regain their initial funding amounts but incur a total loss of **6,768 sats** due to mining fees. + // + // Final Outcome for Maker16102: + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|------------------------------------------| + // | **Maker16102** | 0 | + // + // ------------------------------------------------------------------------------------------------------------------------ + // + // Case 2: Maker6102 is the Second Maker. + // Workflow: Taker -> Maker16102 -> Maker6102 (CloseAtContractSigsForRecvr) + // + // In this case, the Coinswap completes successfully since Maker6102, being the last maker, does not receive [ReqContractSigsForRecvr] from the Taker. + // + // The Fee balance would look like `standard_swap` IT for this case. + // Maker gets banned for being naughty. - match taker.read().unwrap().config.connection_type { + match taker.config.connection_type { ConnectionType::CLEARNET => { assert_eq!( format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } #[cfg(feature = "tor")] @@ -162,13 +188,21 @@ fn abort3_case2_close_at_contract_sigs_for_recvr() { onion_addr.pop(); assert_eq!( format!("{}:{}", onion_addr, 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() + taker.get_bad_makers()[0].address.to_string() ); } } + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); + + info!("All checks successful. Terminating integration test case"); + test_framework.stop(); block_generation_handle.join().unwrap(); } diff --git a/tests/abort3_case3.rs b/tests/abort3_case3.rs index e72f0b22..815f042c 100644 --- a/tests/abort3_case3.rs +++ b/tests/abort3_case3.rs @@ -5,13 +5,14 @@ use coinswap::{ taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; +use std::sync::Arc; mod test_framework; use test_framework::*; use log::{info, warn}; use std::{ - fs::File, io::Read, path::PathBuf, sync::atomic::Ordering::Relaxed, thread, time::Duration, + sync::atomic::Ordering::Relaxed, thread, time::Duration, }; /// ABORT 3: Maker Drops After Setup @@ -32,7 +33,7 @@ fn abort3_case3_close_at_hash_preimage_handover() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, @@ -40,47 +41,27 @@ fn abort3_case3_close_at_hash_preimage_handover() { ); warn!("Running Test: Maker closes conneciton at hash preimage handling"); - let bitcoind = &test_framework.bitcoind; - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - // ---- Start Servers and attempt Swap ---- + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); + + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); + + // Start the Maker Server threads info!("Initiating Maker..."); - // Start the Maker server threads + let maker_threads = makers .iter() .map(|maker| { @@ -91,81 +72,106 @@ fn abort3_case3_close_at_hash_preimage_handover() { }) .collect::>(); - info!("Initiating coinswap protocol"); + // Makers take time to fully setup. + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - // Start swap + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // TODO: Do balance asserts - // Maker gets banned for being naughty. - match taker.read().unwrap().config.connection_type { - ConnectionType::CLEARNET => { - assert_eq!( - format!("127.0.0.1:{}", 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() - ); - } - #[cfg(feature = "tor")] - ConnectionType::TOR => { - let onion_addr_path = - PathBuf::from(format!("/tmp/tor-rust-maker{}/hs-dir/hostname", 6102)); - let mut file = File::open(onion_addr_path).unwrap(); - let mut onion_addr: String = String::new(); - file.read_to_string(&mut onion_addr).unwrap(); - onion_addr.pop(); - assert_eq!( - format!("{}:{}", onion_addr, 6102), - taker.read().unwrap().get_bad_makers()[0] - .address - .to_string() - ); - } - } + //-------- Fee Tracking and Workflow:-------------------------------------------------------------------------- + // + // This fee scenario would occur in both cases whether Maker6102 is the first or last maker. + + // Case 1: Maker6102 is the first maker + // Workflow: Taker -> Maker6102(CloseAtHashPreimage) -> Maker16102 + // + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // | **Maker6102** | 463,500 | 438,642 | 21,858 | 3,000 | 24,858 | + // + // Maker6102 => DropConnectionAfterFullSetup + // + // Participants regain their initial funding amounts but incur a total loss of **6,768 sats** + // due to mining fees (recovery + initial transaction fees). + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker16102** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker6102** | 3,000 | 768 | 3,000 | 6,768 | + // + // Case 2: Maker16102 is the last maker. + // Workflow: Taker -> Maker16102 -> Maker16102(CloseAtHashPreimage) + // + // Same as Case 1. + //----------------------------------------------------------------------------------------------------------------------------------------------- + + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, + ); + + info!("All checks successful. Terminating integration test case"); test_framework.stop(); block_generation_handle.join().unwrap(); diff --git a/tests/dns.rs b/tests/dns.rs index 005a14c5..fc76fa29 100644 --- a/tests/dns.rs +++ b/tests/dns.rs @@ -3,7 +3,7 @@ use std::{io::Write, net::TcpStream, process::Command, thread, time::Duration}; mod test_framework; -use coinswap::utill::{ConnectionType, DnsRequest}; +use coinswap::utill::DnsRequest; use test_framework::{init_bitcoind, start_dns}; fn send_addresses(addresses: &[&str]) { @@ -56,7 +56,7 @@ fn test_dns() { let data_dir = temp_dir.join("dns"); - let mut process = start_dns(&data_dir, ConnectionType::CLEARNET, &bitcoind); + let mut process = start_dns(&data_dir, &bitcoind); let initial_addresses = vec!["127.0.0.1:8080", "127.0.0.1:8081", "127.0.0.1:8082"]; send_addresses(&initial_addresses); @@ -67,7 +67,7 @@ fn test_dns() { process.kill().expect("Failed to kill directoryd process"); process.wait().unwrap(); - let mut process = start_dns(&data_dir, ConnectionType::CLEARNET, &bitcoind); + let mut process = start_dns(&data_dir, &bitcoind); let additional_addresses = vec!["127.0.0.1:8083", "127.0.0.1:8084"]; send_addresses(&additional_addresses); @@ -76,7 +76,7 @@ fn test_dns() { process.kill().expect("Failed to kill directoryd process"); process.wait().unwrap(); - let mut process = start_dns(&data_dir, ConnectionType::CLEARNET, &bitcoind); + let mut process = start_dns(&data_dir, &bitcoind); let all_addresses = vec![ "127.0.0.1:8080", "127.0.0.1:8081", diff --git a/tests/maker_cli.rs b/tests/maker_cli.rs index 7c53398c..0cd4c572 100644 --- a/tests/maker_cli.rs +++ b/tests/maker_cli.rs @@ -2,7 +2,7 @@ #![cfg(feature = "integration-test")] use bitcoin::{Address, Amount}; use bitcoind::{bitcoincore_rpc::RpcApi, BitcoinD}; -use coinswap::utill::{setup_logger, ConnectionType}; +use coinswap::utill::setup_logger; use std::{ fs, io::{BufRead, BufReader}, @@ -142,7 +142,7 @@ fn test_maker_cli() { let maker_cli = MakerCli::new(); let dns_dir = maker_cli.data_dir.parent().unwrap(); - let mut directoryd_proc = start_dns(dns_dir, ConnectionType::CLEARNET, &maker_cli.bitcoind); + let mut directoryd_proc = start_dns(dns_dir, &maker_cli.bitcoind); let (rx, mut makerd_proc) = maker_cli.start_makerd(); // Ping check diff --git a/tests/malice1.rs b/tests/malice1.rs index 44262a16..ab34eda1 100644 --- a/tests/malice1.rs +++ b/tests/malice1.rs @@ -5,14 +5,13 @@ use coinswap::{ taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; +use std::sync::Arc; mod test_framework; use test_framework::*; use log::{info, warn}; -use std::{ - assert_eq, collections::BTreeSet, sync::atomic::Ordering::Relaxed, thread, time::Duration, -}; +use std::{assert_eq, sync::atomic::Ordering::Relaxed, thread, time::Duration}; /// Malice 1: Taker Broadcasts contract transactions prematurely. /// @@ -29,7 +28,7 @@ fn malice1_taker_broadcast_contract_prematurely() { // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::BroadcastContractAfterFullSetup, @@ -38,78 +37,26 @@ fn malice1_taker_broadcast_contract_prematurely() { warn!("Running Test: Taker broadcasts contract transaction prematurely"); - let bitcoind = &test_framework.bitcoind; - - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - let mut all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - // Get the original balances - let org_taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); - let org_taker_balance = org_taker_balance_descriptor_utxo + org_taker_balance_swap_coins; + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); - // ---- Start Servers and attempt Swap ---- + // Start the Maker Server threads + log::info!("Initiating Maker..."); - info!("Initiating Maker..."); - // Start the Maker server threads let maker_threads = makers .iter() .map(|maker| { @@ -120,203 +67,93 @@ fn malice1_taker_broadcast_contract_prematurely() { }) .collect::>(); - info!("Initiating coinswap protocol"); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); - - let swap_params = SwapParams { - send_amount: Amount::from_sat(500000), - maker_count: 2, - tx_count: 3, - required_confirms: 1, - }; - - // Calculate Original balance excluding fidelity bonds. - // Bonds are created automatically after spawning the maker server. - let org_maker_balances = makers + let org_maker_spend_balances = makers .iter() .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); - maker_balance_descriptor_utxo + maker_balance_swap_coins + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance }) - .collect::>(); + .collect::>(); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); + // Swap params for coinswap. + let swap_params = SwapParams { + send_amount: Amount::from_sat(500000), + maker_count: 2, + tx_count: 3, + required_confirms: 1, + }; + taker.do_coinswap(swap_params).unwrap(); - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - let maker_balances = makers - .iter() - .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14992232).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - - maker_balance_descriptor_utxo + maker_balance_swap_coins - }) - .collect::>(); - - all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - // Check everybody looses mining fees of contract txs. - let taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - let taker_balance = taker_balance_descriptor_utxo + taker_balance_swap_coins; - - assert!(maker_balances.len() == 1); // The set only contains one element, - // assert_eq!(maker_balances.first().unwrap(), &Amount::from_sat(14994773)); - - // Everybody looses 4227 sats for contract transactions. - assert_eq!( - org_maker_balances - .first() - .unwrap() - .checked_sub(*maker_balances.first().unwrap()) - .unwrap(), - Amount::from_sat(6768) - ); - - assert_eq!(org_taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - org_taker_balance_descriptor_utxo, - Amount::from_btc(0.15).unwrap() - ); - assert_eq!( - org_taker_balance_live_contract, - Amount::from_btc(0.0).unwrap() - ); - assert_eq!(org_taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - - assert_eq!(taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - taker_balance_descriptor_utxo, - Amount::from_btc(0.14993232).unwrap() - ); - assert_eq!(taker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - assert_eq!(taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - - assert_eq!( - org_taker_balance.checked_sub(taker_balance).unwrap(), - Amount::from_sat(6768) + //-------- Fee Tracking and Workflow:------------ + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // | **Maker6102** | 463,500 | 438,642 | 21,858 | 3,000 | 24,858 | + // + // **Taker** => BroadcastContractAfterFullSetup + // + // Participants regain their initial funding amounts but incur a total loss of **6,768 sats** + // due to mining fees (recovery + initial transaction fees). + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker16102** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker6102** | 3,000 | 768 | 3,000 | 6,768 | + + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, ); + info!("All checks successful. Terminating integration test case"); test_framework.stop(); block_generation_handle.join().unwrap(); diff --git a/tests/malice2.rs b/tests/malice2.rs index d71ba2f3..2e5286b5 100644 --- a/tests/malice2.rs +++ b/tests/malice2.rs @@ -5,11 +5,11 @@ use coinswap::{ taker::{SwapParams, TakerBehavior}, utill::ConnectionType, }; - +use std::sync::Arc; mod test_framework; use test_framework::*; -use std::{collections::BTreeSet, sync::atomic::Ordering::Relaxed, thread, time::Duration}; +use std::{ sync::atomic::Ordering::Relaxed, thread, time::Duration}; /// Malice 2: Maker Broadcasts contract transactions prematurely. /// @@ -23,87 +23,39 @@ fn malice2_maker_broadcast_contract_prematurely() { // ---- Setup ---- let makers_config_map = [ - ((6102, None), MakerBehavior::Normal), - ((16102, None), MakerBehavior::BroadcastContractAfterSetup), + ((6102, None), MakerBehavior::BroadcastContractAfterSetup), + ((16102, None), MakerBehavior::Normal), ]; // Initiate test framework, Makers. // Taker has normal behavior. - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, ConnectionType::CLEARNET, ); - let bitcoind = &test_framework.bitcoind; - - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - - let mut all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - let org_taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let org_taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = fund_and_verify_taker( + &mut taker, + &test_framework.bitcoind, + 3, + Amount::from_btc(0.05).unwrap(), + ); - let org_taker_balance = org_taker_balance_descriptor_utxo + org_taker_balance_swap_coins; + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker( + makers_ref, + &test_framework.bitcoind, + 4, + Amount::from_btc(0.05).unwrap(), + ); - // ---- Start Servers and attempt Swap ---- + // Start the Maker Server threads + log::info!("Initiating Maker..."); - // Start the Maker server threads let maker_threads = makers .iter() .map(|maker| { @@ -114,201 +66,123 @@ fn malice2_maker_broadcast_contract_prematurely() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } + + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - // Calculate Original balance excluding fidelity bonds. - // Bonds are created automatically after spawning the maker server. - let org_maker_balances = makers - .iter() - .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!( - maker_balance_descriptor_utxo, - Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - maker_balance_descriptor_utxo + maker_balance_swap_coins - }) - .collect::>(); - - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - // ---- After Swap checks ---- + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - let maker_balances = makers - .iter() - .map(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - // If the first maker misbehaves, then the 2nd maker doesn't loose anything. - // as they haven't broadcasted their outgoing swap. - assert!( - maker_balance_descriptor_utxo == Amount::from_btc(0.14992232).unwrap() - || maker_balance_descriptor_utxo == Amount::from_btc(0.14999).unwrap() - ); - assert_eq!(maker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - - maker_balance_descriptor_utxo + maker_balance_swap_coins - }) - .collect::>(); - - all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - let taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - let taker_balance = taker_balance_descriptor_utxo + taker_balance_swap_coins; - - assert_eq!(org_taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - org_taker_balance_descriptor_utxo, - Amount::from_btc(0.15).unwrap() - ); - assert_eq!( - org_taker_balance_live_contract, - Amount::from_btc(0.0).unwrap() + // -------- Fee Tracking and Workflow -------- + // + // Case 1: Maker6102 is the First Maker. + // Workflow: Taker -> Maker6102 (BroadcastContractAfterSetup) -> Maker16102 + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker6102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // + // Maker6102 => BroadcastContractAfterSetup + // + // Seeing those contract txes, the Taker recovers from the swap. + // Taker and Maker6102 recover funds but lose **6,768 sats** each in fees. + // + // Final Outcome for Taker & Maker6102: + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker6102** | 3,000 | 768 | 3,000 | 6,768 | + // + // Final Outcome for Maker16102: + // | Participant | Coinswap Outcome (Sats) | + // |----------------|--------------------------| + // | **Maker16102** | 0 | + // + // ------------------------------------------------------------------------------------------------------------------------ + // + // Case 2: Maker6102 is the Last Maker. + // Workflow: Taker -> Maker16102 -> Maker6102 (BroadcastContractAfterSetup) + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // | **Maker6102** | 463,500 | 438,642 | 21,858 | 3,000 | 24,858 | + // + // Maker6102 => BroadcastContractAfterSetup + // + // Participants regain their initial funding amounts but incur a total loss of **6,768 sats** + // due to mining fees (recovery + initial transaction fees). + // + // | Participant | Mining Fee for Contract txes (Sats) | Timelock Fee (Sats) | Funding Fee (Sats) | Total Recovery Fees (Sats) | + // |----------------|------------------------------------|---------------------|--------------------|----------------------------| + // | **Taker** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker16102** | 3,000 | 768 | 3,000 | 6,768 | + // | **Maker6102** | 3,000 | 768 | 3,000 | 6,768 | + + // After Swap checks: + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, ); - assert_eq!(org_taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - assert_eq!(taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!( - taker_balance_descriptor_utxo, - Amount::from_btc(0.14993232).unwrap() - ); - assert_eq!(taker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - assert_eq!(taker_balance_swap_coins, Amount::from_btc(0.0).unwrap()); - - assert_eq!(*maker_balances.first().unwrap(), Amount::from_sat(14992232)); - - // Everybody looses 4227 sats for contract transactions. - assert_eq!( - org_maker_balances - .first() - .unwrap() - .checked_sub(*maker_balances.first().unwrap()) - .unwrap(), - Amount::from_sat(6768) - ); - - assert_eq!( - org_taker_balance.checked_sub(taker_balance).unwrap(), - Amount::from_sat(6768) - ); + log::info!("All checks successful. Terminating integration test case"); test_framework.stop(); block_generation_handle.join().unwrap(); diff --git a/tests/standard_swap.rs b/tests/standard_swap.rs index e4d6c676..864b8c39 100644 --- a/tests/standard_swap.rs +++ b/tests/standard_swap.rs @@ -6,6 +6,7 @@ use coinswap::{ utill::ConnectionType, wallet::{Destination, SendAmount}, }; +use std::sync::Arc; use bitcoind::bitcoincore_rpc::RpcApi; @@ -33,172 +34,30 @@ fn test_standard_coinswap() { // ConnectionType::TOR // }; + let connection_type = ConnectionType::CLEARNET; + // Initiate test framework, Makers and a Taker with default behavior. -<<<<<<< HEAD - let (test_framework, taker, makers, directory_server_instance, block_generation_handle) = + let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) = TestFramework::init( makers_config_map.into(), TakerBehavior::Normal, connection_type, ); -======= - let (test_framework, taker, makers, directory_server_instance) = TestFramework::init( - makers_config_map.into(), - TakerBehavior::Normal, - ConnectionType::CLEARNET, - ); ->>>>>>> 465bbbc (WIP) warn!("Running Test: Standard Coinswap Procedure"); - let bitcoind = &test_framework.bitcoind; - info!("Initiating Takers..."); - // Fund the Taker and Makers with 3 utxos of 0.05 btc each. - for _ in 0..3 { - let taker_address = taker - .write() - .unwrap() - .get_wallet_mut() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &taker_address, Amount::from_btc(0.05).unwrap()); - - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - } - - // Coins for fidelity creation - makers.iter().for_each(|maker| { - let maker_addrs = maker - .get_wallet() - .write() - .unwrap() - .get_next_external_address() - .unwrap(); - send_to_address(bitcoind, &maker_addrs, Amount::from_btc(0.05).unwrap()); - }); - - // confirm balances - generate_blocks(bitcoind, 1); - // --- Basic Checks ---- - - // Assert external address index reached to 4. - assert_eq!(taker.read().unwrap().get_wallet().get_external_index(), &3); - makers.iter().for_each(|maker| { - let next_external_index = *maker.get_wallet().read().unwrap().get_external_index(); - assert_eq!(next_external_index, 4); - }); - - // Check if utxo list looks good. - // TODO: Assert other interesting things from the utxo list. - - let mut all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - - let taker_no_of_descriptor_utxo_unspent = taker - .read() - .unwrap() - .get_wallet() - .list_descriptor_utxo_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - let taker_no_of_fidelity_unspent = taker - .read() - .unwrap() - .get_wallet() - .list_fidelity_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - let taker_no_of_swap_coin_unspent = taker - .read() - .unwrap() - .get_wallet() - .list_swap_coin_utxo_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - let taker_no_of_live_contract_unspent = taker - .read() - .unwrap() - .get_wallet() - .list_live_contract_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - assert_eq!(taker_no_of_descriptor_utxo_unspent, 3); - assert_eq!(taker_no_of_fidelity_unspent, 0); - assert_eq!(taker_no_of_swap_coin_unspent, 0); - assert_eq!(taker_no_of_live_contract_unspent, 0); - - makers.iter().for_each(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - - let maker_no_of_descriptor_utxo_unspent = maker - .get_wallet() - .read() - .unwrap() - .list_descriptor_utxo_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - let maker_no_of_fidelity_unspent = maker - .get_wallet() - .read() - .unwrap() - .list_fidelity_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - let maker_no_of_swap_coin_unspent = maker - .get_wallet() - .read() - .unwrap() - .list_swap_coin_utxo_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - let maker_no_of_live_contract_unspent = maker - .get_wallet() - .read() - .unwrap() - .list_live_contract_spend_info(Some(&all_utxos)) - .unwrap() - .len(); - - assert_eq!(maker_no_of_descriptor_utxo_unspent, 4); - assert_eq!(maker_no_of_fidelity_unspent, 0); - assert_eq!(maker_no_of_swap_coin_unspent, 0); - assert_eq!(maker_no_of_live_contract_unspent, 0); - }); - - // Check locking non-wallet utxos worked. - taker - .read() - .unwrap() - .get_wallet() - .lock_unspendable_utxos() - .unwrap(); - makers.iter().for_each(|maker| { - maker - .get_wallet() - .read() - .unwrap() - .lock_unspendable_utxos() - .unwrap(); - }); - - // ---- Start Servers and attempt Swap ---- - - info!("Initiating Maker..."); - // Start the Maker server threads + // Fund the Taker with 3 utxos of 0.05 btc each and do basic checks on the balance + let org_taker_spend_balance = + fund_and_verify_taker(&mut taker, bitcoind, 3, Amount::from_btc(0.05).unwrap()); + + // Fund the Maker with 4 utxos of 0.05 btc each and do basic checks on the balance. + let makers_ref = makers.iter().map(Arc::as_ref).collect::>(); + fund_and_verify_maker(makers_ref, bitcoind, 4, Amount::from_btc(0.05).unwrap()); + + // Start the Maker Server threads + log::info!("Initiating Maker..."); + let maker_threads = makers .iter() .map(|maker| { @@ -209,199 +68,106 @@ fn test_standard_coinswap() { }) .collect::>(); - // Start swap - // Makers take time to fully setup. - makers.iter().for_each(|maker| { - while !maker.is_setup_complete.load(Relaxed) { - log::info!("Waiting for maker setup completion"); - // Introduce a delay of 10 seconds to prevent write lock starvation. - thread::sleep(Duration::from_secs(10)); - continue; - } - }); + let org_maker_spend_balances = makers + .iter() + .map(|maker| { + while !maker.is_setup_complete.load(Relaxed) { + log::info!("Waiting for maker setup completion"); + // Introduce a delay of 10 seconds to prevent write lock starvation. + thread::sleep(Duration::from_secs(10)); + continue; + } + // Check balance after setting up maker server. + let wallet = maker.wallet.read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + assert_eq!(seed_balance, Amount::from_btc(0.14999).unwrap()); + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance + }) + .collect::>(); + + // Initiate Coinswap + log::info!("Initiating coinswap protocol"); + + // Swap params for coinswap. let swap_params = SwapParams { send_amount: Amount::from_sat(500000), maker_count: 2, tx_count: 3, required_confirms: 1, }; + taker.do_coinswap(swap_params).unwrap(); - info!("Initiating coinswap protocol"); - // Spawn a Taker coinswap thread. - let taker_clone = taker.clone(); - let taker_thread = thread::spawn(move || { - taker_clone - .write() - .unwrap() - .do_coinswap(swap_params) - .unwrap(); - }); - - // Wait for Taker swap thread to conclude. - taker_thread.join().unwrap(); - - // Wait for Maker threads to conclude. + // After Swap is done, wait for maker threads to conclude. makers .iter() .for_each(|maker| maker.shutdown.store(true, Relaxed)); + maker_threads .into_iter() .for_each(|thread| thread.join().unwrap()); - info!("All coinswaps processed successfully. Transaction complete."); + log::info!("All coinswaps processed successfully. Transaction complete."); + // Shutdown Directory Server directory_server_instance.shutdown.store(true, Relaxed); thread::sleep(Duration::from_secs(10)); - // ---- After Swap Asserts ---- - - info!("Final Balance Checks for process"); - // Check everybody hash 6 swapcoins. - assert_eq!(taker.read().unwrap().get_wallet().get_swapcoins_count(), 6); - makers.iter().for_each(|maker| { - let swapcoin_count = maker.get_wallet().read().unwrap().get_swapcoins_count(); - assert_eq!(swapcoin_count, 6); - }); - - // Check balances makes sense - all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - assert_eq!(all_utxos.len(), 12); // how 12? - - let taker_spendable_bal = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap() - + taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - - assert_eq!(taker_spendable_bal, Amount::from_btc(0.1498284).unwrap()); - - let taker_balance_fidelity = taker - .read() - .unwrap() - .get_wallet() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let taker_balance_descriptor_utxo = taker - .read() - .unwrap() - .get_wallet() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let taker_balance_swap_coins = taker - .read() - .unwrap() - .get_wallet() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let taker_balance_live_contract = taker - .read() - .unwrap() - .get_wallet() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - assert_eq!( - taker_balance_fidelity - + taker_balance_descriptor_utxo - + taker_balance_swap_coins - + taker_balance_live_contract, - Amount::from_btc(0.1498284).unwrap() + //-------- Fee Tracking and Workflow:------------ + // + // | Participant | Amount Received (Sats) | Amount Forwarded (Sats) | Fee (Sats) | Funding Mining Fees (Sats) | Total Fees (Sats) | + // |----------------|------------------------|-------------------------|------------|----------------------------|-------------------| + // | **Taker** | _ | 500,000 | _ | 3,000 | 3,000 | + // | **Maker16102** | 500,000 | 463,500 | 33,500 | 3,000 | 36,500 | + // | **Maker6102** | 463,500 | 438,642 | 21,858 | 3,000 | 24,858 | + // + // ## 3. Final Outcome for Taker (Successful Coinswap): + // + // | Participant | Coinswap Outcome (Sats) | + // |---------------|---------------------------------------------------------------------------| + // | **Taker** | 438,642= 500,000 - (Total Fees for Maker16102 + Total Fees for Maker6102) | + // + // ## 4. Final Outcome for Makers: + // + // | Participant | Coinswap Outcome (Sats) | + // |----------------|-------------------------------------------------------------------| + // | **Maker16102** | 500,000 - 463,500 - 3,000 = +33,500 | + // | **Maker6102** | 465,384 - 438,642 - 3,000 = +21,858 | + + // After Swap Asserts + verify_swap_results( + &taker, + &makers, + org_taker_spend_balance, + org_maker_spend_balances, ); - assert_eq!( - taker_balance_descriptor_utxo, - Amount::from_btc(0.14497).unwrap() - ); - assert_eq!( - taker_balance_swap_coins, - Amount::from_btc(0.0048584).unwrap() - ); - assert_eq!(taker_balance_fidelity, Amount::from_btc(0.0).unwrap()); - assert_eq!(taker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - - makers.iter().for_each(|maker| { - all_utxos = maker.get_wallet().read().unwrap().get_all_utxo().unwrap(); - assert_eq!(all_utxos.len(), 10); - - let maker_balance_fidelity = maker - .get_wallet() - .read() - .unwrap() - .balance_fidelity_bonds(Some(&all_utxos)) - .unwrap(); - let maker_balance_descriptor_utxo = maker - .get_wallet() - .read() - .unwrap() - .balance_descriptor_utxo(Some(&all_utxos)) - .unwrap(); - let maker_balance_swap_coins = maker - .get_wallet() - .read() - .unwrap() - .balance_swap_coins(Some(&all_utxos)) - .unwrap(); - let maker_balance_live_contract = maker - .get_wallet() - .read() - .unwrap() - .balance_live_contract(Some(&all_utxos)) - .unwrap(); - - let maker_total_balance = maker.get_wallet().read().unwrap().balance().unwrap(); - - assert!( - maker_total_balance == Amount::from_btc(0.20003044).unwrap() - || maker_total_balance == Amount::from_btc(0.20003116).unwrap(), - "maker total balance didn't match any of the expected values" - ); - - assert!( - maker_balance_descriptor_utxo == Amount::from_btc(0.14503116).unwrap() - || maker_balance_descriptor_utxo == Amount::from_btc(0.1451016).unwrap(), - "maker_balance_descriptor_utxo does not match any of the expected values" - ); - - assert!( - maker_balance_swap_coins == Amount::from_btc(0.00492884).unwrap() - || maker_balance_swap_coins == Amount::from_btc(0.005).unwrap(), - "maker_balance_swap_coins does not match any of the expected values" - ); - assert_eq!(maker_balance_fidelity, Amount::from_btc(0.05).unwrap()); - assert_eq!(maker_balance_live_contract, Amount::from_btc(0.0).unwrap()); - - let maker_spendable_balance = maker_balance_descriptor_utxo + maker_balance_swap_coins; - - assert!( - maker_spendable_balance == Amount::from_btc(0.15003116).unwrap() - || maker_spendable_balance == Amount::from_btc(0.15003044).unwrap(), - "maker spendable balance didn't match any of the expected values" - ); - }); info!("Balance check successful."); // Check spending from swapcoins. info!("Checking Spend from Swapcoin"); - let swap_coins = taker - .read() - .unwrap() - .get_wallet() + + let taker_wallet_mut = taker.get_wallet_mut(); + let swap_coins = taker_wallet_mut .list_swap_coin_utxo_spend_info(None) .unwrap(); - let tx = taker - .write() - .unwrap() - .get_wallet_mut() + let tx = taker_wallet_mut .spend_from_wallet( Amount::from_sat(1000), SendAmount::Max, @@ -419,18 +185,11 @@ fn test_standard_coinswap() { bitcoind.client.send_raw_transaction(&tx).unwrap(); generate_blocks(bitcoind, 1); - taker.write().unwrap().get_wallet_mut().sync().unwrap(); - - let taker_read = taker.read().unwrap(); - - let swap_coin_bal = taker_read.get_wallet().balance_swap_coins(None).unwrap(); - let descriptor_bal = taker_read - .get_wallet() - .balance_descriptor_utxo(None) - .unwrap(); + let swap_coin_bal = taker_wallet_mut.balance_swap_coins(None).unwrap(); + let descriptor_bal = taker_wallet_mut.balance_descriptor_utxo(None).unwrap(); assert_eq!(swap_coin_bal, Amount::ZERO); - assert_eq!(descriptor_bal, Amount::from_btc(0.1498184).unwrap()); + assert_eq!(descriptor_bal, Amount::from_btc(0.14934642).unwrap()); info!("All checks successful. Terminating integration test case"); diff --git a/tests/taker_cli.rs b/tests/taker_cli.rs index e6f71cc0..9ead2e4b 100644 --- a/tests/taker_cli.rs +++ b/tests/taker_cli.rs @@ -36,8 +36,6 @@ impl TakerCli { self.data_dir.to_str().unwrap(), "--bitcoin-network", "regtest", - "--connection-type", - "clearnet", ]; // RPC authentication (user:password) from the cookie file diff --git a/tests/test_framework/mod.rs b/tests/test_framework/mod.rs index 01e6f5b4..4d53a806 100644 --- a/tests/test_framework/mod.rs +++ b/tests/test_framework/mod.rs @@ -11,14 +11,14 @@ //! The test data also includes the backend bitcoind data-directory, which is useful for observing the blockchain states after a swap. //! //! Checkout `tests/standard_swap.rs` for example of simple coinswap simulation test between 1 Taker and 2 Makers. +use bitcoin::Amount; use std::{ - collections::HashMap, env::{self, consts}, fs, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering::Relaxed}, - Arc, RwLock, + Arc, }, thread::{self, JoinHandle}, time::Duration, @@ -113,15 +113,10 @@ pub fn await_message(rx: &Receiver, expected_message: &str) { // Start the DNS server based on given connection type and considers data directory for the server. #[allow(dead_code)] -pub fn start_dns( - data_dir: &std::path::Path, - conn_type: ConnectionType, - bitcoind: &BitcoinD, -) -> process::Child { +pub fn start_dns(data_dir: &std::path::Path, bitcoind: &BitcoinD) -> process::Child { let (stdout_sender, stdout_recv): (Sender, Receiver) = mpsc::channel(); let (stderr_sender, stderr_recv): (Sender, Receiver) = mpsc::channel(); - let conn_type = format!("{}", conn_type); let mut args = vec![ "--data-directory", @@ -182,6 +177,197 @@ pub fn start_dns( directoryd_process } +#[allow(dead_code)] +pub fn fund_and_verify_taker( + taker: &mut Taker, + bitcoind: &BitcoinD, + utxo_count: u32, + utxo_value: Amount, +) -> Amount { + log::info!("Funding Takers..."); + + // Fund the Taker with 3 utxos of 0.05 btc each. + for _ in 0..utxo_count { + let taker_address = taker.get_wallet_mut().get_next_external_address().unwrap(); + send_to_address(bitcoind, &taker_address, utxo_value); + } + + // confirm balances + generate_blocks(bitcoind, 1); + + //------Basic Checks----- + + let wallet = taker.get_wallet(); + // Assert external address index reached to 3. + assert_eq!(wallet.get_external_index(), &utxo_count); + + // Check if utxo list looks good. + // TODO: Assert other interesting things from the utxo list. + + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + // TODO: Think about this: utxo_count*utxo_amt. + assert_eq!(seed_balance, Amount::from_btc(0.15).unwrap()); + assert_eq!(fidelity_balance, Amount::ZERO); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + + seed_balance + swapcoin_balance +} + +#[allow(dead_code)] +pub fn fund_and_verify_maker( + makers: Vec<&Maker>, + bitcoind: &BitcoinD, + utxo_count: u32, + utxo_value: Amount, +) { + // Fund the Maker with 4 utxos of 0.05 btc each. + + log::info!("Funding Makers..."); + + makers.iter().for_each(|&maker| { + // let wallet = maker..write().unwrap(); + let mut wallet_write = maker.wallet.write().unwrap(); + + for _ in 0..utxo_count { + let maker_addr = wallet_write.get_next_external_address().unwrap(); + send_to_address(bitcoind, &maker_addr, utxo_value); + } + }); + + // confirm balances + generate_blocks(bitcoind, 1); + + // --- Basic Checks ---- + makers.iter().for_each(|&maker| { + let wallet = maker.get_wallet().read().unwrap(); + // Assert external address index reached to 4. + assert_eq!(wallet.get_external_index(), &utxo_count); + + let all_utxos = wallet.get_all_utxo().unwrap(); + + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + // TODO: Think about this: utxo_count*utxo_amt. + assert_eq!(seed_balance, Amount::from_btc(0.20).unwrap()); + assert_eq!(fidelity_balance, Amount::ZERO); + assert_eq!(swapcoin_balance, Amount::ZERO); + assert_eq!(live_contract_balance, Amount::ZERO); + }); +} + +/// Verifies the results of a coinswap for the taker and makers after performing a swap. +#[allow(dead_code)] +pub fn verify_swap_results( + taker: &Taker, + makers: &[Arc], + org_taker_spend_balance: Amount, + org_maker_spend_balances: Vec, +) { + // Check Taker balances + { + let wallet = taker.get_wallet(); + let all_utxos = wallet.get_all_utxo().unwrap(); + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + let spendable_balance = seed_balance + swapcoin_balance; + + assert!( + seed_balance == Amount::from_btc(0.14497).unwrap() // Successful coinswap + || seed_balance == Amount::from_btc(0.14993232).unwrap() // Recovery via timelock + || seed_balance == Amount::from_btc(0.15).unwrap(), // No spending + "Taker seed balance mismatch" + ); + + assert!( + swapcoin_balance == Amount::from_btc(0.00438642).unwrap() // Successful coinswap + || swapcoin_balance == Amount::ZERO, // Unsuccessful coinswap + "Taker swapcoin balance mismatch" + ); + + assert_eq!(live_contract_balance, Amount::ZERO); + assert_eq!(fidelity_balance, Amount::ZERO); + + // Check balance difference + let balance_diff = org_taker_spend_balance + .checked_sub(spendable_balance) + .unwrap(); + + assert!( + balance_diff == Amount::from_sat(64358) // Successful coinswap + || balance_diff == Amount::from_sat(6768) // Recovery via timelock + || balance_diff == Amount::ZERO, // No spending + "Taker spendable balance change mismatch" + ); + } + + // Check Maker balances + makers + .iter() + .zip(org_maker_spend_balances.iter()) + .for_each(|(maker, org_spend_balance)| { + let wallet = maker.get_wallet().read().unwrap(); + let all_utxos = wallet.get_all_utxo().unwrap(); + let fidelity_balance = wallet.balance_fidelity_bonds(Some(&all_utxos)).unwrap(); + let seed_balance = wallet.balance_descriptor_utxo(Some(&all_utxos)).unwrap(); + let swapcoin_balance = wallet.balance_swap_coins(Some(&all_utxos)).unwrap(); + let live_contract_balance = wallet.balance_live_contract(Some(&all_utxos)).unwrap(); + + let spendable_balance = seed_balance + swapcoin_balance; + + assert!( + seed_balance == Amount::from_btc(0.14557358).unwrap() // First maker on successful coinswap + || seed_balance == Amount::from_btc(0.14532500).unwrap() // Second maker on successful coinswap + || seed_balance == Amount::from_btc(0.14999).unwrap() // No spending + || seed_balance == Amount::from_btc(0.14992232).unwrap(), // Recovery via timelock + "Maker seed balance mismatch" + ); + + assert!( + swapcoin_balance == Amount::from_btc(0.005).unwrap() // First maker + || swapcoin_balance == Amount::from_btc(0.00463500).unwrap() // Second maker + || swapcoin_balance == Amount::ZERO, // No swap or funding tx missing + "Maker swapcoin balance mismatch" + ); + + assert_eq!(fidelity_balance, Amount::from_btc(0.05).unwrap()); + assert_eq!(live_contract_balance, Amount::ZERO); + + // Check spendable balance difference. + let balance_diff = match org_spend_balance.checked_sub(spendable_balance) { + None => spendable_balance.checked_sub(*org_spend_balance).unwrap(), // Successful swap as Makers balance increase by Coinswap fee. + Some(diff) => diff, // No spending or unsuccessful swap + }; + + assert!( + balance_diff == Amount::from_sat(33500) // First maker fee + || balance_diff == Amount::from_sat(21858) // Second maker fee + || balance_diff == Amount::ZERO // No spending + || balance_diff == Amount::from_sat(6768) // Recovery via timelock + || balance_diff == Amount::from_sat(466500), // TODO: Investigate this value + "Maker spendable balance change mismatch" + ); + }); +} + /// The Test Framework. /// /// Handles initializing, operating and cleaning up of all backend processes. Bitcoind, Taker and Makers. @@ -206,12 +392,12 @@ impl TestFramework { /// If no bitcoind conf is provide a default value will be used. #[allow(clippy::type_complexity)] pub fn init( - makers_config_map: HashMap<(u16, Option), MakerBehavior>, + makers_config_map: Vec<((u16, Option), MakerBehavior)>, taker_behavior: TakerBehavior, connection_type: ConnectionType, ) -> ( Arc, - Arc>, + Taker, Vec>, Arc, JoinHandle<()>, @@ -256,16 +442,15 @@ impl TestFramework { // Create the Taker. let taker_rpc_config = rpc_config.clone(); - let taker = Arc::new(RwLock::new( - Taker::init( - Some(temp_dir.join("taker")), - None, - Some(taker_rpc_config), - taker_behavior, - Some(connection_type), - ) - .unwrap(), - )); + let taker = Taker::init( + Some(temp_dir.join("taker")), + None, + Some(taker_rpc_config), + taker_behavior, + Some(connection_type), + ) + .unwrap(); + let mut base_rpc_port = 3500; // Random port for RPC connection in tests. (Not used) // Create the Makers as per given configuration map. let makers = makers_config_map