Skip to content

Commit

Permalink
fix(legacy_swap): check for existing maker/taker payment before timeo…
Browse files Browse the repository at this point in the history
…ut (#2283)

This commit moves payment existence check in maker_payment/send_taker_payment before timeout validation and skips timeout if payment is already sent, as the taker swap should proceed to waiting for maker to spend the taker payment.
  • Loading branch information
shamardy authored Jan 9, 2025
1 parent 1908a2e commit 07416d1
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 316 deletions.
10 changes: 5 additions & 5 deletions mm2src/mm2_main/src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
use uuid::Uuid;

#[cfg(feature = "custom-swap-locktime")]
#[cfg(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests"))]
use std::sync::atomic::{AtomicU64, Ordering};

mod check_balance;
Expand Down Expand Up @@ -422,13 +422,13 @@ async fn recv_swap_msg<T>(
/// in order to give different and/or heavy communication channels a chance.
const BASIC_COMM_TIMEOUT: u64 = 90;

#[cfg(not(feature = "custom-swap-locktime"))]
#[cfg(not(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests")))]
/// Default atomic swap payment locktime, in seconds.
/// Maker sends payment with LOCKTIME * 2
/// Taker sends payment with LOCKTIME
const PAYMENT_LOCKTIME: u64 = 3600 * 2 + 300 * 2;

#[cfg(feature = "custom-swap-locktime")]
#[cfg(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests"))]
/// Default atomic swap payment locktime, in seconds.
/// Maker sends payment with LOCKTIME * 2
/// Taker sends payment with LOCKTIME
Expand All @@ -437,9 +437,9 @@ pub(crate) static PAYMENT_LOCKTIME: AtomicU64 = AtomicU64::new(super::CUSTOM_PAY
#[inline]
/// Returns `PAYMENT_LOCKTIME`
pub fn get_payment_locktime() -> u64 {
#[cfg(not(feature = "custom-swap-locktime"))]
#[cfg(not(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests")))]
return PAYMENT_LOCKTIME;
#[cfg(feature = "custom-swap-locktime")]
#[cfg(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests"))]
PAYMENT_LOCKTIME.load(Ordering::Relaxed)
}

Expand Down
161 changes: 93 additions & 68 deletions mm2src/mm2_main/src/lp_swap/maker_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use coins::lp_price::fetch_swap_coins_price;
use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin,
MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs,
SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapTxTypeWithSecretHash, TradeFee,
TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput};
TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput, WatcherReward};
use common::log::{debug, error, info, warn};
use common::{bits256, executor::Timer, now_ms, DEX_FEE_ADDR_RAW_PUBKEY};
use common::{now_sec, wait_until_sec};
Expand Down Expand Up @@ -793,90 +793,115 @@ impl MakerSwap {
Ok((Some(MakerSwapCommand::SendPayment), swap_events))
}

async fn maker_payment(&self) -> Result<(Option<MakerSwapCommand>, Vec<MakerSwapEvent>), String> {
let lock_duration = self.r().data.lock_duration;
let timeout = self.r().data.started_at + lock_duration / 3;
let now = now_sec();
if now > timeout {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(ERRL!("Timeout {} > {}", now, timeout).into()),
]));
/// Sets up the watcher reward for the maker's payment in the swap.
///
/// The reward mainly serves as compensation to watchers for the mining fees
/// paid to execute the transactions.
///
/// The reward configuration depends on the specific requirements of the coins
/// involved in the swap.
/// Some coins may not support watcher rewards at all.
async fn setup_watcher_reward(&self, wait_maker_payment_until: u64) -> Result<Option<WatcherReward>, String> {
if !self.r().watcher_reward {
return Ok(None);
}

self.maker_coin
.get_maker_watcher_reward(&self.taker_coin, self.watcher_reward_amount(), wait_maker_payment_until)
.await
.map_err(|err| err.into_inner().to_string())
}

