Skip to content

Commit 3470061

Browse files
authored
use coin selection apis in taproot (citadel-tech#644)
* use coin selection apis in taproot * implement `remove_swapcoin_v2` apis for taker and maker * separate swap balance and swept utxos for taproot * generate and return swap report * test fixes * fix the test * some changes
1 parent 6a2aa8b commit 3470061

File tree

10 files changed

+479
-107
lines changed

10 files changed

+479
-107
lines changed

src/maker/api2.rs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ impl Maker {
479479
connection_state.outgoing_contract.other_pubkey = Some(message.next_party_tweakable_point);
480480

481481
// Verify we have sufficient funds and get necessary data
482-
let (outgoing_privkey, funding_utxo) = {
482+
let (outgoing_privkey, selected_utxos) = {
483483
let mut wallet = self.wallet.write()?;
484484

485485
// Sync wallet to get latest UTXO state
@@ -499,29 +499,29 @@ impl Maker {
499499
wallet.rpc.unlock_unspent_all().map_err(WalletError::Rpc)?;
500500
wallet.lock_unspendable_utxos()?;
501501

502-
// Get funding UTXO from our wallet (excludes locked UTXOs)
503-
let spendable_utxos = wallet.list_descriptor_utxo_spend_info();
504-
let funding_utxo = spendable_utxos
505-
.into_iter()
506-
.find(|(utxo, _)| utxo.amount >= connection_state.swap_amount)
507-
.map(|(utxo, _)| utxo)
508-
.ok_or_else(|| {
509-
MakerError::General("No single UTXO found with sufficient amount")
502+
// Use coin_select to get UTXOs that sum to the required amount
503+
let selected_utxos = wallet
504+
.coin_select(connection_state.swap_amount, MIN_FEE_RATE, None)
505+
.map_err(|e| {
506+
MakerError::General(format!("Coin selection failed: {:?}", e).leak())
510507
})?;
511508

512-
// Lock the selected UTXO to prevent double-spending in concurrent swaps
513-
let funding_outpoint = OutPoint::new(funding_utxo.txid, funding_utxo.vout);
509+
// Lock the selected UTXOs to prevent double-spending in concurrent swaps
510+
let funding_outpoints: Vec<OutPoint> = selected_utxos
511+
.iter()
512+
.map(|(utxo, _)| OutPoint::new(utxo.txid, utxo.vout))
513+
.collect();
514514
wallet
515515
.rpc
516-
.lock_unspent(&[funding_outpoint])
516+
.lock_unspent(&funding_outpoints)
517517
.map_err(WalletError::Rpc)?;
518518
log::info!(
519-
"[{}] Locked funding UTXO {} for swap",
519+
"[{}] Locked {} funding UTXOs for swap",
520520
self.config.network_port,
521-
funding_outpoint
521+
funding_outpoints.len()
522522
);
523523

524-
(outgoing_privkey, funding_utxo)
524+
(outgoing_privkey, selected_utxos)
525525
};
526526

527527
// We expect only one contract for now
@@ -611,11 +611,6 @@ impl Maker {
611611
let outgoing_contract_txid = {
612612
let mut wallet = self.wallet.write()?;
613613

614-
// Get the funding UTXO spend info
615-
let funding_utxo_info = wallet
616-
.get_utxo((funding_utxo.txid, funding_utxo.vout))?
617-
.ok_or_else(|| MakerError::General("Funding UTXO not found"))?;
618-
619614
// Use Destination::Multi to send exact amount to contract and keep the fee as change
620615
let contract_address =
621616
bitcoin::Address::from_script(&taproot_script, wallet.store.network)
@@ -629,7 +624,7 @@ impl Maker {
629624
outputs: vec![(contract_address, outgoing_contract_amount)],
630625
op_return_data: None,
631626
},
632-
&[(funding_utxo.clone(), funding_utxo_info)],
627+
&selected_utxos,
633628
)?;
634629

635630
// Broadcast the signed transaction
@@ -913,19 +908,31 @@ impl Maker {
913908
));
914909
}
915910

916-
// Mark the incoming swapcoin as finished by storing the received private key
917-
connection_state.incoming_contract.other_privkey =
918-
Some(privkey_handover_message.secret_key);
919-
920-
// Update the wallet with the completed incoming swapcoin
911+
// Record and remove the incoming swapcoin since we've successfully swept it
912+
// NOTE: We do NOT remove the outgoing swapcoin here - that happens after
913+
// the PrivateKeyHandover message is successfully sent (in server2.rs)
921914
{
922915
let mut wallet = self.wallet.write()?;
923-
wallet.add_incoming_swapcoin_v2(&connection_state.incoming_contract);
924-
wallet.save_to_disk()?;
916+
let incoming_txid = connection_state
917+
.incoming_contract
918+
.contract_tx
919+
.compute_txid();
920+
921+
// Record the swept coin to track swap balance
922+
let output_scriptpubkey = spending_tx.output[0].script_pubkey.clone();
923+
// [TODO] Look into the key value pair later, it shouldn't be both sriptpubkey
924+
wallet
925+
.store
926+
.swept_incoming_swapcoins
927+
.insert(output_scriptpubkey.clone(), output_scriptpubkey);
928+
929+
wallet.remove_incoming_swapcoin_v2(&incoming_txid);
925930
log::info!(
926-
"[{}] Marked incoming swapcoin as finished (other_privkey stored)",
927-
self.config.network_port
931+
"[{}] Removed incoming swapcoin {} after successful sweep",
932+
self.config.network_port,
933+
incoming_txid
928934
);
935+
wallet.save_to_disk()?;
929936
}
930937

931938
let privkey_handover_message = PrivateKeyHandover {

src/maker/handlers2.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
88
use std::sync::Arc;
99

10-
use bitcoin::Amount;
11-
1210
use super::{
1311
api2::{ConnectionState, Maker},
1412
error::MakerError,
@@ -96,14 +94,15 @@ fn handle_swap_details(
9694

9795
// Reject if there's already an active swap in progress for this connection
9896
// This prevents an attacker from resetting another taker's swap state
99-
if connection_state.swap_amount > Amount::ZERO {
100-
log::warn!(
101-
"[{}] Rejecting SwapDetails - swap already in progress with amount {}",
102-
maker.config.network_port,
103-
connection_state.swap_amount
104-
);
105-
return Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Nack)));
106-
}
97+
// [TODO] Remove this once we have a way to handle multiple swaps using swap_id
98+
// if connection_state.swap_amount > Amount::ZERO {
99+
// log::warn!(
100+
// "[{}] Rejecting SwapDetails - swap already in progress with amount {}",
101+
// maker.config.network_port,
102+
// connection_state.swap_amount
103+
// );
104+
// return Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Nack)));
105+
// }
107106

108107
// Validate swap parameters using api2
109108
maker.validate_swap_parameters(&swap_details)?;

src/maker/server2.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,12 +462,29 @@ fn handle_client_taproot(maker: &Arc<Maker>, stream: &mut TcpStream) -> Result<(
462462
}
463463
MakerToTakerMessage::SenderContractFromMaker(_) => {}
464464
MakerToTakerMessage::PrivateKeyHandover(_) => {
465-
// Swap completed successfully - remove from ongoing swaps
465+
// Swap completed successfully - remove outgoing swapcoin and ongoing swap state
466466
log::info!(
467467
"[{}] Swap completed successfully with {}, removing from ongoing swaps",
468468
maker.config.network_port,
469469
ip
470470
);
471+
472+
// Remove the outgoing swapcoin now that PrivateKeyHandover was sent
473+
let outgoing_txid = connection_state
474+
.outgoing_contract
475+
.contract_tx
476+
.compute_txid();
477+
{
478+
let mut wallet = maker.wallet.write()?;
479+
wallet.remove_outgoing_swapcoin_v2(&outgoing_txid);
480+
log::info!(
481+
"[{}] Removed outgoing swapcoin {} after PrivateKeyHandover sent",
482+
maker.config.network_port,
483+
outgoing_txid
484+
);
485+
wallet.save_to_disk()?;
486+
}
487+
471488
let mut ongoing_swaps = maker.ongoing_swap_state.lock()?;
472489
ongoing_swaps.remove(&ip);
473490
// Exit loop - swap is complete

0 commit comments

Comments
 (0)