Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(legacy_swap): check for existing maker/taker payment before timeout #2283

Merged
merged 10 commits into from
Jan 9, 2025
39 changes: 21 additions & 18 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,6 +793,17 @@ impl MakerSwap {
Ok((Some(MakerSwapCommand::SendPayment), swap_events))
}

async fn setup_maker_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;
Expand All @@ -803,6 +814,7 @@ impl MakerSwap {
let unique_data = self.unique_swap_data();
let payment_instructions = self.r().payment_instructions.clone();
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);

// Look for previously sent maker payment in case of restart
let maybe_existing_payment = match self
Expand All @@ -828,7 +840,7 @@ impl MakerSwap {
},
};

// Skip timeout check if payment was already sent
// 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();
Expand All @@ -840,22 +852,13 @@ impl MakerSwap {
}

// Set up watcher reward if enabled
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 watcher_reward = match self.setup_maker_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
Expand Down
187 changes: 100 additions & 87 deletions mm2src/mm2_main/src/lp_swap/taker_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::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage,
FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr,
RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapTxTypeWithSecretHash,
TradeFee, TradePreimageValue, ValidatePaymentInput, WaitForHTLCTxSpendArgs};
TradeFee, TradePreimageValue, TransactionEnum, ValidatePaymentInput, WaitForHTLCTxSpendArgs, WatcherReward};
use common::executor::Timer;
use common::log::{debug, error, info, warn};
use common::{bits256, now_ms, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY};
Expand Down Expand Up @@ -1506,6 +1506,94 @@ impl TakerSwap {
}
}

async fn setup_watcher_reward(&self, taker_payment_lock: u64) -> Result<Option<WatcherReward>, String> {
shamardy marked this conversation as resolved.
Show resolved Hide resolved
if !self.r().watcher_reward {
return Ok(None);
}

let reward_amount = self.r().reward_amount.clone();
self.taker_coin
.get_taker_watcher_reward(
&self.maker_coin,
Some(self.taker_amount.clone().into()),
Some(self.maker_amount.clone().into()),
reward_amount,
taker_payment_lock,
)
.await
.map(Some)
.map_err(|err| ERRL!("Watcher reward error: {}", err.to_string()))
}

async fn process_watcher_logic(&self, transaction: &TransactionEnum) -> Option<TakerSwapEvent> {
mariocynicys marked this conversation as resolved.
Show resolved Hide resolved
let watchers_enabled_and_supported = self.ctx.use_watchers()
&& self.taker_coin.is_supported_by_watchers()
&& self.maker_coin.is_supported_by_watchers();

if !watchers_enabled_and_supported {
return None;
}

let maker_payment_spend_preimage_fut = self.maker_coin.create_maker_payment_spend_preimage(
&self.r().maker_payment.as_ref().unwrap().tx_hex,
self.maker_payment_lock.load(Ordering::Relaxed),
self.r().other_maker_coin_htlc_pub.as_slice(),
&self.r().secret_hash.0,
&self.unique_swap_data()[..],
);

let time_lock = match std::env::var("USE_TEST_LOCKTIME") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not allow this test locktime only if enabled by a cfg feature?
(I guess we tend to make all test code cfg enabled)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed USE_TEST_LOCKTIME occurrences in swaps code completely, and from the whole codebase. Took me some time to make the tests work again but this should make the legacy swap code cleaner. We should look into removing all other env vars in swaps code when we have the time.

Ok(_) => self.r().data.started_at,
Err(_) => self.r().data.taker_payment_lock,
};

let taker_payment_refund_preimage_fut = self.taker_coin.create_taker_payment_refund_preimage(
&transaction.tx_hex(),
time_lock,
&*self.r().other_taker_coin_htlc_pub,
&self.r().secret_hash.0,
&self.r().data.taker_coin_swap_contract_address,
&self.unique_swap_data(),
);

match try_join(
maker_payment_spend_preimage_fut.compat(),
taker_payment_refund_preimage_fut.compat(),
)
.await
{
Ok((maker_payment_spend, taker_payment_refund)) => {
let watcher_data = self.create_watcher_data(
transaction.tx_hash_as_bytes().into_vec(),
maker_payment_spend.tx_hex(),
taker_payment_refund.tx_hex(),
);
let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data);

let htlc_keypair = self.taker_coin.derive_htlc_key_pair(&self.unique_swap_data());
broadcast_swap_message(
&self.ctx,
watcher_topic(&self.r().data.taker_coin),
swpmsg_watcher,
&Some(htlc_keypair),
);

info!("{}", WATCHER_MESSAGE_SENT_LOG);
Some(TakerSwapEvent::WatcherMessageSent(
Some(maker_payment_spend.tx_hex()),
Some(taker_payment_refund.tx_hex()),
))
},
Err(e) => {
error!(
"The watcher message could not be sent, error creating at least one of the preimages: {}",
e.get_plain_text_format()
);
None
},
}
}