async fn maker_payment(&self) -> Result<(Option<MakerSwapCommand>, Vec<MakerSwapEvent>), String> {
// Extract values from lock before async operations
let lock_duration = self.r().data.lock_duration;
let maker_payment_lock = self.r().data.maker_payment_lock;
let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub;
let secret_hash = self.secret_hash();
let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone();
let unique_data = self.unique_swap_data();
let payment_instructions = self.r().payment_instructions.clone();
let transaction_f = self.maker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs {
time_lock: maker_payment_lock,
other_pub: &*other_maker_coin_htlc_pub,
secret_hash: secret_hash.as_slice(),
search_from_block: self.r().data.maker_coin_start_block,
swap_contract_address: &maker_coin_swap_contract_address,
swap_unique_data: &unique_data,
amount: &self.maker_amount,
payment_instructions: &payment_instructions,
});

let maker_coin_start_block = self.r().data.maker_coin_start_block;
let wait_maker_payment_until = wait_for_maker_payment_conf_until(self.r().data.started_at, lock_duration);
let watcher_reward = if self.r().watcher_reward {
match self
.maker_coin
.get_maker_watcher_reward(&self.taker_coin, self.watcher_reward_amount(), wait_maker_payment_until)
.await
{
Ok(reward) => reward,
Err(err) => {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()),
]))
},
}
} else {
None
};

let transaction = match transaction_f.await {
Ok(res) => match res {
Some(tx) => tx,
None => {
let payment = self
.maker_coin
.send_maker_payment(SendPaymentArgs {
time_lock_duration: lock_duration,
time_lock: maker_payment_lock,
other_pubkey: &*other_maker_coin_htlc_pub,
secret_hash: secret_hash.as_slice(),
amount: self.maker_amount.clone(),
swap_contract_address: &maker_coin_swap_contract_address,
swap_unique_data: &unique_data,
payment_instructions: &payment_instructions,
watcher_reward,
wait_for_confirmation_until: wait_maker_payment_until,
})
.await;

match payment {
Ok(t) => t,
Err(err) => {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(
ERRL!("{}", err.get_plain_text_format()).into(),
),
]));
},
}
},
},
// Look for previously sent maker payment in case of restart
let maybe_existing_payment = match self
.maker_coin
.check_if_my_payment_sent(CheckIfMyPaymentSentArgs {
time_lock: maker_payment_lock,
other_pub: &*other_maker_coin_htlc_pub,
secret_hash: secret_hash.as_slice(),
search_from_block: maker_coin_start_block,
swap_contract_address: &maker_coin_swap_contract_address,
swap_unique_data: &unique_data,
amount: &self.maker_amount,
payment_instructions: &payment_instructions,
})
.await
{
Ok(Some(tx)) => Some(tx),
Ok(None) => None,
Err(e) => {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(ERRL!("{}", e).into()),
]))
},
};

// If the payment is not yet sent, make sure we didn't miss the deadline for sending it.
if maybe_existing_payment.is_none() {
let timeout = self.r().data.started_at + lock_duration / 3;
let now = now_sec();
if now > timeout {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(ERRL!("Timeout {} > {}", now, timeout).into()),
]));
}
}

// Set up watcher reward if enabled
let watcher_reward = match self.setup_watcher_reward(wait_maker_payment_until).await {
Ok(reward) => reward,
Err(err) => {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(err.into()),
]))
},
};

// Use existing payment or create new one
let transaction = match maybe_existing_payment {
Some(tx) => tx,
None => {
match self
.maker_coin
.send_maker_payment(SendPaymentArgs {
time_lock_duration: lock_duration,
time_lock: maker_payment_lock,
other_pubkey: &*other_maker_coin_htlc_pub,
secret_hash: secret_hash.as_slice(),
amount: self.maker_amount.clone(),
swap_contract_address: &maker_coin_swap_contract_address,
swap_unique_data: &unique_data,
payment_instructions: &payment_instructions,
watcher_reward,
wait_for_confirmation_until: wait_maker_payment_until,
})
.await
{
Ok(t) => t,
Err(err) => {
return Ok((Some(MakerSwapCommand::Finish), vec![
MakerSwapEvent::MakerPaymentTransactionFailed(
ERRL!("{}", err.get_plain_text_format()).into(),
),
]));
},
}
},
};

