Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/bin/taker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use log::LevelFilter;
use serde_json::{json, to_string_pretty};
use std::{path::PathBuf, str::FromStr};

#[cfg(feature = "integration-test")]
use coinswap::taker::api2::TakerBehavior as TaprootTakerBehavior;
#[cfg(feature = "integration-test")]
use coinswap::taker::TakerBehavior;

Expand Down Expand Up @@ -200,6 +202,8 @@ fn main() -> Result<(), TakerError> {
args.tor_auth,
args.zmq,
None,
#[cfg(feature = "integration-test")]
TaprootTakerBehavior::Normal,
)?;
taproot_taker.recover_from_swap()?;
}
Expand All @@ -214,6 +218,8 @@ fn main() -> Result<(), TakerError> {
args.tor_auth,
args.zmq,
None,
#[cfg(feature = "integration-test")]
TaprootTakerBehavior::Normal,
)?;

let taproot_swap_params = coinswap::taker::api2::SwapParams {
Expand Down
16 changes: 16 additions & 0 deletions src/maker/api2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub enum MakerBehavior {
/// Close connection after sweeping incoming contract but before completing handover
/// This allows maker to recover their coins but forces taker to recover via hashlock/timelock
CloseAfterSweep,
/// Close connection after accepting Swap offer from taker and sending it AckResponse message.
CloseAfterAckResponse,
}

/// Interval for health checks on a stable RPC connection with bitcoind.
Expand Down Expand Up @@ -925,6 +927,20 @@ impl Maker {
.contract_tx
.compute_txid();

for (vout, _) in connection_state
.incoming_contract
.contract_tx
.output
.iter()
.enumerate()
{
let outpoint = OutPoint {
txid: incoming_txid,
vout: vout as u32,
};
self.watch_service.unwatch(outpoint);
}

// Record the swept coin to track swap balance
let output_scriptpubkey = spending_tx.output[0].script_pubkey.clone();
wallet
Expand Down
12 changes: 12 additions & 0 deletions src/maker/handlers2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ fn handle_swap_details(
our_fee
);

// Check for CloseAfterAckResponse behavior
#[cfg(feature = "integration-test")]
if maker.behavior == super::api2::MakerBehavior::CloseAfterAckResponse {
log::warn!(
"[{}] Maker behavior: CloseAfterAckResponse - Closing connection after sending Ack Response message to taker",
maker.config.network_port
);
return Err(MakerError::General(
"Maker closing connection after sending AckResponse to taker (test behavior)",
));
}

// Send acknowledgment
Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Ack)))
}
Expand Down
113 changes: 100 additions & 13 deletions src/taker/api2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::error::TakerError;
use crate::{
protocol::{
contract2::{
Expand Down Expand Up @@ -52,7 +53,20 @@ use std::{
collections::HashSet, net::TcpStream, path::PathBuf, sync::mpsc, thread, time::Duration,
};

use super::error::TakerError;
/// Represents different behaviors taker can have during the swap.
/// Used for testing various possible scenarios that can happen during a swap.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(feature = "integration-test")]
pub enum TakerBehavior {
/// Normal behaviour
Normal,
/// Close connection after receiving AckResponse message from the maker. (no outgoing/incoming contract created)
CloseAtAckResponse,
/// Close connection after sending SendersContract (outgoing contract created)
CloseAtSendersContract,
/// Close connection after receiving SendersContract from maker (outgoing contract created).
CloseAtSendersContractFromMaker,
}

/// Represents how a taproot contract output was spent
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -201,6 +215,8 @@ pub struct Taker {
ongoing_swap_state: OngoingSwapState,
watch_service: WatchService,
offer_sync_handle: OfferSyncHandle,
#[cfg(feature = "integration-test")]
behavior: TakerBehavior,
}

impl Drop for Taker {
Expand All @@ -219,6 +235,7 @@ impl Drop for Taker {

impl Taker {
/// Initialize a new Taker instance
#[allow(clippy::too_many_arguments)]
pub fn init(
data_dir: Option<PathBuf>,
wallet_file_name: Option<String>,
Expand All @@ -227,6 +244,7 @@ impl Taker {
tor_auth_password: Option<String>,
zmq_addr: String,
password: Option<String>,
#[cfg(feature = "integration-test")] behavior: TakerBehavior,
) -> Result<Taker, TakerError> {
let data_dir = data_dir.unwrap_or_else(get_taker_dir);

Expand Down Expand Up @@ -302,6 +320,8 @@ impl Taker {
ongoing_swap_state: OngoingSwapState::default(),
watch_service,
offer_sync_handle,
#[cfg(feature = "integration-test")]
behavior,
})
}

Expand Down Expand Up @@ -348,6 +368,16 @@ impl Taker {
self.choose_makers_for_swap(swap_params)?;
self.setup_contract_keys_and_scripts()?;

#[cfg(feature = "integration-test")]
{
if self.behavior == TakerBehavior::CloseAtAckResponse {
log::error!(
"Dropping Swap Process after receiving Ack Response message from maker"
);
return Err(TakerError::General("Taker Dropping after receiving AckResponse message from maker. (Test behavior)".to_string(),));
}
}

let outgoing_signed_contract_transactions = self.create_outgoing_contract_transactions()?;
let tx = match outgoing_signed_contract_transactions.first() {
Some(tx) => tx,
Expand Down Expand Up @@ -380,7 +410,6 @@ impl Taker {
for tx in &outgoing_signed_contract_transactions {
self.wallet.wait_for_tx_confirmation(tx.compute_txid())?;
}

match self
.negotiate_with_makers_and_coordinate_sweep(&outgoing_signed_contract_transactions)
{
Expand All @@ -391,6 +420,7 @@ impl Taker {

// Store swap state before reset for report generation
let prereset_swapstate = self.ongoing_swap_state.clone();
self.ongoing_swap_state = OngoingSwapState::default();

// Sync wallet and generate report
self.wallet.sync_and_save()?;
Expand Down Expand Up @@ -763,19 +793,20 @@ impl Taker {
};

let msg = TakerToMakerMessage::SwapDetails(swap_details);
let response = self.send_to_maker_and_get_response(&suitable_maker.address, msg)?;
let response = self.send_to_maker_and_get_response(&suitable_maker.address, msg);
match response {
MakerToTakerMessage::AckResponse(AckResponse::Ack) => {
Ok(MakerToTakerMessage::AckResponse(AckResponse::Ack)) => {
log::info!("Received AckResponse from maker: {:?}", suitable_maker);
self.ongoing_swap_state
.chosen_makers
.push(suitable_maker.clone());
}
MakerToTakerMessage::AckResponse(AckResponse::Nack) => {
Ok(MakerToTakerMessage::AckResponse(AckResponse::Nack)) => {
log::warn!("Maker {:?} did not accept the swap request", suitable_maker);
continue;
}
_ => {
self.offerbook.add_bad_maker(suitable_maker);
log::warn!("Received unexpected message from maker: {:?}", response);
continue;
}
Expand Down Expand Up @@ -872,6 +903,10 @@ impl Taker {

suitable_makers
}
/// Get all the bad makers
pub fn get_bad_makers(&self) -> Vec<OfferAndAddress> {
self.offerbook.get_bad_makers(&MakerProtocol::Taproot)
}

/// Setup contract keys and scripts for the swap
fn setup_contract_keys_and_scripts(&mut self) -> Result<(), TakerError> {
Expand Down Expand Up @@ -1052,13 +1087,24 @@ impl Taker {
};

let msg = TakerToMakerMessage::SendersContract(senders_contract.clone());
let response = self.send_to_maker_and_get_response(&first_maker.address, msg)?;
let response = self.send_to_maker_and_get_response(&first_maker.address, msg);
#[cfg(feature = "integration-test")]
{
if self.behavior == TakerBehavior::CloseAtSendersContract {
log::error!("Dropping connection after sending SendersContract to Maker");
return Err(TakerError::General(
"Taker dropping of after sending Senders Contract to Maker (test behavior) "
.to_string(),
));
}
}

match response {
MakerToTakerMessage::SenderContractFromMaker(incoming_contract) => {
Ok(MakerToTakerMessage::SenderContractFromMaker(incoming_contract)) => {
self.forward_contracts_and_coordinate_sweep(incoming_contract)?;
}
_ => {
self.offerbook.add_bad_maker(first_maker);
return Err(TakerError::General(
"Unexpected response from first maker".to_string(),
));
Expand Down Expand Up @@ -1124,11 +1170,21 @@ impl Taker {
};

let forward_msg = TakerToMakerMessage::SendersContract(forward_contract);
let maker_response =
self.send_to_maker_and_get_response(&maker.address, forward_msg)?;
let maker_response = self.send_to_maker_and_get_response(&maker.address, forward_msg);

match maker_response {
MakerToTakerMessage::SenderContractFromMaker(maker_contract) => {
Ok(MakerToTakerMessage::SenderContractFromMaker(maker_contract)) => {
#[cfg(feature = "integration-test")]
{
if self.behavior == TakerBehavior::CloseAtSendersContractFromMaker {
log::error!(
"Dropping connection after receiving Sender Contract from Maker"
);
return Err(TakerError::General(
"Taker dropping of after receiving Senders Contract from Maker (test behavior) ".to_string(),));
}
}

if maker_index == maker_count - 1 {
// This is the last maker - store its response as final contract data
self.store_final_contract_data(&maker_contract)?;
Expand All @@ -1140,6 +1196,7 @@ impl Taker {
}
}
_ => {
self.offerbook.add_bad_maker(maker);
return Err(TakerError::General(format!(
"Unexpected response from maker {}",
maker_index
Expand Down Expand Up @@ -1272,15 +1329,28 @@ impl Taker {
});

// Send to maker and get their outgoing key in response
let response = self.send_to_maker_and_get_response(&maker_address, privkey_msg)?;

let response = self.send_to_maker_and_get_response(&maker_address, privkey_msg);
// remove taker's outgoing swapcoin since we've handed over the key
if maker_index == 0 {
let outgoing_txid = self
.ongoing_swap_state
.outgoing_contract
.contract_tx
.compute_txid();
for (vout, _) in self
.ongoing_swap_state
.outgoing_contract
.contract_tx
.output
.iter()
.enumerate()
{
let outpoint = OutPoint {
txid: outgoing_txid,
vout: vout as u32,
};
self.watch_service.unwatch(outpoint);
}
self.wallet.remove_outgoing_swapcoin_v2(&outgoing_txid);
log::info!(
"Removed taker's outgoing swapcoin {} after sending PrivateKeyHandover",
Expand All @@ -1291,7 +1361,7 @@ impl Taker {

// Extract maker's outgoing key from response
match response {
MakerToTakerMessage::PrivateKeyHandover(maker_privkey_handover) => {
Ok(MakerToTakerMessage::PrivateKeyHandover(maker_privkey_handover)) => {
let maker_outgoing_privkey = maker_privkey_handover.secret_key;

log::info!(" [Maker {}] Received outgoing private key", maker_index);
Expand All @@ -1301,6 +1371,8 @@ impl Taker {
Some(maker_outgoing_privkey);
}
_ => {
self.offerbook
.add_bad_maker(&self.ongoing_swap_state.chosen_makers[maker_index]);
return Err(TakerError::General(format!(
"Unexpected response from maker {}: expected PrivateKeyHandover",
maker_index
Expand Down Expand Up @@ -1512,6 +1584,21 @@ impl Taker {
incoming_contract_txid
);

for (vout, _) in self
.ongoing_swap_state
.incoming_contract
.contract_tx
.output
.iter()
.enumerate()
{
let outpoint = OutPoint {
txid: incoming_contract_txid,
vout: vout as u32,
};
self.watch_service.unwatch(outpoint);
}

// Remove the incoming swapcoin since we've successfully swept it
self.wallet
.remove_incoming_swapcoin_v2(&incoming_contract_txid);
Expand Down
4 changes: 3 additions & 1 deletion src/taker/offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ impl MakerOfferCandidate {
fn mark_success(&mut self, offer: Offer, protocol: MakerProtocol) {
self.offer = Some(offer);
self.protocol = Some(protocol);
self.state = MakerState::Good;
if self.state != MakerState::Bad {
self.state = MakerState::Good;
}
}

fn mark_failure(&mut self) {
Expand Down
Loading