Skip to content

Commit

Permalink
Separate verification functions and set swap Failed if underpaid
Browse files Browse the repository at this point in the history
  • Loading branch information
dangeross committed Jan 23, 2025
1 parent 6c702bd commit 0cb9ca1
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 183 deletions.
139 changes: 89 additions & 50 deletions lib/core/src/receive_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ use boltz_client::swaps::boltz::RevSwapStates;
use boltz_client::{boltz, Serialize, ToHex};
use log::{debug, error, info, warn};
use lwk_wollet::elements::secp256k1_zkp::Secp256k1;
use lwk_wollet::elements::Txid;
use lwk_wollet::elements::{Transaction, Txid};
use lwk_wollet::hashes::hex::DisplayHex;
use lwk_wollet::secp256k1::SecretKey;
use tokio::sync::{broadcast, Mutex};

use crate::chain::liquid::LiquidChainService;
use crate::model::{BlockListener, PaymentState::*};
use crate::model::{Config, PaymentTxData, PaymentType, ReceiveSwap};
use crate::prelude::{Swap, Transaction};
use crate::prelude::Swap;
use crate::{ensure_sdk, utils};
use crate::{
error::PaymentError, model::PaymentState, persist::Persister, swapper::Swapper,
Expand Down Expand Up @@ -78,19 +78,12 @@ impl ReceiveSwapHandler {
let receive_swap = self.fetch_receive_swap_by_id(id)?;

info!("Handling Receive Swap transition to {swap_state:?} for swap {id}");

if let Some(sync_state) = self.persister.get_sync_state_by_data_id(&receive_swap.id)? {
if !sync_state.is_local {
match swap_state {
// If the swap is not local (pulled from real-time sync) we do not claim twice
RevSwapStates::TransactionMempool | RevSwapStates::TransactionConfirmed => {
log::debug!("Received {swap_state:?} for non-local Receive swap {id} from status stream, skipping update.");
return Ok(());
}
_ => {}
}
}
}
// Get if the swap is local from the sync state. This allows us to verify
// the update but avoid claiming if not local.
let is_local_swap = self
.persister
.get_sync_state_by_data_id(&receive_swap.id)?
.map_or(true, |sync_state| sync_state.is_local);

match swap_state {
RevSwapStates::SwapExpired
Expand Down Expand Up @@ -128,24 +121,38 @@ impl ReceiveSwapHandler {
));
}

// looking for lockup script history to verify lockup was broadcasted
if let Err(e) = self
// Looking for lockup script history to verify lockup was broadcasted
let lockup_tx = match self
.verify_lockup_tx(&receive_swap, &transaction.id, &transaction.hex, false)
.await
{
Ok(lockup_tx) => lockup_tx,
Err(e) => {
return Err(anyhow!(
"Swapper mempool reported lockup could not be verified. txid: {}, err: {}",
transaction.id,
e
));
}
};

if let Err(e) = self
.verify_lockup_tx_amount(&receive_swap, &lockup_tx)
.await
{
// The lockup amount in the tx is underpaid compared to the expected amount
self.update_swap_info(id, Failed, None, None, None, None)?;
return Err(anyhow!(
"swapper mempool reported lockup could not be verified. txid: {}, err: {}",
"Swapper underpaid lockup amount. txid: {}, err: {}",
transaction.id,
e
));
}
info!("swapper lockup was verified");
info!("Swapper lockup was verified");

let lockup_tx_id = &transaction.id;
self.update_swap_info(id, Pending, None, Some(lockup_tx_id), None, None)?;

let lockup_tx = utils::deserialize_tx_hex(&transaction.hex)?;

// If the amount is greater than the zero-conf limit
let max_amount_sat = self.config.zero_conf_max_amount_sat();
let receiver_amount_sat = receive_swap.receiver_amount_sat;
Expand Down Expand Up @@ -181,14 +188,16 @@ impl ReceiveSwapHandler {

debug!("[Receive Swap {id}] Lockup tx fees are within acceptable range ({tx_fees} > {lower_bound_estimated_fees} sat). Proceeding with claim.");

match self.claim(id).await {
Ok(_) => {}
Err(err) => match err {
PaymentError::AlreadyClaimed => {
warn!("Funds already claimed for Receive Swap {id}")
if is_local_swap {
// Only claim a local swap
if let Err(err) = self.claim(id).await {
match err {
PaymentError::AlreadyClaimed => {
warn!("Funds already claimed for Receive Swap {id}")
}
_ => error!("Claim for Receive Swap {id} failed: {err}"),
}
_ => error!("Claim for Receive Swap {id} failed: {err}"),
},
}
}

Ok(())
Expand All @@ -205,33 +214,52 @@ impl ReceiveSwapHandler {
));
}

// looking for lockup script history to verify lockup was broadcasted and confirmed
if let Err(e) = self
// Looking for lockup script history to verify lockup was broadcasted and confirmed
let lockup_tx = match self
.verify_lockup_tx(&receive_swap, &transaction.id, &transaction.hex, true)
.await
{
Ok(lockup_tx) => lockup_tx,
Err(e) => {
return Err(anyhow!(
"Swapper reported lockup could not be verified. txid: {}, err: {}",
transaction.id,
e
));
}
};

if let Err(e) = self
.verify_lockup_tx_amount(&receive_swap, &lockup_tx)
.await
{
// The lockup amount in the tx is underpaid compared to the expected amount
self.update_swap_info(id, Failed, None, None, None, None)?;
return Err(anyhow!(
"swapper reported lockup could not be verified. txid: {}, err: {}",
"Swapper underpaid lockup amount. txid: {}, err: {}",
transaction.id,
e
));
}
info!("swapper lockup was verified, moving to claim");
info!("Swapper lockup was verified, moving to claim");

match receive_swap.claim_tx_id {
Some(claim_tx_id) => {
warn!("Claim tx for Receive Swap {id} was already broadcast: txid {claim_tx_id}")
}
None => {
self.update_swap_info(&receive_swap.id, Pending, None, None, None, None)?;
match self.claim(id).await {
Ok(_) => {}
Err(err) => match err {
PaymentError::AlreadyClaimed => {
warn!("Funds already claimed for Receive Swap {id}")

if is_local_swap {
// Only claim a local swap
if let Err(err) = self.claim(id).await {
match err {
PaymentError::AlreadyClaimed => {
warn!("Funds already claimed for Receive Swap {id}")
}
_ => error!("Claim for Receive Swap {id} failed: {err}"),
}
_ => error!("Claim for Receive Swap {id} failed: {err}"),
},
}
}
}
}
Expand Down Expand Up @@ -311,7 +339,7 @@ impl ReceiveSwapHandler {

info!("Initiating claim for Receive Swap {swap_id}");
let claim_address = self.onchain_wallet.next_unused_address().await?.to_string();
let Transaction::Liquid(claim_tx) = self
let crate::prelude::Transaction::Liquid(claim_tx) = self
.swapper
.create_claim_tx(Swap::Receive(swap.clone()), Some(claim_address))?
else {
Expand Down Expand Up @@ -397,10 +425,7 @@ impl ReceiveSwapHandler {
);
for swap in receive_swaps {
if let Err(e) = self.claim_confirmed_lockup(&swap).await {
error!(
"Error rescanning server lockup of incoming Chain Swap {}: {e:?}",
swap.id,
);
error!("Error rescanning Receive Swap {}: {e:?}", swap.id,);
}
}
Ok(())
Expand All @@ -421,8 +446,13 @@ impl ReceiveSwapHandler {
.ok_or(anyhow!("Lockup tx not found for Receive swap {swap_id}"))?
.serialize()
.to_lower_hex_string();
self.verify_lockup_tx(receive_swap, &tx_id, &tx_hex, true)
let lockup_tx = self
.verify_lockup_tx(receive_swap, &tx_id, &tx_hex, true)
.await?;
if let Err(e) = self.verify_lockup_tx_amount(receive_swap, &lockup_tx).await {
self.update_swap_info(swap_id, Failed, None, None, None, None)?;
return Err(e);
}
info!("Receive Swap {swap_id} lockup tx is confirmed");
self.claim(swap_id)
.await
Expand Down Expand Up @@ -478,20 +508,29 @@ impl ReceiveSwapHandler {
tx_id: &str,
tx_hex: &str,
verify_confirmation: bool,
) -> Result<()> {
) -> Result<Transaction> {
// Looking for lockup script history to verify lockup was broadcasted
let script = receive_swap.get_swap_script()?;
let address = script
.to_address(self.config.network.into())
.map_err(|e| anyhow!("Failed to get swap script address {e:?}"))?;
let lockup_tx = self
.liquid_chain_service
self.liquid_chain_service
.lock()
.await
.verify_tx(&address, tx_id, tx_hex, verify_confirmation)
.await?;
// Verify amount
.await
}

async fn verify_lockup_tx_amount(
&self,
receive_swap: &ReceiveSwap,
lockup_tx: &Transaction,
) -> Result<()> {
let secp = Secp256k1::new();
let script = receive_swap.get_swap_script()?;
let address = script
.to_address(self.config.network.into())
.map_err(|e| anyhow!("Failed to get swap script address {e:?}"))?;
let blinding_key = receive_swap
.get_boltz_create_response()?
.blinding_key
Expand Down
34 changes: 2 additions & 32 deletions lib/core/src/recover/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use anyhow::anyhow;
use boltz_client::ElementsAddress;
use electrum_client::GetBalanceRes;
use lwk_wollet::elements::Txid;
use lwk_wollet::secp256k1::SecretKey;
use lwk_wollet::History;
use lwk_wollet::WalletTx;

Expand Down Expand Up @@ -97,22 +96,13 @@ impl RecoveredOnchainDataSend {

pub(crate) struct RecoveredOnchainDataReceive {
pub(crate) lockup_tx_id: Option<HistoryTxId>,
pub(crate) lockup_amount_sat: Option<u64>,
pub(crate) claim_tx_id: Option<HistoryTxId>,
pub(crate) mrh_tx_id: Option<HistoryTxId>,
pub(crate) mrh_amount_sat: Option<u64>,
}

impl RecoveredOnchainDataReceive {
pub(crate) fn derive_partial_state(
&self,
expected_lockup_amount_sat: u64,
is_expired: bool,
) -> Option<PaymentState> {
let is_refundable = is_expired
|| self
.lockup_amount_sat
.is_some_and(|lockup_amount_sat| lockup_amount_sat < expected_lockup_amount_sat);
pub(crate) fn derive_partial_state(&self, is_expired: bool) -> Option<PaymentState> {
match &self.lockup_tx_id {
Some(_) => match &self.claim_tx_id {
Some(claim_tx_id) => match claim_tx_id.confirmed() {
Expand All @@ -131,7 +121,7 @@ impl RecoveredOnchainDataReceive {
},
// We have no onchain data to support deriving the state as the swap could
// potentially be Created. In this case we return None.
None => match is_refundable {
None => match is_expired {
true => Some(PaymentState::Failed),
false => None,
},
Expand Down Expand Up @@ -295,7 +285,6 @@ pub(crate) struct ReceiveSwapImmutableData {
pub(crate) swap_id: String,
pub(crate) swap_timestamp: u32,
pub(crate) timeout_block_height: u32,
pub(crate) blinding_key: SecretKey,
pub(crate) claim_script: LBtcScript,
pub(crate) mrh_script: Option<LBtcScript>,
}
Expand All @@ -314,15 +303,10 @@ impl TryFrom<ReceiveSwap> for ReceiveSwapImmutableData {
))?;

let swap_id = swap.id;
let blinding_key = create_response
.blinding_key
.and_then(|b| SecretKey::from_str(&b).ok())
.ok_or(anyhow!("Missing blinding key"))?;
Ok(ReceiveSwapImmutableData {
swap_id,
swap_timestamp: swap.created_at,
timeout_block_height: create_response.timeout_block_height,
blinding_key,
claim_script: funding_address.script_pubkey(),
mrh_script: mrh_address.map(|s| s.script_pubkey()),
})
Expand All @@ -331,7 +315,6 @@ impl TryFrom<ReceiveSwap> for ReceiveSwapImmutableData {

pub(crate) struct ReceiveSwapHistory {
pub(crate) lbtc_claim_script_history: Vec<HistoryTxId>,
pub(crate) lbtc_claim_script_txs: Vec<boltz_client::elements::Transaction>,
pub(crate) lbtc_mrh_script_history: Vec<HistoryTxId>,
}

Expand Down Expand Up @@ -552,7 +535,6 @@ impl SwapsList {
pub(crate) fn receive_histories_by_swap_id(
&self,
lbtc_script_to_history_map: &HashMap<LBtcScript, Vec<HistoryTxId>>,
lbtc_script_to_txs_map: &HashMap<LBtcScript, Vec<boltz_client::elements::Transaction>>,
) -> HashMap<String, ReceiveSwapHistory> {
let receive_swaps_by_claim_script = self.receive_swaps_by_claim_script();
let receive_swaps_by_mrh_script = self.receive_swaps_by_mrh_script();
Expand All @@ -562,11 +544,6 @@ impl SwapsList {
.iter()
.for_each(|(lbtc_script, lbtc_script_history)| {
if let Some(imm) = receive_swaps_by_claim_script.get(lbtc_script) {
let claim_script_txs = lbtc_script_to_txs_map
.get(&imm.claim_script)
.cloned()
.unwrap_or_default();

// The MRH script history filtered by the swap timeout block height
let mrh_script_history = imm
.mrh_script
Expand All @@ -586,7 +563,6 @@ impl SwapsList {
imm.swap_id.clone(),
ReceiveSwapHistory {
lbtc_claim_script_history: lbtc_script_history.clone(),
lbtc_claim_script_txs: claim_script_txs,
lbtc_mrh_script_history: mrh_script_history,
},
);
Expand All @@ -596,11 +572,6 @@ impl SwapsList {
.get(&imm.claim_script)
.cloned()
.unwrap_or_default();
let claim_script_txs = lbtc_script_to_txs_map
.get(&imm.claim_script)
.cloned()
.unwrap_or_default();

// The MRH script history filtered by the swap timeout block height
let mrh_script_history = lbtc_script_history
.iter()
Expand All @@ -611,7 +582,6 @@ impl SwapsList {
imm.swap_id.clone(),
ReceiveSwapHistory {
lbtc_claim_script_history: claim_script_history,
lbtc_claim_script_txs: claim_script_txs,
lbtc_mrh_script_history: mrh_script_history,
},
);
Expand Down
Loading

0 comments on commit 0cb9ca1

Please sign in to comment.