// Build transaction identifier and prepare events
let tx_hash = transaction.tx_hash_as_bytes();
info!("{}: Maker payment tx {:02x}", MAKER_PAYMENT_SENT_LOG, tx_hash);

Expand Down
31 changes: 13 additions & 18 deletions mm2src/mm2_main/src/lp_swap/swap_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,7 @@ impl State for ValidateTakerPayment {
let validate_input = WatcherValidatePaymentInput {
payment_tx: taker_payment_hex.clone(),
taker_payment_refund_preimage: watcher_ctx.data.taker_payment_refund_preimage.clone(),
time_lock: match std::env::var("USE_TEST_LOCKTIME") {
Ok(_) => watcher_ctx.data.swap_started_at,
Err(_) => watcher_ctx.taker_locktime(),
},
time_lock: watcher_ctx.taker_locktime(),
taker_pub: watcher_ctx.verified_pub.clone(),
maker_pub: watcher_ctx.data.maker_pub.clone(),
secret_hash: watcher_ctx.data.secret_hash.clone(),
Expand Down Expand Up @@ -451,20 +448,18 @@ impl State for RefundTakerPayment {

async fn on_changed(self: Box<Self>, watcher_ctx: &mut WatcherStateMachine) -> StateResult<WatcherStateMachine> {
debug!("Watcher refund taker payment");
if std::env::var("USE_TEST_LOCKTIME").is_err() {
loop {
match watcher_ctx
.taker_coin
.can_refund_htlc(watcher_ctx.taker_locktime())
.await
{
Ok(CanRefundHtlc::CanRefundNow) => break,
Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await,
Err(e) => {
error!("Error {} on can_refund_htlc, retrying in 30 seconds", e);
Timer::sleep(30.).await;
},
}
loop {
match watcher_ctx
.taker_coin
.can_refund_htlc(watcher_ctx.taker_locktime())
.await
{
Ok(CanRefundHtlc::CanRefundNow) => break,
Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await,
Err(e) => {
error!("Error {} on can_refund_htlc, retrying in 30 seconds", e);
Timer::sleep(30.).await;
},
}
}

Expand Down
11 changes: 2 additions & 9 deletions mm2src/mm2_main/src/lp_swap/taker_restart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,7 @@ pub async fn check_taker_payment_spend(swap: &TakerSwap) -> Result<Option<FoundS
let other_taker_coin_htlc_pub = swap.r().other_taker_coin_htlc_pub;
let taker_coin_start_block = swap.r().data.taker_coin_start_block;
let taker_coin_swap_contract_address = swap.r().data.taker_coin_swap_contract_address.clone();

let taker_payment_lock = match std::env::var("USE_TEST_LOCKTIME") {
Ok(_) => swap.r().data.started_at,
Err(_) => swap.r().data.taker_payment_lock,
};
let taker_payment_lock = swap.r().data.taker_payment_lock;
let secret_hash = swap.r().secret_hash.0.clone();
let unique_data = swap.unique_swap_data();
let watcher_reward = swap.r().watcher_reward;
Expand Down Expand Up @@ -223,10 +219,7 @@ pub async fn add_taker_payment_refunded_by_watcher_event(
) -> Result<TakerSwapCommand, String> {
let other_maker_coin_htlc_pub = swap.r().other_maker_coin_htlc_pub;
let taker_coin_swap_contract_address = swap.r().data.taker_coin_swap_contract_address.clone();
let taker_payment_lock = match std::env::var("USE_TEST_LOCKTIME") {
Ok(_) => swap.r().data.started_at,
Err(_) => swap.r().data.taker_payment_lock,
};
let taker_payment_lock = swap.r().data.taker_payment_lock;
let secret_hash = swap.r().secret_hash.0.clone();

let validate_input = ValidateWatcherSpendInput {
Expand Down
Loading

0 comments on commit 07416d1

Please sign in to comment.