From 950d4243e6edb9115db4865ef8be858e8371c899 Mon Sep 17 00:00:00 2001 From: yse <70684173+hydra-yse@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:53:09 +0200 Subject: [PATCH] fix: incoming Chain swap refund (#522) --- lib/core/src/chain/bitcoin.rs | 3 +- lib/core/src/chain/liquid.rs | 17 +++++--- lib/core/src/swapper/boltz/bitcoin.rs | 23 ++++++---- lib/core/src/swapper/boltz/liquid.rs | 52 +++++++++++++++++----- lib/core/src/swapper/boltz/mod.rs | 63 ++++++++++----------------- 5 files changed, 92 insertions(+), 66 deletions(-) diff --git a/lib/core/src/chain/bitcoin.rs b/lib/core/src/chain/bitcoin.rs index db5a7a3..d3ef4f9 100644 --- a/lib/core/src/chain/bitcoin.rs +++ b/lib/core/src/chain/bitcoin.rs @@ -179,7 +179,6 @@ impl BitcoinChainService for HybridBitcoinChainService { } async fn get_script_utxos(&self, script: &Script) -> Result> { - let script_pubkey = script.to_p2sh(); let utxos = self .client .script_list_unspent(script)? @@ -195,7 +194,7 @@ impl BitcoinChainService for HybridBitcoinChainService { OutPoint::new(*tx_hash, *tx_pos as u32), TxOut { value: Amount::from_sat(*value), - script_pubkey: script_pubkey.clone(), + script_pubkey: script.into(), }, )) }, diff --git a/lib/core/src/chain/liquid.rs b/lib/core/src/chain/liquid.rs index 700e5f7..6b23cb4 100644 --- a/lib/core/src/chain/liquid.rs +++ b/lib/core/src/chain/liquid.rs @@ -129,12 +129,17 @@ impl LiquidChainService for HybridLiquidChainService { } async fn get_transaction_hex(&self, txid: &Txid) -> Result> { - let url = format!("{}/tx/{}/hex", LIQUID_ESPLORA_URL, txid.to_hex()); - let response = get_with_retry(&url, 3).await?; - Ok(match response.status() { - StatusCode::OK => Some(utils::deserialize_tx_hex(&response.text().await?)?), - _ => None, - }) + match self.network { + LiquidNetwork::Mainnet => { + let url = format!("{}/tx/{}/hex", LIQUID_ESPLORA_URL, txid.to_hex()); + let response = get_with_retry(&url, 3).await?; + Ok(match response.status() { + StatusCode::OK => Some(utils::deserialize_tx_hex(&response.text().await?)?), + _ => None, + }) + } + LiquidNetwork::Testnet => Ok(self.get_transactions(&[*txid]).await?.first().cloned()), + } } async fn get_transactions(&self, txids: &[Txid]) -> Result> { diff --git a/lib/core/src/swapper/boltz/bitcoin.rs b/lib/core/src/swapper/boltz/bitcoin.rs index 4d08439..c1d1bdb 100644 --- a/lib/core/src/swapper/boltz/bitcoin.rs +++ b/lib/core/src/swapper/boltz/bitcoin.rs @@ -4,7 +4,7 @@ use boltz_client::{ bitcoin::{address::Address, Transaction}, boltz::SwapTxKind, util::secrets::Preimage, - BtcSwapScript, BtcSwapTx, Keypair, + BtcSwapTx, }; use crate::{ @@ -54,17 +54,21 @@ impl BoltzSwapper { Ok(refund_wrapper) } - #[allow(clippy::too_many_arguments)] pub(crate) fn new_btc_refund_tx( &self, - swap_id: String, - swap_script: BtcSwapScript, + swap: &ChainSwap, refund_address: &str, - refund_keypair: &Keypair, utxos: Vec, broadcast_fee_rate_sat_per_vb: f64, is_cooperative: bool, ) -> Result { + ensure_sdk!( + swap.direction == Direction::Incoming, + SdkError::Generic { + err: "Cannot create BTC refund tx for outgoing Chain swaps.".to_string() + } + ); + let address = Address::from_str(refund_address).map_err(|err| SdkError::Generic { err: format!("Could not parse address: {err:?}"), })?; @@ -83,6 +87,7 @@ impl BoltzSwapper { err: "No UTXO found".to_string(), })?; + let swap_script = swap.get_lockup_swap_script()?.as_bitcoin_script()?; let refund_tx = BtcSwapTx { kind: SwapTxKind::Refund, swap_script, @@ -90,15 +95,17 @@ impl BoltzSwapper { utxo, }; - let refund_tx_size = refund_tx.size(refund_keypair, &Preimage::new())?; + let refund_keypair = swap.get_refund_keypair()?; + let preimage = Preimage::from_str(&swap.preimage)?; + let refund_tx_size = refund_tx.size(&refund_keypair, &preimage)?; let broadcast_fees_sat = (refund_tx_size as f64 * broadcast_fee_rate_sat_per_vb) as u64; let cooperative = match is_cooperative { - true => self.get_cooperative_details(swap_id, None, None), + true => self.get_cooperative_details(swap.id.clone(), None, None), false => None, }; - let signed_tx = refund_tx.sign_refund(refund_keypair, broadcast_fees_sat, cooperative)?; + let signed_tx = refund_tx.sign_refund(&refund_keypair, broadcast_fees_sat, cooperative)?; Ok(signed_tx) } diff --git a/lib/core/src/swapper/boltz/liquid.rs b/lib/core/src/swapper/boltz/liquid.rs index a106071..7dc5eb6 100644 --- a/lib/core/src/swapper/boltz/liquid.rs +++ b/lib/core/src/swapper/boltz/liquid.rs @@ -4,13 +4,17 @@ use boltz_client::{ boltz::SwapTxKind, elements::Transaction, util::{liquid_genesis_hash, secrets::Preimage}, - Amount, Bolt11Invoice, ElementsAddress as Address, Keypair, LBtcSwapScript, LBtcSwapTx, + Amount, Bolt11Invoice, ElementsAddress as Address, LBtcSwapTx, }; use log::info; use crate::{ + ensure_sdk, error::{PaymentError, SdkError}, - prelude::{ChainSwap, Direction, ReceiveSwap, Swap, Utxo, LOWBALL_FEE_RATE_SAT_PER_VBYTE}, + prelude::{ + ChainSwap, Direction, LiquidNetwork, ReceiveSwap, Swap, Utxo, + LOWBALL_FEE_RATE_SAT_PER_VBYTE, STANDARD_FEE_RATE_SAT_PER_VBYTE, + }, }; use super::BoltzSwapper; @@ -92,8 +96,10 @@ impl BoltzSwapper { } fn calculate_refund_fees(&self, refund_tx_size: usize) -> u64 { - // Testnet not supports lowball as well, see https://blog.blockstream.com/elements-23-2-3-discounted-fees-for-confidential-transactions/ - let fee_rate = LOWBALL_FEE_RATE_SAT_PER_VBYTE; + let fee_rate = match self.config.network { + LiquidNetwork::Mainnet => LOWBALL_FEE_RATE_SAT_PER_VBYTE, + LiquidNetwork::Testnet => STANDARD_FEE_RATE_SAT_PER_VBYTE, + }; (refund_tx_size as f64 * fee_rate).ceil() as u64 } @@ -147,13 +153,39 @@ impl BoltzSwapper { pub(crate) fn new_lbtc_refund_tx( &self, - swap_id: String, - swap_script: LBtcSwapScript, + swap: &Swap, refund_address: &str, - refund_keypair: &Keypair, utxos: Vec, is_cooperative: bool, ) -> Result { + let (swap_script, refund_keypair, preimage) = match swap { + Swap::Chain(swap) => { + ensure_sdk!( + swap.direction == Direction::Outgoing, + SdkError::Generic { + err: "Cannot create LBTC refund tx for incoming Chain swaps".to_string() + } + ); + + ( + swap.get_lockup_swap_script()?.as_liquid_script()?, + swap.get_refund_keypair()?, + Preimage::from_str(&swap.preimage)?, + ) + } + Swap::Send(swap) => ( + swap.get_swap_script()?, + swap.get_refund_keypair()?, + Preimage::new(), + ), + Swap::Receive(_) => { + return Err(SdkError::Generic { + err: "Cannot create LBTC refund tx for Receive swaps.".to_string(), + }); + } + }; + let swap_id = swap.id(); + let address = Address::from_str(refund_address).map_err(|err| SdkError::Generic { err: format!("Could not parse address: {err:?}"), })?; @@ -176,16 +208,16 @@ impl BoltzSwapper { genesis_hash, }; - let refund_tx_size = refund_tx.size(refund_keypair, &Preimage::new())?; + let refund_tx_size = refund_tx.size(&refund_keypair, &preimage)?; let broadcast_fees_sat = self.calculate_refund_fees(refund_tx_size); let cooperative = match is_cooperative { - true => self.get_cooperative_details(swap_id, None, None), + true => self.get_cooperative_details(swap_id.clone(), None, None), false => None, }; let signed_tx = refund_tx.sign_refund( - refund_keypair, + &refund_keypair, Amount::from_sat(broadcast_fees_sat), cooperative, )?; diff --git a/lib/core/src/swapper/boltz/mod.rs b/lib/core/src/swapper/boltz/mod.rs index dae6c3a..56d772b 100644 --- a/lib/core/src/swapper/boltz/mod.rs +++ b/lib/core/src/swapper/boltz/mod.rs @@ -329,60 +329,43 @@ impl Swapper for BoltzSwapper { broadcast_fee_rate_sat_per_vb: Option, is_cooperative: bool, ) -> Result { + let swap_id = swap.id(); let refund_address = &refund_address.to_string(); - let tx = match &swap { - Swap::Chain(swap) => { - let swap_id = swap.id.clone(); - let swap_script = swap.get_lockup_swap_script()?; - let refund_keypair = swap.get_refund_keypair()?; - match swap.direction { - Direction::Incoming => { - let Some(broadcast_fee_rate_sat_per_vb) = broadcast_fee_rate_sat_per_vb - else { - return Err(PaymentError::Generic { + let tx = match &swap { + Swap::Chain(chain_swap) => match chain_swap.direction { + Direction::Incoming => { + let Some(broadcast_fee_rate_sat_per_vb) = broadcast_fee_rate_sat_per_vb else { + return Err(PaymentError::Generic { err: format!("No broadcast fee rate provided when refunding incoming Chain Swap {swap_id}") }); - }; + }; - Transaction::Bitcoin(self.new_btc_refund_tx( - swap_id, - swap_script.as_bitcoin_script()?, - refund_address, - &refund_keypair, - utxos, - broadcast_fee_rate_sat_per_vb, - is_cooperative, - )?) - } - Direction::Outgoing => Transaction::Liquid(self.new_lbtc_refund_tx( - swap_id, - swap_script.as_liquid_script()?, + Transaction::Bitcoin(self.new_btc_refund_tx( + chain_swap, refund_address, - &refund_keypair, utxos, + broadcast_fee_rate_sat_per_vb, is_cooperative, - )?), + )?) } - } - Swap::Send(swap) => { - let swap_script = swap.get_swap_script()?; - let refund_keypair = swap.get_refund_keypair()?; - - Transaction::Liquid(self.new_lbtc_refund_tx( - swap.id.clone(), - swap_script, + Direction::Outgoing => Transaction::Liquid(self.new_lbtc_refund_tx( + &swap, refund_address, - &refund_keypair, utxos, is_cooperative, - )?) - } - Swap::Receive(swap) => { + )?), + }, + Swap::Send(_) => Transaction::Liquid(self.new_lbtc_refund_tx( + &swap, + refund_address, + utxos, + is_cooperative, + )?), + Swap::Receive(_) => { return Err(PaymentError::Generic { err: format!( - "Failed to create refund tx for Receive swap {}: invalid swap type", - swap.id + "Failed to create refund tx for Receive swap {swap_id}: invalid swap type", ), }); }