From 660019cc6fe31bc07fbffc89dc88b0647d2e217f Mon Sep 17 00:00:00 2001 From: mojoX911 Date: Mon, 25 Nov 2024 15:32:51 +0530 Subject: [PATCH 1/5] Fix the Fee --- src/maker/api.rs | 52 ++++++++++++++++++++++++--- src/maker/config.rs | 51 ++++++++------------------ src/maker/handlers.rs | 77 +++++++++++++++++++++------------------- src/maker/server.rs | 19 ++-------- src/protocol/contract.rs | 71 +++++++++++++++++------------------- src/taker/routines.rs | 30 ++++++++-------- 6 files changed, 154 insertions(+), 146 deletions(-) diff --git a/src/maker/api.rs b/src/maker/api.rs index 17afc0e2..b135c800 100644 --- a/src/maker/api.rs +++ b/src/maker/api.rs @@ -46,9 +46,51 @@ use crate::{ use super::{config::MakerConfig, error::MakerError}; -use crate::maker::server::{ - HEART_BEAT_INTERVAL_SECS, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS, -}; +/// The core server process interval. many of the maker server's internal threads "beats" at this frequency. +pub const HEART_BEAT_INTERVAL_SECS: u64 = 3; + +/// RPC Backend health check interval. +pub const RPC_PING_INTERVAL_SECS: u64 = 60; + +// Currently we don't refresh address at DNS. The Maker only post it once at startup. +// If the address record gets deleted, or the DNS gets blasted, the Maker won't know. +// TODO: Make the maker repost their address to DNS once a day in spawned thread. +// pub const DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = 60 * 60 * 24; // Once a day. + +/// Maker triggers the recovery mechanism, if Taker is idle for more than 300 secs. +pub const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(300); + +/// Number of confirmation required funding transaction. +pub const REQUIRED_CONFIRMS: u64 = 1; + +/// The minimum locktime difference between the incoming and outgoing swaps. +/// This is the reaction time in blocks a Maker has to claim his refund transaction, in case of recovery. +/// Bolt2 has an estimate of minimum `cltv_expiry_delta` as 18 blocks. https://github.com/lightning/bolts/blob/aa5207aeaa32d841353dd2df3ce725a4046d528d/02-peer-protocol.md?plain=1#L1798 +/// To be a bit more conservative we use 20 as the default value. +pub const MIN_CONTRACT_REACTION_TIME: u16 = 20; + +/// Fee Parameters +/// +/// abs_fee = Constant fee for all swaps. +/// amount_relative_fee = Percentage fee relative to the swap_amount. +/// time_relative_fee = Percentage fee applied to the refund_locktime, i.e, how long the maker needs to wait for their refund time-lock. +/// +/// Increasing the swap amount and refund timelock, increases the coinswap fee, claimed by the maker. +/// Check [REFUND_LOCKTIME] and [REFUND_LOCKTIME_STEP] of taker::api.rs. +/// +/// So Total fee on swap is calculated as +/// `total_fee = abs_fee + (swap_amount * relative_fee)/100 + (swap_amount * refund_locktime * time_relative_fee)/100`; +/// +/// # Example for default values: +/// For swap_amount = 100,000 sats, refund_locktime = 20 Blocks; +/// abs_fee = 1000 sats; +/// amount_relative_fee = (100,000 * 2.5)/100 = 2500 sats; +/// time_relative_fee = (100,000 * 20 * 0.1)/100 = 2000 sats; +/// total_fee = 5500 sats, i.e. 5.5%; +/// The fee rates are set such a way, that the total % fees reaches 5% asymptotically with increase in swap amount. +pub const ABSOLUTE_FEE: u64 = 1000; +pub const AMOUNT_RELATIVE_FEE: f64 = 2.50; +pub const TIME_RELATIVE_FEE: f64 = 0.10; /// Used to configure the maker for testing purposes. #[derive(Debug, Clone, Copy)] @@ -278,7 +320,7 @@ impl Maker { // check that the new locktime is sufficently short enough compared to the // locktime in the provided funding tx let locktime = read_contract_locktime(&funding_info.contract_redeemscript)?; - if locktime - message.next_locktime < MIN_CONTRACT_REACTION_TIME { + if locktime - message.refund_locktime < MIN_CONTRACT_REACTION_TIME { return Err(MakerError::General( "Next hop locktime too close to current hop locktime", )); @@ -558,7 +600,7 @@ pub fn check_for_idle_states(maker: Arc) -> Result<(), MakerError> { ip, no_response_since ); - if no_response_since > std::time::Duration::from_secs(60) { + if no_response_since > IDLE_CONNECTION_TIMEOUT { log::error!( "[{}] Potential Dropped Connection from {}", maker.config.port, diff --git a/src/maker/config.rs b/src/maker/config.rs index 7e8ed124..df3fb6e7 100644 --- a/src/maker/config.rs +++ b/src/maker/config.rs @@ -3,7 +3,6 @@ use crate::utill::parse_toml; use std::{io, path::Path}; -use bitcoin::Amount; use std::io::Write; use crate::utill::{get_maker_dir, parse_field, ConnectionType}; @@ -15,13 +14,8 @@ pub struct MakerConfig { pub port: u16, /// RPC listening port pub rpc_port: u16, - /// Absolute coinswap fee - pub absolute_fee_sats: Amount, - /// Fee rate for timelocked contract in parts per billion (PPB). - /// Similar to `DEFAULT_AMOUNT_RELATIVE_FEE_PPB`, calculated as (amount * fee_ppb) / 1_000_000_000. - pub time_relative_fee_ppb: Amount, - /// Minimum timelock difference between contract transaction of two hops - pub min_size: u64, + /// Minimum swap size. + pub min_swap_amount: u64, /// Socks port pub socks_port: u16, /// Directory server address (can be clearnet or onion) @@ -39,9 +33,7 @@ impl Default for MakerConfig { Self { port: 6102, rpc_port: 6103, - absolute_fee_sats: Amount::from_sat(1000), - time_relative_fee_ppb: Amount::from_sat(100_000), - min_size: 10_000, + min_swap_amount: 100_000, // A swap amount lower than this will not be economical. socks_port: 19050, directory_server_address: "127.0.0.1:8080".to_string(), fidelity_value: 5_000_000, // 5 million sats @@ -97,21 +89,15 @@ impl MakerConfig { Ok(MakerConfig { port: parse_field(config_map.get("port"), default_config.port), rpc_port: parse_field(config_map.get("rpc_port"), default_config.rpc_port), - absolute_fee_sats: parse_field( - config_map.get("absolute_fee_sats"), - default_config.absolute_fee_sats, + min_swap_amount: parse_field( + config_map.get("min_swap_amount"), + default_config.min_swap_amount, ), - time_relative_fee_ppb: parse_field( - config_map.get("time_relative_fee_ppb"), - default_config.time_relative_fee_ppb, - ), - min_size: parse_field(config_map.get("min_size"), default_config.min_size), socks_port: parse_field(config_map.get("socks_port"), default_config.socks_port), directory_server_address: parse_field( config_map.get("directory_server_address"), default_config.directory_server_address, ), - fidelity_value: parse_field( config_map.get("fidelity_value"), default_config.fidelity_value, @@ -131,20 +117,16 @@ impl MakerConfig { pub fn write_to_file(&self, path: &Path) -> std::io::Result<()> { let toml_data = format!( "port = {} -rpc_port = {} -absolute_fee_sats = {} -time_relative_fee_ppb = {} -min_size = {} -socks_port = {} -directory_server_address = {} -fidelity_value = {} -fidelity_timelock = {} -connection_type = {:?}", + rpc_port = {} + min_swap_amount = {} + socks_port = {} + directory_server_address = {} + fidelity_value = {} + fidelity_timelock = {} + connection_type = {:?}", self.port, self.rpc_port, - self.absolute_fee_sats, - self.time_relative_fee_ppb, - self.min_size, + self.min_swap_amount, self.socks_port, self.directory_server_address, self.fidelity_value, @@ -187,12 +169,9 @@ mod tests { [maker_config] port = 6102 rpc_port = 6103 - absolute_fee_sats = 1000 - amount_relative_fee_ppb = 10000000 - time_relative_fee_ppb = 100000 required_confirms = 1 min_contract_reaction_time = 48 - min_size = 10000 + min_swap_amount = 10000 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 dc6b32ff..f26e3f88 100644 --- a/src/maker/handlers.rs +++ b/src/maker/handlers.rs @@ -17,8 +17,9 @@ use bitcoin::{ use bitcoind::bitcoincore_rpc::RpcApi; use crate::{ - maker::api::recover_from_swap, + maker::api::{recover_from_swap, ABSOLUTE_FEE, AMOUNT_RELATIVE_FEE, TIME_RELATIVE_FEE}, protocol::{ + contract::calculate_coinswap_fee, error::ProtocolError, messages::{MakerHello, MultisigPrivkey, PrivKeyHandover}, Hash160, @@ -33,8 +34,7 @@ use crate::{ }, protocol::{ contract::{ - calculate_coinswap_fee, create_receivers_contract_tx, find_funding_output_index, - read_contract_locktime, read_hashvalue_from_contract, + create_receivers_contract_tx, find_funding_output_index, read_hashvalue_from_contract, read_pubkeys_from_multisig_redeemscript, FUNDING_TX_VBYTE_SIZE, }, messages::{ @@ -47,9 +47,8 @@ use crate::{ wallet::{IncomingSwapCoin, SwapCoin}, }; -use crate::maker::server::{ - AMOUNT_RELATIVE_FEE_PPB, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS, -}; +use super::api::{MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS}; + /// The Global Handle Message function. Takes in a [`Arc`] and handle messages /// according to a [ConnectionState]. pub fn handle_message( @@ -96,13 +95,13 @@ pub fn handle_message( let fidelity = maker.highest_fidelity_proof.read()?; let fidelity = fidelity.as_ref().expect("proof expected"); Some(MakerToTakerMessage::RespOffer(Box::new(Offer { - absolute_fee_sat: maker.config.absolute_fee_sats, - amount_relative_fee_ppb: AMOUNT_RELATIVE_FEE_PPB, - time_relative_fee_ppb: maker.config.time_relative_fee_ppb, + absolute_fee: ABSOLUTE_FEE, + amount_relative_fee: AMOUNT_RELATIVE_FEE, + time_relative_fee: TIME_RELATIVE_FEE, required_confirms: REQUIRED_CONFIRMS, minimum_locktime: MIN_CONTRACT_REACTION_TIME, max_size, - min_size: maker.config.min_size, + min_size: maker.config.min_swap_amount, tweakable_point, fidelity: fidelity.clone(), }))) @@ -241,7 +240,7 @@ impl Maker { acc + txinfo.funding_input_value.to_sat() }); - if total_funding_amount >= self.config.min_size + if total_funding_amount >= self.config.min_swap_amount && total_funding_amount < self.wallet.read()?.store.offer_maxsize { log::info!( @@ -300,7 +299,7 @@ impl Maker { }, funding_output.value, &funding_info.contract_redeemscript, - Amount::from_sat(message.next_fee_rate), + Amount::from_sat(message.contract_feerate), )?; let (tweakable_privkey, _) = self.wallet.read()?.get_tweakable_keypair()?; @@ -359,19 +358,30 @@ impl Maker { })?; let calc_coinswap_fees = calculate_coinswap_fee( - self.config.absolute_fee_sats, - AMOUNT_RELATIVE_FEE_PPB, - self.config.time_relative_fee_ppb, - Amount::from_sat(incoming_amount), - REQUIRED_CONFIRMS, //time_in_blocks just 1 for now + incoming_amount, + message.refund_locktime, + ABSOLUTE_FEE, + AMOUNT_RELATIVE_FEE, + TIME_RELATIVE_FEE, ); let calc_funding_tx_fees = (FUNDING_TX_VBYTE_SIZE - * message.next_fee_rate + * message.contract_feerate * (message.next_coinswap_info.len() as u64)) / 1000; - let outgoing_amount = incoming_amount - calc_coinswap_fees - calc_funding_tx_fees; + // 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. + // TODO: Ensure at Taker protocol that this never happens. + let outgoing_amount = if let Some(a) = + incoming_amount.checked_sub(calc_coinswap_fees + calc_funding_tx_fees) + { + a + } else { + return Err(MakerError::General( + "Fatal Error! Total swap fee is more than the swap amount. Failing the swap.", + )); + }; // Create outgoing coinswap of the next hop let (my_funding_txes, outgoing_swapcoins, act_funding_txs_fees) = { @@ -388,12 +398,14 @@ impl Maker { .map(|next_hop| next_hop.next_hashlock_pubkey) .collect::>(), hashvalue, - message.next_locktime, - Amount::from_sat(message.next_fee_rate), + message.refund_locktime, + Amount::from_sat(message.contract_feerate), )? }; - let act_coinswap_fees = incoming_amount - outgoing_amount - act_funding_txs_fees.to_sat(); + 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."); log::info!( "[{}] Outgoing Funding Txids: {:?}.", @@ -403,27 +415,18 @@ impl Maker { .map(|tx| tx.compute_txid()) .collect::>() ); - log::debug!( - "[{}] Outgoing Swapcoins: {:?}.", - self.config.port, - outgoing_swapcoins - ); log::info!( - "incoming_amount = {} | incoming_locktime = {} | outgoing_amount = {} | outgoing_locktime = {}", + "Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Swap Revenue = {}", Amount::from_sat(incoming_amount), - read_contract_locktime( - &message.confirmed_funding_txes[0].contract_redeemscript - )?, Amount::from_sat(outgoing_amount), - message.next_locktime + Amount::from_sat(act_coinswap_fees) ); + log::info!( - "Calculated Funding Txs Fees = {} | Actual Funding Txs Fees = {} | Calculated Swap Revenue = {} | Actual Swap Revenue = {}", - Amount::from_sat(calc_funding_tx_fees), - act_funding_txs_fees, - Amount::from_sat(calc_coinswap_fees), - Amount::from_sat(act_coinswap_fees) + "Our Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | ", + message.refund_locktime, + act_funding_txs_fees ); connection_state.pending_funding_txes = my_funding_txes; diff --git a/src/maker/server.rs b/src/maker/server.rs index ee2054ee..99d2cf29 100644 --- a/src/maker/server.rs +++ b/src/maker/server.rs @@ -30,6 +30,7 @@ use bitcoind::bitcoincore_rpc::RpcApi; #[cfg(feature = "tor")] use socks::Socks5Stream; +use super::api::{HEART_BEAT_INTERVAL_SECS, RPC_PING_INTERVAL_SECS}; pub use super::Maker; use crate::{ @@ -49,18 +50,6 @@ use crate::utill::monitor_log_for_completion; use crate::maker::error::MakerError; -// Default values for Maker configurations -pub const HEART_BEAT_INTERVAL_SECS: u64 = 3; -pub const RPC_PING_INTERVAL_SECS: u64 = 60; -pub const _DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = 60 * 60 * 12; // 12 Hours -pub const _IDLE_CONNECTION_TIMEOUT: u64 = 300; -pub const REQUIRED_CONFIRMS: u64 = 1; -pub const MIN_CONTRACT_REACTION_TIME: u16 = 48; - -/// Fee rate per swap amount in parts per billion (PPB). -/// E.g., for 1 billion sats (0.01 BTC), a value of 10_000 would result in a 0.1% fee. -pub const AMOUNT_RELATIVE_FEE_PPB: Amount = Amount::from_sat(10_000_000); - #[cfg(feature = "tor")] type OptionalJoinHandle = Option>; @@ -438,8 +427,6 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { maker_address ); - let heart_beat_interval = HEART_BEAT_INTERVAL_SECS; // All maker internal threads loops at this frequency. - // Global server Mutex, to switch on/off p2p network. let accepting_clients = Arc::new(AtomicBool::new(false)); @@ -512,8 +499,8 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { maker.thread_pool.add_thread(rpc_thread); - sleep(Duration::from_secs(heart_beat_interval)); // wait for 1 beat, to complete spawns of all the threads. - maker.is_setup_complete.store(true, Relaxed); + sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); // wait for 1 beat, to complete spawns of all the threads. + maker.setup_complete()?; log::info!("[{}] Maker setup is ready", maker.config.port); } diff --git a/src/protocol/contract.rs b/src/protocol/contract.rs index e94e244b..654a85d3 100644 --- a/src/protocol/contract.rs +++ b/src/protocol/contract.rs @@ -54,16 +54,19 @@ const PUBKEY1_OFFSET: usize = 2; const PUBKEY2_OFFSET: usize = PUBKEY1_OFFSET + PUBKEY_LENGTH + 1; /// Calculate the coin swap fee based on various parameters. +/// swap_amount in sats, refund_locktime in blocks. pub fn calculate_coinswap_fee( - absolute_fee_sat: Amount, - amount_relative_fee_ppb: Amount, - time_relative_fee_ppb: Amount, - total_funding_amount: Amount, - time_in_blocks: u64, + swap_amount: u64, + refund_locktime: u16, + abs_fee: u64, + amt_rel_fee: f64, + time_rel_fee: f64, ) -> u64 { - absolute_fee_sat.to_sat() - + (total_funding_amount.to_sat() * amount_relative_fee_ppb.to_sat()) / 1_000_000_000 - + (time_in_blocks * time_relative_fee_ppb.to_sat()) / 1_000_000_000 + let total_fee = abs_fee as f64 + + (swap_amount as f64 * amt_rel_fee) / 1_00.00 + + (swap_amount as f64 * refund_locktime as f64 * time_rel_fee) / 1_00.00; + + total_fee.ceil() as u64 } /// Apply two signatures to a 2-of-2 multisig spend. @@ -1092,54 +1095,46 @@ mod test { #[test] fn calculate_coinswap_fee_normal() { // Test with typical values - let absolute_fee_sat = Amount::from_sat(1000); - let amount_relative_fee_ppb = Amount::from_sat(500_000_000); - let time_relative_fee_ppb = Amount::from_sat(200_000_000); - let total_funding_amount = Amount::from_sat(1_000_000_000); - let time_in_blocks = 100; + let absolute_fee_sat = 1000; + let amount_rel_fee = 2.5; + let time_rel_fee = 0.1; + let swap_amount = 100_000; + let refund_locktime = 20; - let expected_fee = 1000 - + (1_000_000_000 * 500_000_000) / 1_000_000_000 - + (100 * 200_000_000) / 1_000_000_000; + let expected_fee = 5500; let calculated_fee = calculate_coinswap_fee( + swap_amount, + refund_locktime, absolute_fee_sat, - amount_relative_fee_ppb, - time_relative_fee_ppb, - total_funding_amount, - time_in_blocks, + amount_rel_fee, + time_rel_fee, ); assert_eq!(calculated_fee, expected_fee); // Test with zero values assert_eq!( - calculate_coinswap_fee(Amount::ZERO, Amount::ZERO, Amount::ZERO, Amount::ZERO, 0), + calculate_coinswap_fee(swap_amount, refund_locktime, 0, 0.0, 0.0), 0 ); // Test with only the absolute fee being non-zero assert_eq!( - calculate_coinswap_fee( - Amount::from_sat(1000), - Amount::ZERO, - Amount::ZERO, - Amount::ZERO, - 0 - ), + calculate_coinswap_fee(swap_amount, refund_locktime, absolute_fee_sat, 0.0, 0.0), 1000 ); // Test with only the relative fees being non-zero assert_eq!( calculate_coinswap_fee( - Amount::ZERO, - Amount::from_sat(1_000_000_000), - Amount::from_sat(1_000_000_000), - Amount::from_sat(1000), - 10 + swap_amount, + refund_locktime, + 0, + amount_rel_fee, + time_rel_fee ), - 1010 + 4500 ); } @@ -1292,8 +1287,8 @@ mod test { next_hashlock_pubkey: pub_1, next_multisig_pubkey: pub_2, }], - next_locktime: u16::default(), - next_fee_rate: u64::default(), + refund_locktime: u16::default(), + contract_feerate: u64::default(), }; // case with same hash value @@ -1321,8 +1316,8 @@ mod test { next_hashlock_pubkey: pub_1, next_multisig_pubkey: pub_2, }], - next_locktime: u16::default(), - next_fee_rate: u64::default(), + refund_locktime: u16::default(), + contract_feerate: u64::default(), }; let hash_value_from_fn = check_hashvalues_are_equal(&funding_proof).unwrap_err(); diff --git a/src/taker/routines.rs b/src/taker/routines.rs index 25ee1609..66cbb251 100644 --- a/src/taker/routines.rs +++ b/src/taker/routines.rs @@ -245,22 +245,22 @@ pub struct ThisMakerInfo { pub this_maker: OfferAndAddress, pub funding_tx_infos: Vec, pub this_maker_contract_txs: Vec, + pub this_maker_refund_locktime: u16, + pub this_maker_fee_rate: Amount, // Applies to both funding and contract transaction } // Type for information related to the next peer #[derive(Clone)] -pub struct NextPeerInfoArgs { +pub struct NextMakerInfo { pub next_peer_multisig_pubkeys: Vec, pub next_peer_hashlock_pubkeys: Vec, - pub next_maker_refund_locktime: u16, - pub next_maker_fee_rate: Amount, } /// [Internal] Send a Proof funding to the maker and init next hop. pub(crate) fn send_proof_of_funding_and_init_next_hop( socket: &mut TcpStream, tmi: ThisMakerInfo, - npi: NextPeerInfoArgs, + npi: NextMakerInfo, hashvalue: Hash160, ) -> Result<(ContractSigsAsRecvrAndSender, Vec), TakerError> { // Send POF @@ -279,8 +279,8 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( let pof_msg = TakerToMakerMessage::RespProofOfFunding(ProofOfFunding { confirmed_funding_txes: tmi.funding_tx_infos.clone(), next_coinswap_info, - next_locktime: npi.next_maker_refund_locktime, - next_fee_rate: npi.next_maker_fee_rate.to_sat(), + refund_locktime: tmi.this_maker_refund_locktime, + contract_feerate: tmi.this_maker_fee_rate.to_sat(), }); send_message(socket, &pof_msg)?; @@ -337,15 +337,17 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( .iter() .map(|i| i.funding_amount) .sum::(); + let coinswap_fees = calculate_coinswap_fee( - tmi.this_maker.offer.absolute_fee_sat, - tmi.this_maker.offer.amount_relative_fee_ppb, - tmi.this_maker.offer.time_relative_fee_ppb, - Amount::from_sat(this_amount), - 1, //time_in_blocks just 1 for now + this_amount, + tmi.this_maker_refund_locktime, + tmi.this_maker.offer.absolute_fee, + tmi.this_maker.offer.amount_relative_fee, + tmi.this_maker.offer.time_relative_fee, ); + let miner_fees_paid_by_taker = (FUNDING_TX_VBYTE_SIZE - * npi.next_maker_fee_rate.to_sat() + * tmi.this_maker_fee_rate.to_sat() * (npi.next_peer_multisig_pubkeys.len() as u64)) / 1000; let calculated_next_amount = this_amount - coinswap_fees - miner_fees_paid_by_taker; @@ -357,7 +359,7 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( .into()); } log::info!( - "this_amount={} coinswap_fees={} miner_fees_paid_by_taker={} next_amount={}", + "Next maker's amount = {} | Next maker's fees = {} | Miner fees covered by us = {} | Maker is forwarding = {}", this_amount, coinswap_fees, miner_fees_paid_by_taker, @@ -394,7 +396,7 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( hashlock_pubkey, &senders_contract_tx_info.timelock_pubkey, &hashvalue, - &npi.next_maker_refund_locktime, + &tmi.this_maker_refund_locktime, ) }) .collect::>(); From 5513e6c5bc2b684f26365fb66a6dc4ef2133e246 Mon Sep 17 00:00:00 2001 From: mojoX911 Date: Mon, 25 Nov 2024 15:34:15 +0530 Subject: [PATCH 2/5] refactor taker's internal structs --- src/taker/api.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/taker/api.rs b/src/taker/api.rs index 8f1d4141..b044c032 100644 --- a/src/taker/api.rs +++ b/src/taker/api.rs @@ -957,18 +957,19 @@ impl Taker { log::info!("Fundix Txids: {:?}", funding_txids); // Struct for information related to the next peer - let next_maker_info = NextPeerInfoArgs { + let next_maker_info = NextMakerInfo { next_peer_multisig_pubkeys: next_peer_multisig_pubkeys.clone(), next_peer_hashlock_pubkeys: next_peer_hashlock_pubkeys.clone(), - next_maker_refund_locktime: maker_refund_locktime, - next_maker_fee_rate: Amount::from_sat(MINER_FEE), }; let this_maker_info = ThisMakerInfo { this_maker: this_maker.clone(), funding_tx_infos: funding_tx_infos.to_vec(), this_maker_contract_txs, + this_maker_refund_locktime: maker_refund_locktime, + this_maker_fee_rate: self.ongoing_swap_state.swap_params.fee_rate, }; + let (contract_sigs_as_recvr_sender, next_swap_contract_redeemscripts) = send_proof_of_funding_and_init_next_hop( &mut socket, From 876680bfd051b8a19eefbe24b5f3ba6864b19bb2 Mon Sep 17 00:00:00 2001 From: mojoX911 Date: Mon, 25 Nov 2024 15:34:40 +0530 Subject: [PATCH 3/5] other auxiliary fixes --- maker.toml | 3 +- src/bin/directoryd.rs | 11 +++--- src/bin/makerd.rs | 11 +++--- src/bin/taker.rs | 11 +++--- src/maker/api.rs | 76 +++++++++++++++++++++---------------- src/maker/config.rs | 18 +++++---- src/maker/handlers.rs | 73 +++++++++++++++++++---------------- src/maker/rpc/server.rs | 12 +++--- src/maker/server.rs | 61 +++++++++++++---------------- src/market/directory.rs | 5 ++- src/market/rpc/server.rs | 8 ++-- src/protocol/contract.rs | 32 ++++++++-------- src/protocol/messages.rs | 17 +++++---- src/taker/api.rs | 53 ++++++++++++-------------- src/taker/config.rs | 4 +- src/taker/offers.rs | 11 +++--- src/taker/routines.rs | 30 +++++++-------- src/utill.rs | 4 +- src/wallet/rpc.rs | 6 +-- tests/abort1.rs | 29 +++++++++++++- tests/abort2_case1.rs | 25 ++++++++++++ tests/maker_cli.rs | 2 - tests/standard_swap.rs | 20 +++++++--- tests/test_framework/mod.rs | 2 - 24 files changed, 296 insertions(+), 228 deletions(-) diff --git a/maker.toml b/maker.toml index ef34f8ba..12325f5c 100644 --- a/maker.toml +++ b/maker.toml @@ -1,3 +1,4 @@ +Think about this? [maker_config] # Listening port port = 6102 @@ -11,7 +12,7 @@ directory_servers_refresh_interval_secs = 43200 # Time interval to close a connection if no response is received idle_connection_timeout = 300 # Absolute coinswap fee -absolute_fee_sats = 1000 +base_fee_sats = 1000 # Fee rate per swap amount in ppb. amount_relative_fee_ppb = 10000000 # Fee rate for timelocked contract in ppb diff --git a/src/bin/directoryd.rs b/src/bin/directoryd.rs index 26a5f47b..6d3fce88 100644 --- a/src/bin/directoryd.rs +++ b/src/bin/directoryd.rs @@ -14,9 +14,6 @@ use std::{path::PathBuf, str::FromStr, sync::Arc}; #[clap(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"), author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))] struct Cli { - /// Optional network type. - #[clap(long, short = 'n', default_value = "tor", possible_values = &["tor", "clearnet"])] - network: String, /// Optional DNS data directory. Default value : "~/.coinswap/dns" #[clap(long, short = 'd')] data_directory: Option, @@ -53,8 +50,6 @@ fn main() -> Result<(), DirectoryServerError> { let rpc_network = bitcoin::Network::from_str(&args.rpc_network).unwrap(); - let conn_type = ConnectionType::from_str(&args.network)?; - let rpc_config = RPCConfig { url: args.rpc, auth: Auth::UserPass(args.auth.0, args.auth.1), @@ -62,6 +57,12 @@ fn main() -> Result<(), DirectoryServerError> { wallet_name: "random".to_string(), // we can put anything here as it will get updated in the init. }; + let conn_type = if cfg!(feature = "integration-test") { + ConnectionType::CLEARNET + } else { + ConnectionType::TOR + }; + #[cfg(feature = "tor")] { if conn_type == ConnectionType::TOR { diff --git a/src/bin/makerd.rs b/src/bin/makerd.rs index 0d067e08..905d97ec 100644 --- a/src/bin/makerd.rs +++ b/src/bin/makerd.rs @@ -17,9 +17,6 @@ use coinswap::tor::setup_mitosis; #[clap(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"), author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))] struct Cli { - /// Optional Connection Network Type - #[clap(long, default_value = "tor", possible_values = &["tor", "clearnet"])] - network: String, /// Optional DNS data directory. Default value : "~/.coinswap/maker" #[clap(long, short = 'd')] data_directory: Option, @@ -60,8 +57,6 @@ fn main() -> Result<(), MakerError> { let rpc_network = bitcoin::Network::from_str(&args.rpc_network).unwrap(); - let conn_type = ConnectionType::from_str(&args.network)?; - let rpc_config = RPCConfig { url: args.rpc, auth: Auth::UserPass(args.auth.0, args.auth.1), @@ -69,6 +64,12 @@ fn main() -> Result<(), MakerError> { wallet_name: "random".to_string(), // we can put anything here as it will get updated in the init. }; + let conn_type = if cfg!(feature = "integration-test") { + ConnectionType::CLEARNET + } else { + ConnectionType::TOR + }; + #[cfg(feature = "tor")] { if conn_type == ConnectionType::TOR { diff --git a/src/bin/taker.rs b/src/bin/taker.rs index 89f08ed1..1c59c560 100644 --- a/src/bin/taker.rs +++ b/src/bin/taker.rs @@ -14,9 +14,6 @@ use std::{path::PathBuf, str::FromStr}; #[clap(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"), author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))] struct Cli { - /// Optional Connection Network Type - #[clap(long, default_value = "clearnet",short= 'c', possible_values = &["tor","clearnet"])] - connection_type: String, /// Optional DNS data directory. Default value : "~/.coinswap/taker" #[clap(long, short = 'd')] data_directory: Option, @@ -56,7 +53,7 @@ struct Cli { pub tx_count: u32, /// Sets the required on-chain confirmations. #[clap(name = "required_confirms", default_value = "1000")] - pub required_confirms: u64, + pub required_confirms: u32, /// List of sub commands to process various endpoints of taker cli app. #[clap(subcommand)] command: Commands, @@ -101,7 +98,11 @@ fn main() -> Result<(), TakerError> { let args = Cli::parse(); let rpc_network = bitcoin::Network::from_str(&args.bitcoin_network).unwrap(); - let connection_type = ConnectionType::from_str(&args.connection_type)?; + let connection_type = if cfg!(feature = "integration-test") { + ConnectionType::CLEARNET + } else { + ConnectionType::TOR + }; let rpc_config = RPCConfig { url: args.rpc, diff --git a/src/maker/api.rs b/src/maker/api.rs index b135c800..f487214a 100644 --- a/src/maker/api.rs +++ b/src/maker/api.rs @@ -12,7 +12,7 @@ use crate::{ messages::{FidelityProof, ReqContractSigsForSender}, Hash160, }, - utill::{get_maker_dir, redeemscript_to_scriptpubkey, ConnectionType}, + utill::{get_maker_dir, redeemscript_to_scriptpubkey, ConnectionType, HEART_BEAT_INTERVAL}, wallet::{RPCConfig, SwapCoin, WalletSwapCoin}, }; use bitcoin::{ @@ -46,51 +46,63 @@ use crate::{ use super::{config::MakerConfig, error::MakerError}; -/// The core server process interval. many of the maker server's internal threads "beats" at this frequency. -pub const HEART_BEAT_INTERVAL_SECS: u64 = 3; - -/// RPC Backend health check interval. -pub const RPC_PING_INTERVAL_SECS: u64 = 60; +/// Interval for health checks on a stable RPC connection with bitcoind. +pub const RPC_PING_INTERVAL: Duration = Duration::from_secs(60); // Currently we don't refresh address at DNS. The Maker only post it once at startup. // If the address record gets deleted, or the DNS gets blasted, the Maker won't know. // TODO: Make the maker repost their address to DNS once a day in spawned thread. -// pub const DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = 60 * 60 * 24; // Once a day. +// pub const DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = Duartion::from_days(1); // Once a day. /// Maker triggers the recovery mechanism, if Taker is idle for more than 300 secs. pub const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(300); /// Number of confirmation required funding transaction. -pub const REQUIRED_CONFIRMS: u64 = 1; +pub const REQUIRED_CONFIRMS: u32 = 1; -/// The minimum locktime difference between the incoming and outgoing swaps. -/// This is the reaction time in blocks a Maker has to claim his refund transaction, in case of recovery. -/// Bolt2 has an estimate of minimum `cltv_expiry_delta` as 18 blocks. https://github.com/lightning/bolts/blob/aa5207aeaa32d841353dd2df3ce725a4046d528d/02-peer-protocol.md?plain=1#L1798 -/// To be a bit more conservative we use 20 as the default value. +/// The minimum difference in locktime (in blocks) between the incoming and outgoing swaps. +/// +/// This value specifies the reaction time, in blocks, available to a Maker +/// to claim the refund transaction in case of recovery. +/// +/// According to [BOLT #2](https://github.com/lightning/bolts/blob/aa5207aeaa32d841353dd2df3ce725a4046d528d/02-peer-protocol.md?plain=1#L1798), +/// the estimated minimum `cltv_expiry_delta` is 18 blocks. +/// To enhance safety, the default value is set to 20 blocks. pub const MIN_CONTRACT_REACTION_TIME: u16 = 20; -/// Fee Parameters +/// # Fee Parameters for Coinswap +/// +/// These parameters define the fees charged by Makers in a coinswap transaction. /// -/// abs_fee = Constant fee for all swaps. -/// amount_relative_fee = Percentage fee relative to the swap_amount. -/// time_relative_fee = Percentage fee applied to the refund_locktime, i.e, how long the maker needs to wait for their refund time-lock. +/// TODO: These parameters are currently hardcoded. Consider making them configurable for Makers in the future. +///p +/// - `BASE_FEE`: A fixed base fee charged by the Maker for providing its services +/// - `AMOUNT_RELATIVE_FEE_PCT`: A percentage fee based on the swap amount. +/// - `TIME_RELATIVE_FEE_PCT`: A percentage fee based on the refund locktime (duration the Maker must wait for a refund). /// -/// Increasing the swap amount and refund timelock, increases the coinswap fee, claimed by the maker. -/// Check [REFUND_LOCKTIME] and [REFUND_LOCKTIME_STEP] of taker::api.rs. +/// The coinswap fee increases with both the swap amount and the refund locktime. +/// Refer to `REFUND_LOCKTIME` and `REFUND_LOCKTIME_STEP` in `taker::api.rs` for related parameters. /// -/// So Total fee on swap is calculated as -/// `total_fee = abs_fee + (swap_amount * relative_fee)/100 + (swap_amount * refund_locktime * time_relative_fee)/100`; +/// ### Fee Calculation +/// The total fee for a swap is calculated as: +/// `total_fee = base_fee + (swap_amount * amount_relative_fee_pct) / 100 + (swap_amount * refund_locktime * time_relative_fee_pct) / 100` /// -/// # Example for default values: -/// For swap_amount = 100,000 sats, refund_locktime = 20 Blocks; -/// abs_fee = 1000 sats; -/// amount_relative_fee = (100,000 * 2.5)/100 = 2500 sats; -/// time_relative_fee = (100,000 * 20 * 0.1)/100 = 2000 sats; -/// total_fee = 5500 sats, i.e. 5.5%; -/// The fee rates are set such a way, that the total % fees reaches 5% asymptotically with increase in swap amount. -pub const ABSOLUTE_FEE: u64 = 1000; -pub const AMOUNT_RELATIVE_FEE: f64 = 2.50; -pub const TIME_RELATIVE_FEE: f64 = 0.10; +/// ### Example (Default Values) +/// For a swap amount of 100,000 sats and a refund locktime of 20 blocks: +/// - `base_fee` = 1,000 sats +/// - `amount_relative_fee` = (100,000 * 2.5) / 100 = 2,500 sats +/// - `time_relative_fee` = (100,000 * 20 * 0.1) / 100 = 2,000 sats +/// - `total_fee` = 5,500 sats (5.5%) +/// +/// Fee rates are designed to asymptotically approach 5% of the swap amount as the swap amount increases.. +pub const BASE_FEE: u64 = 1000; +pub const AMOUNT_RELATIVE_FEE_PCT: f64 = 2.50; +pub const TIME_RELATIVE_FEE_PCT: f64 = 0.10; + +/// Minimum Coinswap amount; makers will not accept amounts below this. +pub const MIN_SWAP_AMOUNT: u64 = 100000; + +// What's the use of RefundLocktimeStep? /// Used to configure the maker for testing purposes. #[derive(Debug, Clone, Copy)] @@ -567,7 +579,7 @@ pub fn check_for_broadcasted_contracts(maker: Arc) -> Result<(), MakerErr } } // All locks are cleared here. - std::thread::sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); + std::thread::sleep(HEART_BEAT_INTERVAL); } Ok(()) @@ -652,7 +664,7 @@ pub fn check_for_idle_states(maker: Arc) -> Result<(), MakerError> { } } // All locks are cleared here - std::thread::sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); + std::thread::sleep(HEART_BEAT_INTERVAL); } Ok(()) diff --git a/src/maker/config.rs b/src/maker/config.rs index df3fb6e7..6a48ad12 100644 --- a/src/maker/config.rs +++ b/src/maker/config.rs @@ -7,6 +7,8 @@ use std::io::Write; use crate::utill::{get_maker_dir, parse_field, ConnectionType}; +use super::api::MIN_SWAP_AMOUNT; + /// Maker Configuration, controlling various maker behavior. #[derive(Debug, Clone, PartialEq)] pub struct MakerConfig { @@ -33,7 +35,7 @@ impl Default for MakerConfig { Self { port: 6102, rpc_port: 6103, - min_swap_amount: 100_000, // A swap amount lower than this will not be economical. + min_swap_amount: MIN_SWAP_AMOUNT, socks_port: 19050, directory_server_address: "127.0.0.1:8080".to_string(), fidelity_value: 5_000_000, // 5 million sats @@ -117,13 +119,13 @@ impl MakerConfig { pub fn write_to_file(&self, path: &Path) -> std::io::Result<()> { let toml_data = format!( "port = {} - rpc_port = {} - min_swap_amount = {} - socks_port = {} - directory_server_address = {} - fidelity_value = {} - fidelity_timelock = {} - connection_type = {:?}", +rpc_port = {} +min_swap_amount = {} +socks_port = {} +directory_server_address = {} +fidelity_value = {} +fidelity_timelock = {} +connection_type = {:?}", self.port, self.rpc_port, self.min_swap_amount, diff --git a/src/maker/handlers.rs b/src/maker/handlers.rs index f26e3f88..0c24f927 100644 --- a/src/maker/handlers.rs +++ b/src/maker/handlers.rs @@ -16,39 +16,34 @@ use bitcoin::{ }; use bitcoind::bitcoincore_rpc::RpcApi; -use crate::{ - maker::api::{recover_from_swap, ABSOLUTE_FEE, AMOUNT_RELATIVE_FEE, TIME_RELATIVE_FEE}, - protocol::{ - contract::calculate_coinswap_fee, - error::ProtocolError, - messages::{MakerHello, MultisigPrivkey, PrivKeyHandover}, - Hash160, +use super::{ + api::{ + recover_from_swap, ConnectionState, ExpectedMessage, Maker, MakerBehavior, + AMOUNT_RELATIVE_FEE_PCT, BASE_FEE, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS, + TIME_RELATIVE_FEE_PCT, }, - wallet::{WalletError, WalletSwapCoin}, + error::MakerError, }; use crate::{ - maker::{ - api::{ConnectionState, ExpectedMessage, Maker, MakerBehavior}, - error::MakerError, - }, protocol::{ contract::{ - create_receivers_contract_tx, find_funding_output_index, read_hashvalue_from_contract, - read_pubkeys_from_multisig_redeemscript, FUNDING_TX_VBYTE_SIZE, + 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::{ ContractSigsAsRecvrAndSender, ContractSigsForRecvr, ContractSigsForRecvrAndSender, - ContractSigsForSender, HashPreimage, MakerToTakerMessage, Offer, ProofOfFunding, - ReqContractSigsForRecvr, ReqContractSigsForSender, SenderContractTxInfo, - TakerToMakerMessage, + ContractSigsForSender, HashPreimage, MakerHello, MakerToTakerMessage, MultisigPrivkey, + Offer, PrivKeyHandover, ProofOfFunding, ReqContractSigsForRecvr, + ReqContractSigsForSender, SenderContractTxInfo, TakerToMakerMessage, }, + Hash160, }, - wallet::{IncomingSwapCoin, SwapCoin}, + wallet::{IncomingSwapCoin, SwapCoin, WalletError, WalletSwapCoin}, }; -use super::api::{MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS}; - /// The Global Handle Message function. Takes in a [`Arc`] and handle messages /// according to a [ConnectionState]. pub fn handle_message( @@ -95,9 +90,9 @@ pub fn handle_message( let fidelity = maker.highest_fidelity_proof.read()?; let fidelity = fidelity.as_ref().expect("proof expected"); Some(MakerToTakerMessage::RespOffer(Box::new(Offer { - absolute_fee: ABSOLUTE_FEE, - amount_relative_fee: AMOUNT_RELATIVE_FEE, - time_relative_fee: TIME_RELATIVE_FEE, + base_fee: BASE_FEE, + amount_relative_fee_pct: AMOUNT_RELATIVE_FEE_PCT, + time_relative_fee_pct: TIME_RELATIVE_FEE_PCT, required_confirms: REQUIRED_CONFIRMS, minimum_locktime: MIN_CONTRACT_REACTION_TIME, max_size, @@ -242,6 +237,8 @@ impl Maker { if total_funding_amount >= self.config.min_swap_amount && total_funding_amount < self.wallet.read()?.store.offer_maxsize + // TODO: Taker must not be allowed to send the amount beyond this range? + // why can't it be <= ? { log::info!( "[{}] Total Funding Amount = {} | Funding Txids = {:?}", @@ -253,6 +250,7 @@ impl Maker { ContractSigsForSender { sigs }, )) } else { + // Instead , we must create a MakerToTakerMessage variant stating about it rather than giving error back to Maker itself. Err(MakerError::General("not enough funds")) } } @@ -360,9 +358,9 @@ impl Maker { let calc_coinswap_fees = calculate_coinswap_fee( incoming_amount, message.refund_locktime, - ABSOLUTE_FEE, - AMOUNT_RELATIVE_FEE, - TIME_RELATIVE_FEE, + BASE_FEE, + AMOUNT_RELATIVE_FEE_PCT, + TIME_RELATIVE_FEE_PCT, ); let calc_funding_tx_fees = (FUNDING_TX_VBYTE_SIZE @@ -403,6 +401,11 @@ 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."); @@ -417,18 +420,24 @@ impl Maker { ); log::info!( - "Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Swap Revenue = {}", + "[{}] Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Swap Revenue = {} + /n + Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | + ", + self.config.port, Amount::from_sat(incoming_amount), Amount::from_sat(outgoing_amount), - Amount::from_sat(act_coinswap_fees) - ); - - log::info!( - "Our Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | ", + Amount::from_sat(act_coinswap_fees), message.refund_locktime, act_funding_txs_fees ); + // log::info!( + // "Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | ", + // message.refund_locktime, + // act_funding_txs_fees + // ); + connection_state.pending_funding_txes = my_funding_txes; connection_state.outgoing_swapcoins = outgoing_swapcoins; diff --git a/src/maker/rpc/server.rs b/src/maker/rpc/server.rs index 4a8e2a32..036485b0 100644 --- a/src/maker/rpc/server.rs +++ b/src/maker/rpc/server.rs @@ -9,15 +9,14 @@ use std::{ use bitcoin::{Address, Amount}; +use super::messages::RpcMsgReq; use crate::{ maker::{error::MakerError, rpc::messages::RpcMsgResp, Maker}, - utill::{read_message, send_message, ConnectionType}, + utill::{read_message, send_message, ConnectionType, HEART_BEAT_INTERVAL}, wallet::{Destination, SendAmount}, }; use std::str::FromStr; -use super::messages::RpcMsgReq; - fn handle_request(maker: &Arc, socket: &mut TcpStream) -> Result<(), MakerError> { let msg_bytes = read_message(socket)?; let rpc_request: RpcMsgReq = serde_cbor::from_slice(&msg_bytes)?; @@ -206,16 +205,15 @@ pub fn start_rpc_server(maker: Arc) -> Result<(), MakerError> { Err(e) => { if e.kind() == ErrorKind::WouldBlock { - sleep(Duration::from_secs(3)); - continue; + // do nothing } else { log::error!("Error accepting RPC connection: {:?}", e); - return Err(e.into()); + return Err(e.into()); // Why are we returning the error instead of continuing? } } } - sleep(Duration::from_secs(3)); + sleep(HEART_BEAT_INTERVAL); } Ok(()) diff --git a/src/maker/server.rs b/src/maker/server.rs index 99d2cf29..47a5979e 100644 --- a/src/maker/server.rs +++ b/src/maker/server.rs @@ -30,8 +30,7 @@ use bitcoind::bitcoincore_rpc::RpcApi; #[cfg(feature = "tor")] use socks::Socks5Stream; -use super::api::{HEART_BEAT_INTERVAL_SECS, RPC_PING_INTERVAL_SECS}; -pub use super::Maker; +pub use super::{api::RPC_PING_INTERVAL, Maker}; use crate::{ error::NetError, @@ -41,7 +40,9 @@ use crate::{ rpc::start_rpc_server, }, protocol::messages::TakerToMakerMessage, - utill::{read_message, send_message, ConnectionType, DnsMetadata, DnsRequest}, + utill::{ + read_message, send_message, ConnectionType, DnsMetadata, DnsRequest, HEART_BEAT_INTERVAL, + }, wallet::WalletError, }; @@ -152,36 +153,29 @@ fn network_bootstrap(maker: Arc) -> Result<(String, OptionalJoinHandle), // Keep trying until send is successful. loop { - let mut stream = match maker.config.connection_type { - ConnectionType::CLEARNET => match TcpStream::connect(&dns_address) { - Ok(s) => s, + let mut stream = loop { + let conn_result = match maker.config.connection_type { + ConnectionType::CLEARNET => TcpStream::connect(&dns_address), + + #[cfg(feature = "tor")] + ConnectionType::TOR => Socks5Stream::connect( + format!("127.0.0.1:{}", maker.config.socks_port), + dns_address.as_str(), + ) + .map(|stream| stream.into_inner()), + }; + + match conn_result { + Ok(stream) => break stream, Err(e) => { log::warn!( "[{}] TCP connection error with directory, reattempting: {}", maker_port, e ); - thread::sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); + thread::sleep(HEART_BEAT_INTERVAL); continue; } - }, - #[cfg(feature = "tor")] - ConnectionType::TOR => { - match Socks5Stream::connect( - format!("127.0.0.1:{}", maker.config.socks_port), - dns_address.as_str(), - ) { - Ok(s) => s.into_inner(), - Err(e) => { - log::warn!( - "[{}] TCP connection error with directory, reattempting: {}", - maker_port, - e - ); - thread::sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); - continue; - } - } } }; @@ -191,9 +185,7 @@ fn network_bootstrap(maker: Arc) -> Result<(String, OptionalJoinHandle), maker_port, e ); - - // Wait before reattempting - std::thread::sleep(std::time::Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); + thread::sleep(HEART_BEAT_INTERVAL); continue; }; @@ -310,10 +302,10 @@ fn check_connection_with_core( // If connection is live, keep tring at rpc_ping_interval (60 sec). match rpc_ping_success { true => { - sleep(Duration::from_secs(RPC_PING_INTERVAL_SECS)); + sleep(RPC_PING_INTERVAL); } false => { - sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); + sleep(HEART_BEAT_INTERVAL); } } if let Err(e) = maker.wallet.read()?.rpc.get_blockchain_info() { @@ -499,8 +491,8 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { maker.thread_pool.add_thread(rpc_thread); - sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); // wait for 1 beat, to complete spawns of all the threads. - maker.setup_complete()?; + sleep(HEART_BEAT_INTERVAL); // wait for 1 beat, to complete spawns of all the threads. + maker.is_setup_complete.store(true, Relaxed); log::info!("[{}] Maker setup is ready", maker.config.port); } @@ -509,7 +501,6 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { // This loop beats at `maker.config.heart_beat_interval_secs` while !maker.shutdown.load(Relaxed) { let maker = maker.clone(); // This clone is needed to avoid moving the Arc in each iterations. - let heart_beat_interval = HEART_BEAT_INTERVAL_SECS; // Block client connections if accepting_client=false if !accepting_clients.load(Relaxed) { @@ -517,7 +508,7 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { "[{}] Temporary failure in backend node. Not accepting swap request. Check your node if this error persists", maker.config.port ); - sleep(Duration::from_secs(heart_beat_interval)); + sleep(HEART_BEAT_INTERVAL); continue; } @@ -549,7 +540,7 @@ pub fn start_maker_server(maker: Arc) -> Result<(), MakerError> { } }; - sleep(Duration::from_secs(heart_beat_interval)); + sleep(HEART_BEAT_INTERVAL); } log::info!("[{}] Maker is shutting down.", port); diff --git a/src/market/directory.rs b/src/market/directory.rs index b4b58525..cb9eb9c5 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, DnsRequest, + ConnectionType, HEART_BEAT_INTERVAL, DnsRequest, }, wallet::{RPCConfig, WalletError}, }; @@ -343,6 +343,7 @@ pub fn start_directory_server( let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, directory.port))?; + // why we have not set it to non-blocking mode? while !directory.shutdown.load(Relaxed) { match listener.accept() { Ok((mut stream, addrs)) => { @@ -358,7 +359,7 @@ pub fn start_directory_server( } } - sleep(Duration::from_secs(3)); + sleep(HEART_BEAT_INTERVAL); } log::info!("Shutdown signal received. Stopping directory server."); diff --git a/src/market/rpc/server.rs b/src/market/rpc/server.rs index 4097cf72..44b3f45a 100644 --- a/src/market/rpc/server.rs +++ b/src/market/rpc/server.rs @@ -12,7 +12,7 @@ use std::{ thread::sleep, time::Duration, }; - +use crate::utill::HEART_BEAT_INTERVAL; fn handle_request( socket: &mut TcpStream, address: Arc>>, @@ -59,15 +59,15 @@ pub fn start_rpc_server_thread( } Err(e) => { if e.kind() == ErrorKind::WouldBlock { - sleep(Duration::from_secs(3)); - continue; + // do nothing } else { log::error!("Error accepting RPC connection: {:?}", e); break; } } } - sleep(Duration::from_secs(3)); + // use heart_beat + sleep(HEART_BEAT_INTERVAL); } Ok(()) diff --git a/src/protocol/contract.rs b/src/protocol/contract.rs index 654a85d3..53cc425e 100644 --- a/src/protocol/contract.rs +++ b/src/protocol/contract.rs @@ -56,15 +56,17 @@ const PUBKEY2_OFFSET: usize = PUBKEY1_OFFSET + PUBKEY_LENGTH + 1; /// Calculate the coin swap fee based on various parameters. /// swap_amount in sats, refund_locktime in blocks. pub fn calculate_coinswap_fee( + // Should we consider value in Amount? swap_amount: u64, refund_locktime: u16, - abs_fee: u64, - amt_rel_fee: f64, - time_rel_fee: f64, + base_fee: u64, + amt_rel_fee_pct: f64, + time_rel_fee_pct: f64, ) -> u64 { - let total_fee = abs_fee as f64 - + (swap_amount as f64 * amt_rel_fee) / 1_00.00 - + (swap_amount as f64 * refund_locktime as f64 * time_rel_fee) / 1_00.00; + // swap_amount as f64 * refund_locktime as f64 -> can overflow inside f64? + let total_fee = base_fee as f64 + + (swap_amount as f64 * amt_rel_fee_pct) / 1_00.00 + + (swap_amount as f64 * refund_locktime as f64 * time_rel_fee_pct) / 1_00.00; total_fee.ceil() as u64 } @@ -1095,9 +1097,9 @@ mod test { #[test] fn calculate_coinswap_fee_normal() { // Test with typical values - let absolute_fee_sat = 1000; - let amount_rel_fee = 2.5; - let time_rel_fee = 0.1; + let base_fee_sat = 1000; + let amt_rel_fee_pct = 2.5; + let time_rel_fee_pct = 0.1; let swap_amount = 100_000; let refund_locktime = 20; @@ -1106,9 +1108,9 @@ mod test { let calculated_fee = calculate_coinswap_fee( swap_amount, refund_locktime, - absolute_fee_sat, - amount_rel_fee, - time_rel_fee, + base_fee_sat, + amt_rel_fee_pct, + time_rel_fee_pct, ); assert_eq!(calculated_fee, expected_fee); @@ -1121,7 +1123,7 @@ mod test { // Test with only the absolute fee being non-zero assert_eq!( - calculate_coinswap_fee(swap_amount, refund_locktime, absolute_fee_sat, 0.0, 0.0), + calculate_coinswap_fee(swap_amount, refund_locktime, base_fee_sat, 0.0, 0.0), 1000 ); @@ -1131,8 +1133,8 @@ mod test { swap_amount, refund_locktime, 0, - amount_rel_fee, - time_rel_fee + amt_rel_fee_pct, + time_rel_fee_pct ), 4500 ); diff --git a/src/protocol/messages.rs b/src/protocol/messages.rs index 400ea6ef..3ad83ee3 100644 --- a/src/protocol/messages.rs +++ b/src/protocol/messages.rs @@ -142,8 +142,8 @@ pub struct ProofOfFunding { pub confirmed_funding_txes: Vec, // TODO: Directly use Vec of Pubkeys. pub next_coinswap_info: Vec, - pub next_locktime: u16, - pub next_fee_rate: u64, + pub refund_locktime: u16, + pub contract_feerate: u64, } /// Signatures required for an intermediate Maker to perform receiving and sending of coinswaps. @@ -236,18 +236,19 @@ pub struct FidelityProof { } /// Represents an offer in the context of the Coinswap protocol. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Hash)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd)] pub struct Offer { - pub absolute_fee_sat: Amount, - pub amount_relative_fee_ppb: Amount, - pub time_relative_fee_ppb: Amount, - pub required_confirms: u64, + pub base_fee: u64, // base fee in sats + pub amount_relative_fee_pct: f64, // % fee on total amount + pub time_relative_fee_pct: f64, // amount * refund_locktime * TRF% = fees for locking the fund. + pub required_confirms: u32, pub minimum_locktime: u16, pub max_size: u64, pub min_size: u64, pub tweakable_point: PublicKey, pub fidelity: FidelityProof, } +// TODO: Should Offer struct use Amount struct instead of u64? /// Contract Tx signatures provided by a Sender of a Coinswap. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -283,7 +284,7 @@ pub struct ContractSigsForRecvr { } /// All messages sent from Maker to Taker. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] // why do we require PartialEq? pub enum MakerToTakerMessage { /// Protocol Handshake. MakerHello(MakerHello), diff --git a/src/taker/api.rs b/src/taker/api.rs index b044c032..14bfe315 100644 --- a/src/taker/api.rs +++ b/src/taker/api.rs @@ -61,8 +61,8 @@ use crate::{ }; // Default values for Taker configurations -pub const REFUND_LOCKTIME: u16 = 48; -pub const REFUND_LOCKTIME_STEP: u16 = 48; +pub const REFUND_LOCKTIME: u16 = 20; +pub const REFUND_LOCKTIME_STEP: u16 = 20; pub const FIRST_CONNECT_ATTEMPTS: u32 = 5; pub const FIRST_CONNECT_SLEEP_DELAY_SEC: u64 = 1; pub const FIRST_CONNECT_ATTEMPT_TIMEOUT_SEC: u64 = 60; @@ -89,7 +89,7 @@ pub struct SwapParams { pub tx_count: u32, // TODO: Following two should be moved to TakerConfig as global configuration. /// Confirmation count required for funding txs. - pub required_confirms: u64, + pub required_confirms: u32, } // Defines the Taker's position in the current ongoing swap. @@ -240,10 +240,6 @@ impl Taker { } pub fn do_coinswap(&mut self, swap_params: SwapParams) -> Result<(), TakerError> { - let tor_log_dir = "/tmp/tor-rust-taker/log".to_string(); - - let taker_port = self.config.port; - #[cfg(feature = "tor")] let mut handle = None; @@ -253,6 +249,7 @@ impl Taker { ConnectionType::TOR => { let taker_socks_port = self.config.socks_port; + let tor_log_dir = "/tmp/tor-rust-taker/log".to_string(); if Path::new(tor_log_dir.as_str()).exists() { match fs::remove_file(Path::new(tor_log_dir.clone().as_str())) { Ok(_) => log::info!("Previous taker log file deleted successfully"), @@ -262,19 +259,22 @@ impl Taker { handle = Some(crate::tor::spawn_tor( taker_socks_port, - taker_port, + self.config.port, "/tmp/tor-rust-taker".to_string(), )); - thread::sleep(Duration::from_secs(10)); + thread::sleep(Duration::from_secs(10)); // Think about this? + // when we are getting error while reading the file -> how can we say that taker tor is started? if let Err(e) = monitor_log_for_completion(&PathBuf::from(tor_log_dir), "100%") { log::error!("Error monitoring taker log file: {}", e); } + // TODO: Think about here? log::info!("Taker tor is instantiated"); } } + self.send_coinswap(swap_params)?; #[cfg(feature = "tor")] @@ -451,7 +451,7 @@ impl Taker { // Loop until we find a live maker who responded to our signature request. let (maker, funding_txs) = loop { // Fail early if not enough good makers in the list to satisfy swap requirements. - let untried_maker_count = self.offerbook.get_all_untried().len(); + let untried_maker_count = self.offerbook.get_all_untried().len(); //TODO: why we want to remove good makers? if untried_maker_count < (self.ongoing_swap_state.swap_params.maker_count) { log::error!("Not enough makers to satisfy swap requirements."); @@ -655,7 +655,7 @@ impl Taker { } } //TODO handle confirm<0 - if gettx.confirmations >= Some(required_confirmations as u32) { + if gettx.confirmations >= Some(required_confirmations) { txid_tx_map.insert( *txid, deserialize::(&gettx.hex).map_err(WalletError::from)?, @@ -859,6 +859,7 @@ impl Taker { maker_refund_locktime: u16, funding_tx_infos: &[FundingTxInfo], ) -> Result<(NextPeerInfo, ContractSigsAsRecvrAndSender), TakerError> { + // TODO: WHy we are using this api two times. let this_maker = &self .ongoing_swap_state .peer_infos @@ -874,7 +875,7 @@ impl Taker { ); let address = this_maker.address.to_string(); let mut socket = match self.config.connection_type { - ConnectionType::CLEARNET => TcpStream::connect(address)?, + ConnectionType::CLEARNET => TcpStream::connect(address)?, // Why we give return here instead of trying again? #[cfg(feature = "tor")] ConnectionType::TOR => Socks5Stream::connect( format!("127.0.0.1:{}", self.config.socks_port).as_str(), @@ -967,7 +968,6 @@ impl Taker { funding_tx_infos: funding_tx_infos.to_vec(), this_maker_contract_txs, this_maker_refund_locktime: maker_refund_locktime, - this_maker_fee_rate: self.ongoing_swap_state.swap_params.fee_rate, }; let (contract_sigs_as_recvr_sender, next_swap_contract_redeemscripts) = @@ -1928,30 +1928,25 @@ impl Taker { /// Synchronizes the offer book with addresses obtained from directory servers and local configurations. pub fn sync_offerbook(&mut self) -> Result<(), TakerError> { - let directory_address = match self.config.connection_type { - ConnectionType::CLEARNET => { - let mut address = self.config.directory_server_address.clone(); - if cfg!(feature = "integration-test") { - address = format!("127.0.0.1:{}", 8080); + let mut directory_address = self.config.directory_server_address.clone(); + if cfg!(feature = "integration-test") { + match self.config.connection_type { + ConnectionType::CLEARNET => { + directory_address = format!("127.0.0.1:{}", 8080); } - address - } - #[cfg(feature = "tor")] - ConnectionType::TOR => { - let mut address = self.config.directory_server_address.clone(); - if cfg!(feature = "integration-test") { + #[cfg(feature = "tor")] + ConnectionType::TOR => { let directory_hs_path_str = "/tmp/tor-rust-directory/hs-dir/hostname".to_string(); - let directory_hs_path = PathBuf::from(directory_hs_path_str); - let mut directory_file = fs::File::open(directory_hs_path)?; + let mut directory_file = fs::File::open(directory_hs_path_str)?; let mut directory_onion_addr = String::new(); directory_file.read_to_string(&mut directory_onion_addr)?; directory_onion_addr.pop(); - address = format!("{}:{}", directory_onion_addr, 8080); + directory_address = format!("{}:{}", directory_onion_addr, 8080); } - address } - }; + } + let mut socks_port: Option = None; #[cfg(feature = "tor")] { diff --git a/src/taker/config.rs b/src/taker/config.rs index 4d25438b..57c521c0 100644 --- a/src/taker/config.rs +++ b/src/taker/config.rs @@ -162,7 +162,7 @@ mod tests { let config = TakerConfig::new(Some(&config_path)).unwrap(); remove_temp_config(&config_path); - assert_eq!(REFUND_LOCKTIME, 48); + assert_eq!(REFUND_LOCKTIME, 20); assert_eq!(config, TakerConfig::default()); } @@ -188,7 +188,7 @@ mod tests { let config_path = create_temp_config(contents, "different_data_taker_config.toml"); let config = TakerConfig::new(Some(&config_path)).unwrap(); remove_temp_config(&config_path); - assert_eq!(REFUND_LOCKTIME, 48); + assert_eq!(REFUND_LOCKTIME, 20); assert_eq!( TakerConfig { socks_port: 19051, // Configurable via TOML. diff --git a/src/taker/offers.rs b/src/taker/offers.rs index d256d382..47df06ba 100644 --- a/src/taker/offers.rs +++ b/src/taker/offers.rs @@ -28,7 +28,7 @@ use crate::{ use super::{config::TakerConfig, error::TakerError, routines::download_maker_offer}; /// Represents an offer along with the corresponding maker address. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct OfferAndAddress { pub offer: Offer, pub address: MakerAddress, @@ -142,9 +142,9 @@ pub fn fetch_offer_from_makers( let maker_addresses_len = maker_addresses.len(); for addr in maker_addresses { let offers_writer = offers_writer.clone(); - let taker_config: TakerConfig = config.clone(); + let taker_config = config.clone(); let thread = Builder::new() - .name(format!("maker_offer_fecth_thread_{}", addr)) + .name(format!("maker_offer_fetch_thread_{}", addr)) .spawn(move || -> Result<(), TakerError> { let offer = download_maker_offer(addr, taker_config); Ok(offers_writer.send(offer)?) @@ -152,7 +152,7 @@ pub fn fetch_offer_from_makers( thread_pool.push(thread); } - let mut result = Vec::::new(); + let mut result = Vec::new(); for _ in 0..maker_addresses_len { if let Some(offer_addr) = offers_reader.recv()? { result.push(offer_addr); @@ -165,8 +165,9 @@ pub fn fetch_offer_from_makers( thread.thread().name().expect("thread names expected") ); let join_result = thread.join(); + if let Err(e) = join_result { - log::error!("Error in internal thread: {:?}", e); + log::error!("Error while joining thread: {:?}", e); } } Ok(result) diff --git a/src/taker/routines.rs b/src/taker/routines.rs index 66cbb251..79769583 100644 --- a/src/taker/routines.rs +++ b/src/taker/routines.rs @@ -2,14 +2,14 @@ //! //! It includes functions for handshaking, requesting contract signatures, sending proofs of funding, and downloading maker offers. //! It also defines structs for contract transactions and contract information. -//! Notable types include [ContractTransaction], [ContractsInfo], [ThisMakerInfo], and [NextPeerInfoArgs]. +//! Notable types include [ContractTransaction], [ContractsInfo], [ThisMakerInfo], and [NextMakerInfo]. //! It also handles downloading maker offers with retry mechanisms and implements the necessary message structures //! for communication between taker and maker. use serde::{Deserialize, Serialize}; #[cfg(feature = "tor")] use socks::Socks5Stream; -use std::{io::ErrorKind, net::TcpStream, thread::sleep, time::Duration}; +use std::{io::ErrorKind, net::TcpStream, thread::sleep, time::Duration, u64::MIN}; use crate::{ protocol::{ @@ -27,6 +27,7 @@ use crate::{ }, Hash160, }, + taker::api::MINER_FEE, utill::{read_message, send_message, ConnectionType}, wallet::WalletError, }; @@ -246,10 +247,9 @@ pub struct ThisMakerInfo { pub funding_tx_infos: Vec, pub this_maker_contract_txs: Vec, pub this_maker_refund_locktime: u16, - pub this_maker_fee_rate: Amount, // Applies to both funding and contract transaction } -// Type for information related to the next peer +// Type for information related to the next peer // why not next Maker? #[derive(Clone)] pub struct NextMakerInfo { pub next_peer_multisig_pubkeys: Vec, @@ -280,7 +280,7 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( confirmed_funding_txes: tmi.funding_tx_infos.clone(), next_coinswap_info, refund_locktime: tmi.this_maker_refund_locktime, - contract_feerate: tmi.this_maker_fee_rate.to_sat(), + contract_feerate: MINER_FEE, }); send_message(socket, &pof_msg)?; @@ -341,15 +341,13 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( let coinswap_fees = calculate_coinswap_fee( this_amount, tmi.this_maker_refund_locktime, - tmi.this_maker.offer.absolute_fee, - tmi.this_maker.offer.amount_relative_fee, - tmi.this_maker.offer.time_relative_fee, + tmi.this_maker.offer.base_fee, + tmi.this_maker.offer.amount_relative_fee_pct, + tmi.this_maker.offer.time_relative_fee_pct, ); - let miner_fees_paid_by_taker = (FUNDING_TX_VBYTE_SIZE - * tmi.this_maker_fee_rate.to_sat() - * (npi.next_peer_multisig_pubkeys.len() as u64)) - / 1000; + let miner_fees_paid_by_taker = + (FUNDING_TX_VBYTE_SIZE * MINER_FEE * (npi.next_peer_multisig_pubkeys.len() as u64)) / 1000; 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 { @@ -359,11 +357,10 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( .into()); } log::info!( - "Next maker's amount = {} | Next maker's fees = {} | Miner fees covered by us = {} | Maker is forwarding = {}", - this_amount, - coinswap_fees, + "This Maker is forwarding = {} to next Maker | Next maker's fees = {} | Miner fees covered by us = {}", + next_amount, + coinswap_fees, // These are not in Amount.. miner_fees_paid_by_taker, - next_amount ); for ((receivers_contract_tx, contract_tx), contract_redeemscript) in @@ -493,6 +490,7 @@ pub fn download_maker_offer(address: MakerAddress, config: TakerConfig) -> Optio match download_maker_offer_attempt_once(&address, &config) { Ok(offer) => return Some(OfferAndAddress { offer, address }), Err(TakerError::IO(e)) => { + // TODO: Think about here for better logic? if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut { if ii <= FIRST_CONNECT_ATTEMPTS { log::warn!( diff --git a/src/utill.rs b/src/utill.rs index f2149808..fa6b8e78 100644 --- a/src/utill.rs +++ b/src/utill.rs @@ -55,7 +55,7 @@ pub const NET_TIMEOUT: Duration = Duration::from_secs(60); /// Used as delays on reattempting some network communications. pub const GLOBAL_PAUSE: Duration = Duration::from_secs(10); -/// Global heartbeat interval for internal server threads. +/// Global heartbeat interval used during waiting periods in critical situations. pub const HEART_BEAT_INTERVAL: Duration = Duration::from_secs(3); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -421,7 +421,7 @@ pub fn monitor_log_for_completion(log_file: &Path, pattern: &str) -> io::Result< last_size = current_size; } - thread::sleep(Duration::from_secs(3)); + thread::sleep(HEART_BEAT_INTERVAL); } } diff --git a/src/wallet/rpc.rs b/src/wallet/rpc.rs index 056f7d2e..b4e63e5e 100644 --- a/src/wallet/rpc.rs +++ b/src/wallet/rpc.rs @@ -1,10 +1,10 @@ //! Manages connection with a Bitcoin Core RPC. //! -use std::{convert::TryFrom, thread, time::Duration}; - +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 crate::wallet::api::KeychainKind; @@ -142,7 +142,7 @@ impl Wallet { Err(e) => { log::warn!("Sync Error, Retrying: {}", e); - thread::sleep(Duration::from_secs(3)); + thread::sleep(HEART_BEAT_INTERVAL); continue; } } diff --git a/tests/abort1.rs b/tests/abort1.rs index 84274b82..f0a38393 100644 --- a/tests/abort1.rs +++ b/tests/abort1.rs @@ -194,7 +194,6 @@ fn test_stop_taker_after_setup() { .unwrap(); }); - // Wait for Taker swap thread to conclude. taker_thread.join().unwrap(); // Wait for Maker threads to conclude. @@ -208,6 +207,8 @@ fn test_stop_taker_after_setup() { 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); @@ -220,6 +221,30 @@ fn test_stop_taker_after_setup() { all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().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 | 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) | + // + // ## Regaining Funds After a Failed Coinswap: + // + // | 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). + // Check everybody looses mining fees of contract txs. let taker_balance_fidelity = taker .read() @@ -246,7 +271,7 @@ fn test_stop_taker_after_setup() { .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!( diff --git a/tests/abort2_case1.rs b/tests/abort2_case1.rs index bb815857..b6cd4d95 100644 --- a/tests/abort2_case1.rs +++ b/tests/abort2_case1.rs @@ -145,8 +145,33 @@ fn test_abort_case_2_move_on_with_other_makers() { 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() { assert_eq!( diff --git a/tests/maker_cli.rs b/tests/maker_cli.rs index f07f4a53..7c53398c 100644 --- a/tests/maker_cli.rs +++ b/tests/maker_cli.rs @@ -51,8 +51,6 @@ impl MakerCli { .args([ "--data-directory", self.data_dir.to_str().unwrap(), - "--network", - "clearnet", "-a", &rpc_auth, "-r", diff --git a/tests/standard_swap.rs b/tests/standard_swap.rs index d7ee3165..e4d6c676 100644 --- a/tests/standard_swap.rs +++ b/tests/standard_swap.rs @@ -27,19 +27,27 @@ fn test_standard_coinswap() { ((16102, Some(19052)), MakerBehavior::Normal), ]; - let connection_type = if cfg!(target_os = "macos") { - ConnectionType::CLEARNET - } else { - ConnectionType::TOR - }; + // let connection_type = if cfg!(target_os = "macos") { + // ConnectionType::CLEARNET + // } else { + // ConnectionType::TOR + // }; // Initiate test framework, Makers and a Taker with default behavior. +<<<<<<< HEAD let (test_framework, 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"); @@ -260,7 +268,7 @@ fn test_standard_coinswap() { // Check balances makes sense all_utxos = taker.read().unwrap().get_wallet().get_all_utxo().unwrap(); - assert_eq!(all_utxos.len(), 12); + assert_eq!(all_utxos.len(), 12); // how 12? let taker_spendable_bal = taker .read() diff --git a/tests/test_framework/mod.rs b/tests/test_framework/mod.rs index 31bfdeb3..01e6f5b4 100644 --- a/tests/test_framework/mod.rs +++ b/tests/test_framework/mod.rs @@ -126,8 +126,6 @@ pub fn start_dns( let mut args = vec![ "--data-directory", data_dir.to_str().unwrap(), - "--network", - &conn_type, "--rpc_network", "regtest", ]; From 4a3c042c087ab60cffd540ff2d9a0ae976f9fa6c Mon Sep 17 00:00:00 2001 From: KnowWhoami Date: Mon, 30 Dec 2024 18:47:53 +0530 Subject: [PATCH 4/5] enhancements in test section. This involves: - Docmentation about fee workflow for every IT. - Refactor the IT's as almost IT's uses same code -> thus created some api's to reduce redundancy. - Fix some bugs in calculating coinswap fees. --- src/maker/api.rs | 2 +- src/maker/config.rs | 2 +- src/maker/handlers.rs | 21 +- src/market/directory.rs | 2 +- src/market/rpc/server.rs | 3 +- src/taker/routines.rs | 14 +- src/utill.rs | 1 + src/wallet/rpc.rs | 2 +- tests/abort1.rs | 357 ++++++++---------------------- tests/abort2_case1.rs | 255 +++++++++++++--------- tests/abort2_case2.rs | 312 ++++++++++----------------- tests/abort2_case3.rs | 203 +++++++++++------- tests/abort3_case1.rs | 215 ++++++++++++------- tests/abort3_case2.rs | 186 +++++++++------- tests/abort3_case3.rs | 196 ++++++++--------- tests/dns.rs | 8 +- tests/maker_cli.rs | 4 +- tests/malice1.rs | 333 ++++++++-------------------- tests/malice2.rs | 358 ++++++++++--------------------- tests/standard_swap.rs | 417 ++++++++---------------------------- tests/taker_cli.rs | 2 - tests/test_framework/mod.rs | 226 +++++++++++++++++-- 22 files changed, 1342 insertions(+), 1777 deletions(-) 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..fd6848b7 100644 --- a/tests/abort3_case3.rs +++ b/tests/abort3_case3.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::{ - fs::File, io::Read, path::PathBuf, sync::atomic::Ordering::Relaxed, thread, time::Duration, -}; +use std::{sync::atomic::Ordering::Relaxed, thread, time::Duration}; /// ABORT 3: Maker Drops After Setup /// Case 3: CloseAtHashPreimage @@ -32,7 +31,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 +39,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 +70,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..2b361247 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..0041c819 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,198 @@ 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 + || balance_diff == Amount::from_sat(441642), // 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 +393,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 +443,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 From cedfe95dbb62b2049c9b6ae32df0e92bd28ca4de Mon Sep 17 00:00:00 2001 From: KnowWhoami Date: Wed, 1 Jan 2025 13:01:34 +0530 Subject: [PATCH 5/5] fixes --- directory.toml | 8 +++++-- maker.toml | 40 +++++++++++--------------------- src/bin/directoryd.rs | 7 ++---- src/bin/makerd.rs | 6 +---- src/bin/taker.rs | 14 ++++-------- src/maker/api.rs | 17 ++++++++++---- src/maker/config.rs | 21 ++++++++--------- src/maker/handlers.rs | 13 ++--------- src/maker/rpc/server.rs | 1 - src/maker/server.rs | 2 +- src/protocol/messages.rs | 21 ++++++++--------- src/taker/api.rs | 21 ++++++++--------- src/taker/offers.rs | 6 ++--- src/taker/routines.rs | 2 +- src/utill.rs | 20 ++++++++++------ taker.toml | 49 ++++++++-------------------------------- tests/maker_cli.rs | 2 +- tests/standard_swap.rs | 12 ++++------ 18 files changed, 103 insertions(+), 159 deletions(-) diff --git a/directory.toml b/directory.toml index 49112ba9..5d4cffe3 100644 --- a/directory.toml +++ b/directory.toml @@ -1,4 +1,8 @@ -[directory_config] +# Listening port port = 8080 +# Socks port socks_port = 19060 -connection_type = "tor" \ No newline at end of file +# Connection type +connection_type = TOR +# RPC listening port +rpc_port = 4321 \ No newline at end of file diff --git a/maker.toml b/maker.toml index 12325f5c..b3505a7c 100644 --- a/maker.toml +++ b/maker.toml @@ -1,30 +1,16 @@ -Think about this? -[maker_config] # Listening port port = 6102 -# Time interval between connection checks -heart_beat_interval_secs = 3 -# Time interval to ping the RPC backend -rpc_ping_interval_secs = 60 -# Time interval to ping directory server -# 12 hours = 60*60*12 = 43200 -directory_servers_refresh_interval_secs = 43200 -# Time interval to close a connection if no response is received -idle_connection_timeout = 300 -# Absolute coinswap fee -base_fee_sats = 1000 -# Fee rate per swap amount in ppb. -amount_relative_fee_ppb = 10000000 -# Fee rate for timelocked contract in ppb -time_relative_fee_ppb = 100000 -# No of confirmation required for funding transaction -required_confirms = 1 -# Minimum timelock difference between contract transaction of two hops -min_contract_reaction_time = 48 -# Minimum coinswap amount size in sats -min_size = 10000 +# RPC listening port +rpc_port +# Minimum Coinswap amount +min_swap_amount = 100000 # Socks port -socks_part = 19050 -# Directory server onion address -directory_server_onion_address = "directoryhiddenserviceaddress.onion:8080" -connection_type = "tor" \ No newline at end of file +socks_port = 19050 +# Directory server address +directory_server_address = 127.0.0.1:8080 +# Fidelity Bond amount +fidelity_amount = 5000000 +# Fidelity Bond timelock in Block heights +fidelity_timelock = 26000 +# Connection type +connection_type = TOR \ No newline at end of file diff --git a/src/bin/directoryd.rs b/src/bin/directoryd.rs index 6d3fce88..5cf26de3 100644 --- a/src/bin/directoryd.rs +++ b/src/bin/directoryd.rs @@ -57,11 +57,7 @@ fn main() -> Result<(), DirectoryServerError> { wallet_name: "random".to_string(), // we can put anything here as it will get updated in the init. }; - let conn_type = if cfg!(feature = "integration-test") { - ConnectionType::CLEARNET - } else { - ConnectionType::TOR - }; + let conn_type = ConnectionType::TOR; #[cfg(feature = "tor")] { @@ -69,6 +65,7 @@ fn main() -> Result<(), DirectoryServerError> { setup_mitosis(); } } + let directory = Arc::new(DirectoryServer::new(args.data_directory, Some(conn_type))?); start_directory_server(directory, Some(rpc_config))?; diff --git a/src/bin/makerd.rs b/src/bin/makerd.rs index 905d97ec..160f0c03 100644 --- a/src/bin/makerd.rs +++ b/src/bin/makerd.rs @@ -64,11 +64,7 @@ fn main() -> Result<(), MakerError> { wallet_name: "random".to_string(), // we can put anything here as it will get updated in the init. }; - let conn_type = if cfg!(feature = "integration-test") { - ConnectionType::CLEARNET - } else { - ConnectionType::TOR - }; + let conn_type = ConnectionType::TOR; #[cfg(feature = "tor")] { diff --git a/src/bin/taker.rs b/src/bin/taker.rs index 1c59c560..c89d80bf 100644 --- a/src/bin/taker.rs +++ b/src/bin/taker.rs @@ -3,7 +3,7 @@ use bitcoind::bitcoincore_rpc::{json::ListUnspentResultEntry, Auth}; use clap::Parser; use coinswap::{ taker::{error::TakerError, SwapParams, Taker, TakerBehavior}, - utill::{parse_proxy_auth, setup_taker_logger, ConnectionType}, + utill::{parse_proxy_auth, setup_taker_logger, ConnectionType, REQUIRED_CONFIRMS}, wallet::{Destination, RPCConfig, SendAmount}, }; use log::LevelFilter; @@ -51,9 +51,6 @@ struct Cli { /// Sets the transaction count. #[clap(name = "tx_count", default_value = "3")] pub tx_count: u32, - /// Sets the required on-chain confirmations. - #[clap(name = "required_confirms", default_value = "1000")] - pub required_confirms: u32, /// List of sub commands to process various endpoints of taker cli app. #[clap(subcommand)] command: Commands, @@ -98,11 +95,8 @@ fn main() -> Result<(), TakerError> { let args = Cli::parse(); let rpc_network = bitcoin::Network::from_str(&args.bitcoin_network).unwrap(); - let connection_type = if cfg!(feature = "integration-test") { - ConnectionType::CLEARNET - } else { - ConnectionType::TOR - }; + + let connection_type = ConnectionType::TOR; let rpc_config = RPCConfig { url: args.rpc, @@ -115,7 +109,7 @@ fn main() -> Result<(), TakerError> { send_amount: Amount::from_sat(args.send_amount), maker_count: args.maker_count, tx_count: args.tx_count, - required_confirms: args.required_confirms, + required_confirms: REQUIRED_CONFIRMS, }; let mut taker = Taker::init( diff --git a/src/maker/api.rs b/src/maker/api.rs index 7cd679c8..8e900f19 100644 --- a/src/maker/api.rs +++ b/src/maker/api.rs @@ -12,7 +12,10 @@ use crate::{ messages::{FidelityProof, ReqContractSigsForSender}, Hash160, }, - utill::{get_maker_dir, redeemscript_to_scriptpubkey, ConnectionType, HEART_BEAT_INTERVAL}, + utill::{ + get_maker_dir, redeemscript_to_scriptpubkey, ConnectionType, HEART_BEAT_INTERVAL, + REQUIRED_CONFIRMS, + }, wallet::{RPCConfig, SwapCoin, WalletSwapCoin}, }; use bitcoin::{ @@ -57,9 +60,6 @@ pub const RPC_PING_INTERVAL: Duration = Duration::from_secs(60); /// Maker triggers the recovery mechanism, if Taker is idle for more than 300 secs. pub const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(300); -/// Number of confirmation required funding transaction. -pub const REQUIRED_CONFIRMS: u32 = 1; - /// The minimum difference in locktime (in blocks) between the incoming and outgoing swaps. /// /// This value specifies the reaction time, in blocks, available to a Maker @@ -591,6 +591,13 @@ pub fn check_for_broadcasted_contracts(maker: Arc) -> Result<(), MakerErr /// Broadcast the contract transactions and claim funds via timelock. pub fn check_for_idle_states(maker: Arc) -> Result<(), MakerError> { let mut bad_ip = Vec::new(); + + let conn_timeout = if cfg!(feature = "integration-test") { + Duration::from_secs(60) + } else { + IDLE_CONNECTION_TIMEOUT + }; + loop { if maker.shutdown.load(Relaxed) { break; @@ -612,7 +619,7 @@ pub fn check_for_idle_states(maker: Arc) -> Result<(), MakerError> { ip, no_response_since ); - if no_response_since > IDLE_CONNECTION_TIMEOUT { + if no_response_since > conn_timeout { log::error!( "[{}] Potential Dropped Connection from {}", maker.config.port, diff --git a/src/maker/config.rs b/src/maker/config.rs index 6612c5b5..c4486a64 100644 --- a/src/maker/config.rs +++ b/src/maker/config.rs @@ -16,14 +16,14 @@ pub struct MakerConfig { pub port: u16, /// RPC listening port pub rpc_port: u16, - /// Minimum swap size. + /// Minimum Coinswap amount pub min_swap_amount: u64, /// Socks port pub socks_port: u16, /// Directory server address (can be clearnet or onion) pub directory_server_address: String, - /// Fidelity Bond Value - pub fidelity_value: u64, + /// Fidelity Bond amount + pub fidelity_amount: u64, /// Fidelity Bond timelock in Block heights. pub fidelity_timelock: u32, /// Connection type @@ -38,8 +38,8 @@ impl Default for MakerConfig { min_swap_amount: MIN_SWAP_AMOUNT, socks_port: 19050, directory_server_address: "127.0.0.1:8080".to_string(), - fidelity_value: 5_000_000, // 5 million sats - fidelity_timelock: 26_000, // Approx 6 months of blocks + fidelity_amount: 5_000_000, // 5 million sats + fidelity_timelock: 26_000, // Approx 6 months of blocks connection_type: { #[cfg(feature = "tor")] { @@ -100,9 +100,9 @@ impl MakerConfig { config_map.get("directory_server_address"), default_config.directory_server_address, ), - fidelity_value: parse_field( - config_map.get("fidelity_value"), - default_config.fidelity_value, + fidelity_amount: parse_field( + config_map.get("fidelity_amount"), + default_config.fidelity_amount, ), fidelity_timelock: parse_field( config_map.get("fidelity_timelock"), @@ -123,7 +123,7 @@ rpc_port = {} min_swap_amount = {} socks_port = {} directory_server_address = {} -fidelity_value = {} +fidelity_amount = {} fidelity_timelock = {} connection_type = {:?}", self.port, @@ -131,7 +131,7 @@ connection_type = {:?}", self.min_swap_amount, self.socks_port, self.directory_server_address, - self.fidelity_value, + self.fidelity_amount, self.fidelity_timelock, self.connection_type, ); @@ -172,7 +172,6 @@ mod tests { port = 6102 rpc_port = 6103 required_confirms = 1 - min_contract_reaction_time = 48 min_swap_amount = 100000 socks_port = 19050 "#; diff --git a/src/maker/handlers.rs b/src/maker/handlers.rs index ecf30a7b..425805fe 100644 --- a/src/maker/handlers.rs +++ b/src/maker/handlers.rs @@ -19,8 +19,7 @@ use bitcoind::bitcoincore_rpc::RpcApi; use super::{ api::{ recover_from_swap, ConnectionState, ExpectedMessage, Maker, MakerBehavior, - AMOUNT_RELATIVE_FEE_PCT, BASE_FEE, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS, - TIME_RELATIVE_FEE_PCT, + AMOUNT_RELATIVE_FEE_PCT, BASE_FEE, MIN_CONTRACT_REACTION_TIME, TIME_RELATIVE_FEE_PCT, }, error::MakerError, }; @@ -40,6 +39,7 @@ use crate::{ }, Hash160, }, + utill::REQUIRED_CONFIRMS, wallet::{IncomingSwapCoin, SwapCoin, WalletError, WalletSwapCoin}, }; @@ -236,8 +236,6 @@ impl Maker { if total_funding_amount >= self.config.min_swap_amount && total_funding_amount < self.wallet.read()?.store.offer_maxsize - // TODO: Taker must not be allowed to send the amount beyond this range? - // why can't it be <= ? { log::info!( "[{}] Total Funding Amount = {} | Funding Txids = {:?}", @@ -249,7 +247,6 @@ impl Maker { ContractSigsForSender { sigs }, )) } else { - // Instead , we must create a MakerToTakerMessage variant stating about it rather than giving error back to Maker itself. Err(MakerError::General("not enough funds")) } } @@ -425,12 +422,6 @@ impl Maker { act_funding_txs_fees ); - // log::info!( - // "Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | ", - // message.refund_locktime, - // act_funding_txs_fees - // ); - connection_state.pending_funding_txes = my_funding_txes; connection_state.outgoing_swapcoins = outgoing_swapcoins; diff --git a/src/maker/rpc/server.rs b/src/maker/rpc/server.rs index 036485b0..76ee4acf 100644 --- a/src/maker/rpc/server.rs +++ b/src/maker/rpc/server.rs @@ -208,7 +208,6 @@ pub fn start_rpc_server(maker: Arc) -> Result<(), MakerError> { // do nothing } else { log::error!("Error accepting RPC connection: {:?}", e); - return Err(e.into()); // Why are we returning the error instead of continuing? } } } diff --git a/src/maker/server.rs b/src/maker/server.rs index 47a5979e..3cf7af5c 100644 --- a/src/maker/server.rs +++ b/src/maker/server.rs @@ -211,7 +211,7 @@ fn setup_fidelity_bond(maker: &Arc, maker_address: &str) -> Result<(), Ma *proof = Some(highest_proof); } else { // No bond in the wallet. Lets attempt to create one. - let amount = Amount::from_sat(maker.config.fidelity_value); + let amount = Amount::from_sat(maker.config.fidelity_amount); let current_height = maker .get_wallet() .read()? diff --git a/src/protocol/messages.rs b/src/protocol/messages.rs index 3ad83ee3..b3182431 100644 --- a/src/protocol/messages.rs +++ b/src/protocol/messages.rs @@ -170,14 +170,14 @@ pub struct HashPreimage { } /// Multisig Privatekeys used in the last step of coinswap to perform privatekey handover. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct MultisigPrivkey { pub multisig_redeemscript: ScriptBuf, pub key: SecretKey, } /// Message to perform the final Privatekey Handover. This is the last message of the Coinswap Protocol. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct PrivKeyHandover { pub multisig_privkeys: Vec, } @@ -221,14 +221,14 @@ impl Display for TakerToMakerMessage { } /// Represents the initial handshake message sent from Maker to Taker. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct MakerHello { pub protocol_version_min: u32, pub protocol_version_max: u32, } /// Contains proof data related to fidelity bond. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct FidelityProof { pub bond: FidelityBond, pub cert_hash: Hash, @@ -236,7 +236,7 @@ pub struct FidelityProof { } /// Represents an offer in the context of the Coinswap protocol. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct Offer { pub base_fee: u64, // base fee in sats pub amount_relative_fee_pct: f64, // % fee on total amount @@ -248,16 +248,15 @@ pub struct Offer { pub tweakable_point: PublicKey, pub fidelity: FidelityProof, } -// TODO: Should Offer struct use Amount struct instead of u64? /// Contract Tx signatures provided by a Sender of a Coinswap. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct ContractSigsForSender { pub sigs: Vec, } /// Contract Tx and extra metadata from a Sender of a Coinswap -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct SenderContractTxInfo { pub contract_tx: Transaction, pub timelock_pubkey: PublicKey, @@ -269,7 +268,7 @@ pub struct SenderContractTxInfo { /// for the Maker as both Sender and Receiver of Coinswaps. /// /// This message is sent by a Maker after a [`ProofOfFunding`] has been received. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct ContractSigsAsRecvrAndSender { /// Contract Tx by which this maker is receiving Coinswap. pub receivers_contract_txs: Vec, @@ -278,13 +277,13 @@ pub struct ContractSigsAsRecvrAndSender { } /// Contract Tx signatures a Maker sends as a Receiver of CoinSwap. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct ContractSigsForRecvr { pub sigs: Vec, } /// All messages sent from Maker to Taker. -#[derive(Debug, Serialize, Deserialize, PartialEq)] // why do we require PartialEq? +#[derive(Debug, Serialize, Deserialize)] pub enum MakerToTakerMessage { /// Protocol Handshake. MakerHello(MakerHello), diff --git a/src/taker/api.rs b/src/taker/api.rs index 14bfe315..59665673 100644 --- a/src/taker/api.rs +++ b/src/taker/api.rs @@ -12,7 +12,7 @@ use std::{ collections::{HashMap, HashSet}, net::TcpStream, path::PathBuf, - thread::{self, sleep}, + thread::sleep, time::{Duration, Instant}, }; @@ -174,7 +174,7 @@ impl Taker { /// ### Parameters: /// - `data_dir`: /// - `Some(value)`: Use the specified directory for storing data. - /// - `None`: Use the default data directory (e.g., for Linux: `~/.coinswap/maker`). + /// - `None`: Use the default data directory (e.g., for Linux: `~/.coinswap/taker`). /// - `wallet_file_name`: /// - `Some(value)`: Attempt to load a wallet file named `value`. If it does not exist, a new wallet with the given name will be created. /// - `None`: Create a new wallet file with the default name `maker-wallet`. @@ -263,15 +263,13 @@ impl Taker { "/tmp/tor-rust-taker".to_string(), )); - thread::sleep(Duration::from_secs(10)); // Think about this? + // wait for tor process to create a new log file. + std::thread::sleep(Duration::from_secs(3)); - // when we are getting error while reading the file -> how can we say that taker tor is started? if let Err(e) = monitor_log_for_completion(&PathBuf::from(tor_log_dir), "100%") { - log::error!("Error monitoring taker log file: {}", e); + log::error!("Error monitoring taker log file: {}\n Try removing the tor directory and retry", e); + return Err(TakerError::IO(e)); } - - // TODO: Think about here? - log::info!("Taker tor is instantiated"); } } @@ -451,7 +449,7 @@ impl Taker { // Loop until we find a live maker who responded to our signature request. let (maker, funding_txs) = loop { // Fail early if not enough good makers in the list to satisfy swap requirements. - let untried_maker_count = self.offerbook.get_all_untried().len(); //TODO: why we want to remove good makers? + let untried_maker_count = self.offerbook.get_all_untried().len(); if untried_maker_count < (self.ongoing_swap_state.swap_params.maker_count) { log::error!("Not enough makers to satisfy swap requirements."); @@ -859,7 +857,6 @@ impl Taker { maker_refund_locktime: u16, funding_tx_infos: &[FundingTxInfo], ) -> Result<(NextPeerInfo, ContractSigsAsRecvrAndSender), TakerError> { - // TODO: WHy we are using this api two times. let this_maker = &self .ongoing_swap_state .peer_infos @@ -875,7 +872,7 @@ impl Taker { ); let address = this_maker.address.to_string(); let mut socket = match self.config.connection_type { - ConnectionType::CLEARNET => TcpStream::connect(address)?, // Why we give return here instead of trying again? + ConnectionType::CLEARNET => TcpStream::connect(address)?, #[cfg(feature = "tor")] ConnectionType::TOR => Socks5Stream::connect( format!("127.0.0.1:{}", self.config.socks_port).as_str(), @@ -1700,7 +1697,7 @@ impl Taker { .get_all_untried() .iter() .find(|oa| { - send_amount > Amount::from_sat(oa.offer.min_size) + send_amount >= Amount::from_sat(oa.offer.min_size) && send_amount < Amount::from_sat(oa.offer.max_size) && !self .ongoing_swap_state diff --git a/src/taker/offers.rs b/src/taker/offers.rs index 47df06ba..beaa6b77 100644 --- a/src/taker/offers.rs +++ b/src/taker/offers.rs @@ -28,7 +28,7 @@ use crate::{ use super::{config::TakerConfig, error::TakerError, routines::download_maker_offer}; /// Represents an offer along with the corresponding maker address. -#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Clone, PartialEq)] pub struct OfferAndAddress { pub offer: Offer, pub address: MakerAddress, @@ -175,7 +175,7 @@ pub fn fetch_offer_from_makers( /// Retrieves advertised maker addresses from directory servers based on the specified network. pub fn fetch_addresses_from_dns( - socks_port: Option, + _socks_port: Option, directory_server_address: String, connection_type: ConnectionType, ) -> Result, TakerError> { @@ -186,7 +186,7 @@ pub fn fetch_addresses_from_dns( ConnectionType::CLEARNET => TcpStream::connect(directory_server_address.as_str())?, #[cfg(feature = "tor")] ConnectionType::TOR => { - let socket_addrs = format!("127.0.0.1:{}", socks_port.expect("Tor port expected")); + let socket_addrs = format!("127.0.0.1:{}", _socks_port.expect("Tor port expected")); Socks5Stream::connect(socket_addrs, directory_server_address.as_str())?.into_inner() } }; diff --git a/src/taker/routines.rs b/src/taker/routines.rs index 3beed557..1ee71a09 100644 --- a/src/taker/routines.rs +++ b/src/taker/routines.rs @@ -358,7 +358,7 @@ pub(crate) fn send_proof_of_funding_and_init_next_hop( } log::info!( - "Maker Received ={} | Maker is Forwarding = {} | Coinswap Fees = {} | Miner Fees paid by us={} ", + "Maker Received = {} | Maker is Forwarding = {} | Coinswap Fees = {} | Miner Fees paid by us = {} ", Amount::from_sat(this_amount), next_amount, Amount::from_sat(coinswap_fees), diff --git a/src/utill.rs b/src/utill.rs index d06d2993..057f1020 100644 --- a/src/utill.rs +++ b/src/utill.rs @@ -58,6 +58,9 @@ pub const GLOBAL_PAUSE: Duration = Duration::from_secs(10); /// Global heartbeat interval used during waiting periods in critical situations. pub const HEART_BEAT_INTERVAL: Duration = Duration::from_secs(3); +/// Number of confirmation required funding transaction. +pub const REQUIRED_CONFIRMS: u32 = 1; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ConnectionType { #[cfg(feature = "tor")] @@ -588,17 +591,20 @@ mod tests { protocol_version_min: 1, protocol_version_max: 100, }); + thread::spawn(move || { let (mut socket, _) = listener.accept().unwrap(); let msg_bytes = read_message(&mut socket).unwrap(); let msg: MakerToTakerMessage = serde_cbor::from_slice(&msg_bytes).unwrap(); - assert_eq!( - msg, - MakerToTakerMessage::MakerHello(MakerHello { - protocol_version_min: 1, - protocol_version_max: 100 - }) - ); + + if let MakerToTakerMessage::MakerHello(hello) = msg { + assert!(hello.protocol_version_min == 1 && hello.protocol_version_max == 100); + } else { + panic!( + "Received Wrong Message: Expected MakerHello variant, Got: {:?}", + msg, + ); + } }); let mut stream = TcpStream::connect(address).unwrap(); diff --git a/taker.toml b/taker.toml index 79d94d63..af140627 100644 --- a/taker.toml +++ b/taker.toml @@ -1,39 +1,10 @@ -[taker_config] -# relatively low value for now so that its easier to test without having to wait too much -# right now only the very brave will try coinswap out on mainnet with non-trivial amounts - -#in blocks -refund_locktime = 48 -#in blocks -refund_locktime_step = 48 - -# first connect means the first time you're ever connecting, without having gotten any txes -# confirmed yet, so the taker will not be very persistent since there should be plenty of other -# makers out there -# but also it should allow for flaky connections, otherwise you exclude raspberry pi nodes running -# in people's closets, which are very important for decentralization - -first_connect_attempts = 5 -first_connect_sleep_delay_sec = 1 -first_connect_attempt_timeout_sec = 60 - -# reconnect means when connecting to a maker again after having already gotten txes confirmed -# as it would be a waste of miner fees to give up, the taker is coded to be very persistent -# taker will first attempt to connect with a short delay between attempts -# after that will attempt to connect with a longer delay between attempts -# these figures imply that taker will attempt to connect for just over 48 hours -# of course the user can ctrl+c before then if they give up themselves - -reconnect_attempts = 3200 -reconnect_short_sleep_delay = 10 -reconnect_long_sleep_delay = 60 -# after this many attempts, switch to sleeping longer -short_long_sleep_delay_transition = 60 -reconnect_attempt_timeout_sec = 300 - -# tor configuration -tor_port = 8000 -socks_port = 19050 -# Directory server onion address -directory_server_onion_address = "directoryhiddenserviceaddress.onion:8080" -connection_type = "tor" \ No newline at end of file +# Network listening port +port= 8000, +#Socks port +socks_port= 19070, +# Directory server address +directory_server_address=directoryhiddenserviceaddress.onion:8080 , +# Connection type +connection_type= TOR, +# RPC port +rpc_port= 8081, \ No newline at end of file diff --git a/tests/maker_cli.rs b/tests/maker_cli.rs index 0cd4c572..9130994f 100644 --- a/tests/maker_cli.rs +++ b/tests/maker_cli.rs @@ -158,7 +158,7 @@ fn test_maker_cli() { // Tor address check let tor_addr = maker_cli.execute_maker_cli(&["get-tor-address"]); await_message(&rx, "RPC request received: GetTorAddress"); - assert_eq!(tor_addr, "Maker is not running on TOR"); + assert!(tor_addr.contains("onion:6102")); // Initial Balance checks let seed_balance = maker_cli.execute_maker_cli(&["seed-balance"]); diff --git a/tests/standard_swap.rs b/tests/standard_swap.rs index 864b8c39..e70194a7 100644 --- a/tests/standard_swap.rs +++ b/tests/standard_swap.rs @@ -28,13 +28,11 @@ fn test_standard_coinswap() { ((16102, Some(19052)), MakerBehavior::Normal), ]; - // let connection_type = if cfg!(target_os = "macos") { - // ConnectionType::CLEARNET - // } else { - // ConnectionType::TOR - // }; - - let connection_type = ConnectionType::CLEARNET; + let connection_type = if cfg!(target_os = "macos") { + ConnectionType::CLEARNET + } else { + ConnectionType::TOR + }; // Initiate test framework, Makers and a Taker with default behavior. let (test_framework, mut taker, makers, directory_server_instance, block_generation_handle) =