async fn send_taker_payment(&self) -> Result<(Option<TakerSwapCommand>, Vec<TakerSwapEvent>), String> {
#[cfg(test)]
if self.fail_at == Some(FailAt::TakerPayment) {
Expand Down Expand Up @@ -1548,7 +1636,7 @@ impl TakerSwap {
},
};

// Skip timeout check if payment was already sent
// 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.maker_payment_wait;
let now = now_sec();
Expand All @@ -1559,31 +1647,14 @@ impl TakerSwap {
}
}

// Set up watcher reward if enabled
let reward_amount = self.r().reward_amount.clone();
let watcher_reward = if self.r().watcher_reward {
match self
.taker_coin
.get_taker_watcher_reward(
&self.maker_coin,
Some(self.taker_amount.clone().into()),
Some(self.maker_amount.clone().into()),
reward_amount,
taker_payment_lock,
)
.await
{
Ok(reward) => Some(reward),
Err(err) => {
return Ok((Some(TakerSwapCommand::Finish), vec![
TakerSwapEvent::TakerPaymentTransactionFailed(
ERRL!("Watcher reward error: {}", err.to_string()).into(),
),
]))
},
}
} else {
None
// Set up watcher reward if enable
let watcher_reward = match self.setup_watcher_reward(taker_payment_lock).await {
Ok(reward) => reward,
Err(err) => {
return Ok((Some(TakerSwapCommand::Finish), vec![
TakerSwapEvent::TakerPaymentTransactionFailed(err.into()),
]));
},
};

// Use existing payment or create new one
Expand Down Expand Up @@ -1635,66 +1706,8 @@ impl TakerSwap {
let mut swap_events = vec![TakerSwapEvent::TakerPaymentSent(tx_ident)];

// Process watcher logic if enabled and supported by both coins
laruh marked this conversation as resolved.
Show resolved Hide resolved
if self.ctx.use_watchers()
&& self.taker_coin.is_supported_by_watchers()
&& self.maker_coin.is_supported_by_watchers()
{
let maker_payment_spend_preimage_fut = self.maker_coin.create_maker_payment_spend_preimage(
&self.r().maker_payment.as_ref().unwrap().tx_hex,
self.maker_payment_lock.load(Ordering::Relaxed),
self.r().other_maker_coin_htlc_pub.as_slice(),
&self.r().secret_hash.0,
&self.unique_swap_data()[..],
);

let time_lock = match std::env::var("USE_TEST_LOCKTIME") {
Ok(_) => self.r().data.started_at,
Err(_) => self.r().data.taker_payment_lock,
};

let taker_payment_refund_preimage_fut = self.taker_coin.create_taker_payment_refund_preimage(
&transaction.tx_hex(),
time_lock,
&*self.r().other_taker_coin_htlc_pub,
&self.r().secret_hash.0,
&self.r().data.taker_coin_swap_contract_address,
&self.unique_swap_data(),
);

match try_join(
maker_payment_spend_preimage_fut.compat(),
taker_payment_refund_preimage_fut.compat(),
)
.await
{
Ok((maker_payment_spend, taker_payment_refund)) => {
// Prepare and broadcast watcher message
let watcher_data = self.create_watcher_data(
transaction.tx_hash_as_bytes().into_vec(),
maker_payment_spend.tx_hex(),
taker_payment_refund.tx_hex(),
);
let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data);

let htlc_keypair = self.taker_coin.derive_htlc_key_pair(&self.unique_swap_data());
broadcast_swap_message(
&self.ctx,
watcher_topic(&self.r().data.taker_coin),
swpmsg_watcher,
&Some(htlc_keypair),
);

swap_events.push(TakerSwapEvent::WatcherMessageSent(
Some(maker_payment_spend.tx_hex()),
Some(taker_payment_refund.tx_hex()),
));
info!("{}", WATCHER_MESSAGE_SENT_LOG);
},
Err(e) => error!(
"The watcher message could not be sent, error creating at least one of the preimages: {}",
e.get_plain_text_format()
),
}
if let Some(watcher_event) = self.process_watcher_logic(&transaction).await {
swap_events.push(watcher_event);
}

Ok((Some(TakerSwapCommand::WaitForTakerPaymentSpend), swap_events))
Expand Down
Loading