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
153 changes: 85 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,107 @@ 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()),
]));
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;
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_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
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
Loading
Loading