From 4c3668b99b09ec6cdebfd99e282962ad18423827 Mon Sep 17 00:00:00 2001 From: chaitika Date: Thu, 2 Oct 2025 17:08:35 +0530 Subject: [PATCH 1/2] refactor: list methods return references --- examples/taker_basic.rs | 4 +- examples/wallet_basic.rs | 10 ++-- src/bin/taker.rs | 4 +- src/maker/rpc/server.rs | 9 ++-- src/taker/api.rs | 46 ++++++++++------- src/utill.rs | 17 +++++++ src/wallet/api.rs | 99 ++++++++++++++++--------------------- src/wallet/spend.rs | 27 +++++----- tests/fidelity.rs | 17 +++---- tests/test_framework/mod.rs | 4 +- tests/utxo_behavior.rs | 3 +- 11 files changed, 123 insertions(+), 117 deletions(-) diff --git a/examples/taker_basic.rs b/examples/taker_basic.rs index c63a70818..017b67c78 100644 --- a/examples/taker_basic.rs +++ b/examples/taker_basic.rs @@ -106,7 +106,7 @@ fn main() -> Result<(), Box> { println!("Initial wallet state:"); println!(" Spendable: {} BTC", balances.spendable.to_btc()); println!(" Regular: {} BTC", balances.regular.to_btc()); - println!(" UTXOs: {}", utxos.len()); + println!(" UTXOs: {}", utxos.count()); // Fund the wallet if empty if balances.spendable == Amount::ZERO { @@ -173,7 +173,7 @@ fn main() -> Result<(), Box> { println!(" Contract: {} BTC", final_balances.contract.to_btc()); // Show UTXOs - let utxos = wallet_mut.list_all_utxo(); + let utxos: Vec<_> = wallet_mut.list_all_utxo().collect(); println!("\nUTXO information:"); println!(" Total UTXOs: {}", utxos.len()); if !utxos.is_empty() { diff --git a/examples/wallet_basic.rs b/examples/wallet_basic.rs index 2157a4683..7696956b9 100644 --- a/examples/wallet_basic.rs +++ b/examples/wallet_basic.rs @@ -149,7 +149,7 @@ fn main() -> Result<(), Box> { println!("\nUTXO Summary:"); let utxos = wallet.list_all_utxo(); let swapcoins_count = wallet.get_swapcoins_count(); - println!(" Total UTXOs: {}", utxos.len()); + println!(" Total UTXOs: {}", utxos.count()); println!(" Swap coins: {swapcoins_count}"); // Categorize UTXOs by type @@ -160,7 +160,7 @@ fn main() -> Result<(), Box> { let swept_utxos = wallet.list_swept_incoming_swap_utxos(); println!(" Regular UTXOs: {}", regular_utxos.len()); println!(" Swap UTXOs: {}", swap_utxos.len()); - println!(" Fidelity UTXOs: {}", fidelity_utxos.len()); + println!(" Fidelity UTXOs: {}", fidelity_utxos.count()); println!(" Swept swap UTXOs: {}", swept_utxos.len()); // Generate addresses @@ -265,9 +265,9 @@ fn main() -> Result<(), Box> { let hashlock_contracts = wallet.list_live_hashlock_contract_spend_info(); let all_utxo_info = wallet.list_all_utxo_spend_info(); - println!(" Live contracts: {}", live_contracts.len()); - println!(" Timelock contracts: {}", timelock_contracts.len()); - println!(" Hashlock contracts: {}", hashlock_contracts.len()); + println!(" Live contracts: {}", live_contracts.count()); + println!(" Timelock contracts: {}", timelock_contracts.count()); + println!(" Hashlock contracts: {}", hashlock_contracts.count()); println!( " Detailed UTXO info available for {} UTXOs", all_utxo_info.len() diff --git a/src/bin/taker.rs b/src/bin/taker.rs index b1627c3c4..1b7d49bfc 100644 --- a/src/bin/taker.rs +++ b/src/bin/taker.rs @@ -204,14 +204,14 @@ fn main() -> Result<(), TakerError> { Commands::ListUtxoSwap => { let utxos = taker.get_wallet().list_incoming_swap_coin_utxo_spend_info(); for utxo in utxos { - let utxo = UTXO::from_utxo_data(utxo); + let utxo = UTXO::from(utxo); println!("{}", serde_json::to_string_pretty(&utxo)?); } } Commands::ListUtxoContract => { let utxos = taker.get_wallet().list_live_timelock_contract_spend_info(); for utxo in utxos { - let utxo = UTXO::from_utxo_data(utxo); + let utxo = UTXO::from(utxo); println!("{}", serde_json::to_string_pretty(&utxo)?); } } diff --git a/src/maker/rpc/server.rs b/src/maker/rpc/server.rs index 8bc10fdc9..25a558d51 100644 --- a/src/maker/rpc/server.rs +++ b/src/maker/rpc/server.rs @@ -38,8 +38,7 @@ fn handle_request(maker: &Arc, socket: &mut TcpStream) -> Result .wallet() .read()? .list_live_timelock_contract_spend_info() - .into_iter() - .map(UTXO::from_utxo_data) + .map(UTXO::from) .collect(); RpcMsgResp::ContractUtxoResp { utxos } } @@ -48,8 +47,7 @@ fn handle_request(maker: &Arc, socket: &mut TcpStream) -> Result .wallet() .read()? .list_fidelity_spend_info() - .into_iter() - .map(UTXO::from_utxo_data) + .map(UTXO::from) .collect(); RpcMsgResp::FidelityUtxoResp { utxos } } @@ -68,8 +66,7 @@ fn handle_request(maker: &Arc, socket: &mut TcpStream) -> Result .wallet() .read()? .list_incoming_swap_coin_utxo_spend_info() - .into_iter() - .map(UTXO::from_utxo_data) + .map(UTXO::from) .collect(); RpcMsgResp::SwapUtxoResp { utxos } } diff --git a/src/taker/api.rs b/src/taker/api.rs index 5fa8dcdea..afd371dcf 100644 --- a/src/taker/api.rs +++ b/src/taker/api.rs @@ -328,7 +328,6 @@ impl Taker { /// If that fails too. Open an issue at [our github](https://github.com/citadel-tech/coinswap/issues) pub(crate) fn send_coinswap(&mut self, swap_params: SwapParams) -> Result<(), TakerError> { let swap_start_time = std::time::Instant::now(); - let initial_utxoset = self.wallet.list_all_utxo(); self.ongoing_swap_state.swap_params = swap_params.clone(); // Check if we have enough balance - try regular first, then swap @@ -518,7 +517,11 @@ impl Taker { // Generate post-swap report self.wallet.sync_and_save()?; - self.print_swap_report(prereset_swapstate, swap_start_time, initial_utxoset)?; + self.print_swap_report( + prereset_swapstate, + swap_start_time, + self.wallet.list_all_utxo(), + )?; } Err(e) => { log::error!("Swap Settlement Failed : {e:?}"); @@ -531,11 +534,11 @@ impl Taker { Ok(()) } - fn print_swap_report( + fn print_swap_report<'a>( &self, prereset_swapstate: &OngoingSwapState, start_time: std::time::Instant, - initial_utxos: Vec, + initial_utxos: impl Iterator, ) -> Result<(), TakerError> { let swap_state = &prereset_swapstate; @@ -549,14 +552,6 @@ impl Taker { .map(|(utxo, _)| utxo) .collect::>(); - let initial_outpoints = initial_utxos - .iter() - .map(|utxo| OutPoint { - txid: utxo.txid, - vout: utxo.vout, - }) - .collect::>(); - let current_outpoints = all_regular_utxo .iter() .map(|utxo| OutPoint { @@ -566,17 +561,30 @@ impl Taker { .collect::>(); // Present in initial set but not in current set (destroyed) - let input_utxos = initial_utxos - .iter() - .filter(|utxo| { - let initial_outpoint = OutPoint { + let (initial_outpoints, input_utxos): (HashSet<_>, Vec<_>) = initial_utxos + .into_iter() + .map(|utxo| { + let outpoint = OutPoint { txid: utxo.txid, vout: utxo.vout, }; - !current_outpoints.contains(&initial_outpoint) + let amount = if !current_outpoints.contains(&outpoint) { + Some(utxo.amount.to_sat()) + } else { + None + }; + (outpoint, amount) }) - .map(|utxo| utxo.amount.to_sat()) - .collect::>(); + .fold( + (HashSet::new(), Vec::new()), + |(mut set, mut vec), (outpoint, amount)| { + set.insert(outpoint); + if let Some(a) = amount { + vec.push(a); + } + (set, vec) + }, + ); // Present in current set but not in initial regular set (created) let output_regular_utxos = all_regular_utxo diff --git a/src/utill.rs b/src/utill.rs index 554ba8758..e374f5213 100644 --- a/src/utill.rs +++ b/src/utill.rs @@ -443,6 +443,23 @@ impl UTXO { } } +impl From<(&ListUnspentResultEntry, &UTXOSpendInfo)> for UTXO { + fn from((utxo, spend_info): (&ListUnspentResultEntry, &UTXOSpendInfo)) -> Self { + let addr = utxo + .address + .clone() + .expect("address always expected") + .assume_checked() + .to_string(); + Self { + addr, + amount: utxo.amount, + confirmations: utxo.confirmations, + utxo_type: spend_info.to_string(), + } + } +} + /// Compute the checksum of a descriptor pub(crate) fn compute_checksum(descriptor: &str) -> Result { let mut checksum = CHECKSUM_FINAL_XOR_VALUE; diff --git a/src/wallet/api.rs b/src/wallet/api.rs index 1f74c5f23..d3007597f 100644 --- a/src/wallet/api.rs +++ b/src/wallet/api.rs @@ -410,7 +410,6 @@ impl Wallet { .fold(Amount::ZERO, |sum, (utxo, _)| sum + utxo.amount); let contract = self .list_live_timelock_contract_spend_info() - .iter() .fold(Amount::ZERO, |sum, (utxo, _)| sum + utxo.amount); let swap = self .list_swept_incoming_swap_utxos() @@ -418,7 +417,6 @@ impl Wallet { .fold(Amount::ZERO, |sum, (utxo, _)| sum + utxo.amount); let fidelity = self .list_fidelity_spend_info() - .iter() .fold(Amount::ZERO, |sum, (utxo, _)| sum + utxo.amount); let spendable = regular + swap; @@ -736,11 +734,8 @@ impl Wallet { } /// Returns a list of all UTXOs tracked by the wallet. Including fidelity, live_contracts and swap coins. - pub fn list_all_utxo(&self) -> Vec { - self.list_all_utxo_spend_info() - .iter() - .map(|(utxo, _)| utxo.clone()) - .collect() + pub fn list_all_utxo(&self) -> impl Iterator { + self.store.utxo_cache.values().map(|(utxo, _)| utxo) } /// Returns a list all utxos with their spend info tracked by the wallet. @@ -757,62 +752,58 @@ impl Wallet { } /// Lists live contract UTXOs along with their Spend info. - pub fn list_live_contract_spend_info(&self) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { - let all_valid_utxo = self.list_all_utxo_spend_info(); - let filtered_utxos: Vec<_> = all_valid_utxo - .iter() - .filter(|x| { - matches!(x.1, UTXOSpendInfo::HashlockContract { .. }) - || matches!(x.1, UTXOSpendInfo::TimelockContract { .. }) + pub fn list_live_contract_spend_info( + &self, + ) -> impl Iterator { + self.store + .utxo_cache + .values() + .filter(|(_, spend_info)| { + matches!(spend_info, UTXOSpendInfo::HashlockContract { .. }) + || matches!(spend_info, UTXOSpendInfo::TimelockContract { .. }) }) - .cloned() - .collect(); - filtered_utxos + .map(|(utxo, spend_info)| (utxo, spend_info)) } /// Lists live timelock contract UTXOs along with their Spend info. pub fn list_live_timelock_contract_spend_info( &self, - ) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { - let all_valid_utxo = self.list_all_utxo_spend_info(); - let filtered_utxos: Vec<_> = all_valid_utxo - .iter() - .filter(|x| matches!(x.1, UTXOSpendInfo::TimelockContract { .. })) - .cloned() - .collect(); - filtered_utxos + ) -> impl Iterator + '_ { + self.store + .utxo_cache + .values() + .filter(|(_, spend)| matches!(spend, &UTXOSpendInfo::TimelockContract { .. })) + .map(|(utxo, spend)| (utxo, spend)) } + /// Lists all live hashlock contract UTXOs along with their Spend info. pub fn list_live_hashlock_contract_spend_info( &self, - ) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { - let all_valid_utxo = self.list_all_utxo_spend_info(); - let filtered_utxos: Vec<_> = all_valid_utxo - .iter() - .filter(|x| matches!(x.1, UTXOSpendInfo::HashlockContract { .. })) - .cloned() - .collect(); - filtered_utxos + ) -> impl Iterator + '_ { + self.store + .utxo_cache + .values() + .filter(|(_, spend)| matches!(spend, &UTXOSpendInfo::HashlockContract { .. })) + .map(|(u, s)| (u, s)) } /// Lists fidelity UTXOs along with their Spend info. - pub fn list_fidelity_spend_info(&self) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { - let all_valid_utxo = self.list_all_utxo_spend_info(); - let filtered_utxos: Vec<_> = all_valid_utxo - .iter() - .filter(|x| matches!(x.1, UTXOSpendInfo::FidelityBondCoin { .. })) - .cloned() - .collect(); - filtered_utxos + pub fn list_fidelity_spend_info( + &self, + ) -> impl Iterator + '_ { + self.store + .utxo_cache + .values() + .filter(|(_, spend)| matches!(spend, &UTXOSpendInfo::FidelityBondCoin { .. })) + .map(|(u, s)| (u, s)) } /// Lists descriptor UTXOs along with their Spend info. pub fn list_descriptor_utxo_spend_info(&self) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { let all_valid_utxo = self.list_all_utxo_spend_info(); let filtered_utxos: Vec<_> = all_valid_utxo - .iter() + .into_iter() .filter(|x| matches!(x.1, UTXOSpendInfo::SeedCoin { .. })) - .cloned() .collect(); filtered_utxos } @@ -821,14 +812,13 @@ impl Wallet { pub fn list_swap_coin_utxo_spend_info(&self) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { let all_valid_utxo = self.list_all_utxo_spend_info(); let filtered_utxos: Vec<_> = all_valid_utxo - .iter() + .into_iter() .filter(|x| { matches!( x.1, UTXOSpendInfo::IncomingSwapCoin { .. } | UTXOSpendInfo::OutgoingSwapCoin { .. } ) }) - .cloned() .collect(); filtered_utxos } @@ -836,22 +826,19 @@ impl Wallet { /// Lists all incoming swapcoin UTXOs along with their Spend info. pub fn list_incoming_swap_coin_utxo_spend_info( &self, - ) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { - let all_valid_utxo = self.list_all_utxo_spend_info(); - let filtered_utxos: Vec<_> = all_valid_utxo - .iter() - .filter(|x| matches!(x.1, UTXOSpendInfo::IncomingSwapCoin { .. })) - .cloned() - .collect(); - filtered_utxos + ) -> impl Iterator { + self.store + .utxo_cache + .values() + .filter(|(_, spend_info)| matches!(spend_info, UTXOSpendInfo::IncomingSwapCoin { .. })) + .map(|(utxo, spend_info)| (utxo, spend_info)) } /// Lists all swept incoming swapcoin UTXOs along with their Spend info. pub fn list_swept_incoming_swap_utxos(&self) -> Vec<(ListUnspentResultEntry, UTXOSpendInfo)> { let all_valid_utxo = self.list_all_utxo_spend_info(); let filtered_utxos: Vec<_> = all_valid_utxo - .iter() + .into_iter() .filter(|(_, spend_info)| matches!(spend_info, UTXOSpendInfo::SweptCoin { .. })) - .cloned() .collect(); filtered_utxos } @@ -1815,7 +1802,7 @@ impl Wallet { internal_address ); let sweep_tx = self.spend_coins( - &[(utxo.clone(), spend_info)], + &[(utxo.clone(), spend_info.clone())], Destination::Sweep(internal_address.clone()), feerate, )?; diff --git a/src/wallet/spend.rs b/src/wallet/spend.rs index 79ec61882..b221b2121 100644 --- a/src/wallet/spend.rs +++ b/src/wallet/spend.rs @@ -101,11 +101,11 @@ impl Wallet { let destination = Destination::Sweep(change_addr.clone()); // Find utxo corresponding to expired fidelity bond. - let utxo = match self + let fidelity_spend_info = self .list_fidelity_spend_info() - .iter() - .find(|(_, spend_info)| *spend_info == expired_fidelity_spend_info) - { + .find(|(_, spend_info)| **spend_info == expired_fidelity_spend_info); + + let utxo = match fidelity_spend_info { Some((utxo, _)) => utxo, None => { // If no UTXO is found for the expired fidelity bond, it means the bond was already spent, @@ -159,13 +159,13 @@ impl Wallet { if let UTXOSpendInfo::TimelockContract { swapcoin_multisig_redeemscript, input_value, - } = spend_info.clone() + } = spend_info { - if swapcoin_multisig_redeemscript == og_sc.get_multisig_redeemscript() - && input_value == og_sc.contract_tx.output[0].value + if *swapcoin_multisig_redeemscript == og_sc.get_multisig_redeemscript() + && *input_value == og_sc.contract_tx.output[0].value { let destination = Destination::Sweep(destination_address.clone()); - let coins = vec![(utxo, spend_info)]; + let coins = [(utxo.clone(), spend_info.clone())]; let tx = self.spend_coins(&coins, destination, feerate)?; return Ok(tx); } @@ -185,15 +185,14 @@ impl Wallet { if let UTXOSpendInfo::HashlockContract { swapcoin_multisig_redeemscript, input_value, - } = spend_info.clone() + } = spend_info { - if swapcoin_multisig_redeemscript == ic_sc.get_multisig_redeemscript() - && input_value == ic_sc.contract_tx.output[0].value + if *swapcoin_multisig_redeemscript == ic_sc.get_multisig_redeemscript() + && *input_value == ic_sc.contract_tx.output[0].value { let destination = Destination::Sweep(destination_address.clone()); - let coin = (utxo, spend_info); - let coins = vec![coin]; - let tx = self.spend_coins(&coins, destination, feerate)?; + let coins = [(utxo.clone(), spend_info.clone())]; + let tx: Transaction = self.spend_coins(&coins, destination, feerate)?; return Ok(tx); } } diff --git a/tests/fidelity.rs b/tests/fidelity.rs index 2cc3d0510..2ba724551 100644 --- a/tests/fidelity.rs +++ b/tests/fidelity.rs @@ -322,13 +322,13 @@ fn test_fidelity_spending() { // Assert UTXO shows up in list and track the specific fidelity UTXO let fidelity_utxo_info = { let wallet = maker.get_wallet().read().unwrap(); - let all_utxos = wallet.list_all_utxo(); // Find the specific fidelity bond UTXO by amount - let fidelity_utxo = all_utxos - .iter() + let fidelity_utxo = wallet + .list_all_utxo() .find(|utxo| utxo.amount == fidelity_amount) .expect("Fidelity bond UTXO should be in the list"); + let total_utxos = wallet.list_all_utxo().count(); log::info!( "🔍 Found fidelity bond UTXO: txid={}, vout={}, amount={} sats", @@ -336,7 +336,7 @@ fn test_fidelity_spending() { fidelity_utxo.vout, fidelity_utxo.amount.to_sat() ); - log::info!("📊 Total UTXOs in wallet: {}", all_utxos.len()); + log::info!("📊 Total UTXOs in wallet: {}", total_utxos); // Store UTXO identifiers for tracking (fidelity_utxo.txid, fidelity_utxo.vout, fidelity_utxo.amount) @@ -344,9 +344,8 @@ fn test_fidelity_spending() { let check_fidelity_utxo_integrity = |iteration: usize| { let wallet = maker.get_wallet().read().unwrap(); - let all_utxos = wallet.list_all_utxo(); - let fidelity_utxo_still_exists = all_utxos.iter().any(|utxo| { + let fidelity_utxo_still_exists = wallet.list_all_utxo().any(|utxo| { utxo.txid == fidelity_utxo_info.0 && utxo.vout == fidelity_utxo_info.1 && utxo.amount == fidelity_utxo_info.2 @@ -448,13 +447,13 @@ fn test_fidelity_spending() { // Verify the specific UTXO is now consumed and bond is spent { let wallet = maker.get_wallet().read().unwrap(); - let all_utxos = wallet.list_all_utxo(); - let fidelity_utxo_still_exists = all_utxos.iter().any(|utxo| { + let fidelity_utxo_still_exists = wallet.list_all_utxo().any(|utxo| { utxo.txid == fidelity_utxo_info.0 && utxo.vout == fidelity_utxo_info.1 && utxo.amount == fidelity_utxo_info.2 }); + let total_utxos = wallet.list_all_utxo().count(); if fidelity_utxo_still_exists { panic!( @@ -474,7 +473,7 @@ fn test_fidelity_spending() { fidelity_utxo_info.0, fidelity_utxo_info.1 ); - log::info!("📊 UTXOs after redemption: {}", all_utxos.len()); + log::info!("📊 UTXOs after redemption: {}", total_utxos); } let new_fidelity_index = { diff --git a/tests/test_framework/mod.rs b/tests/test_framework/mod.rs index 06903c864..19972d1a6 100644 --- a/tests/test_framework/mod.rs +++ b/tests/test_framework/mod.rs @@ -236,7 +236,7 @@ pub fn fund_and_verify_taker( // Get initial state before funding let wallet = taker.get_wallet_mut(); wallet.sync_no_fail(); - let initial_utxos = wallet.list_all_utxo(); + let initial_utxos = wallet.list_all_utxo().collect::>(); let initial_utxo_count = initial_utxos.len(); let initial_external_index = *wallet.get_external_index(); @@ -263,7 +263,7 @@ pub fn fund_and_verify_taker( wallet.sync_no_fail(); // Check if utxo list looks good. - let utxos = wallet.list_all_utxo(); + let utxos = wallet.list_all_utxo().collect::>(); // Assert UTXO count assert_eq!( diff --git a/tests/utxo_behavior.rs b/tests/utxo_behavior.rs index acb08a7fc..d692b59f3 100644 --- a/tests/utxo_behavior.rs +++ b/tests/utxo_behavior.rs @@ -449,7 +449,7 @@ fn test_maunal_coinselection() { taker.get_wallet_mut().sync_no_fail(); - let all_utxos = taker.get_wallet_mut().list_all_utxo(); + let all_utxos = taker.get_wallet_mut().list_all_utxo().collect::>(); let manually_selected_utxos: Vec = amounts .iter() @@ -504,7 +504,6 @@ fn test_maunal_coinselection() { let remaining_utxos: Vec = taker .get_wallet_mut() .list_all_utxo() - .iter() .map(|utxo| OutPoint::new(utxo.txid, utxo.vout)) .collect(); From 668c963dbde14ca63cb4e04aa5634fc996ab25e8 Mon Sep 17 00:00:00 2001 From: chaitika Date: Thu, 9 Oct 2025 10:28:11 +0530 Subject: [PATCH 2/2] fix: use saturating_sub to calculate mining fee --- src/taker/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/taker/api.rs b/src/taker/api.rs index afd371dcf..4d4fe6325 100644 --- a/src/taker/api.rs +++ b/src/taker/api.rs @@ -706,7 +706,7 @@ impl Taker { }) .sum::() as u64; - let mining_fee = total_fee - total_maker_fees; + let mining_fee = total_fee.saturating_sub(total_maker_fees); println!("\n\x1b[1;37mMining Fees :\x1b[0m \x1b[36m{mining_fee} sats\x1b[0m",); let fee_percentage = (total_fee as f64 / target_amount as f64) * 100.0; println!("\x1b[1;37mTotal Fee Rate :\x1b[0m \x1b[1;31m{fee_percentage:.2} %\x1b[0m",);