From c9e3c40987542305a78f65c8f98ea66c238deeda Mon Sep 17 00:00:00 2001 From: patrini32 <171664803+patrini32@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:24:35 +0300 Subject: [PATCH] Fix multiple things --- swap/src/cli/cancel_and_refund.rs | 65 ++++++++----------- swap/src/protocol/bob/state.rs | 56 ++++++++++++++-- ...punishes_after_bob_dead_and_bob_cancels.rs | 2 +- 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 549dddbe..aab9f2cd 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -1,5 +1,5 @@ use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Wallet}; -use crate::protocol::bob::BobState; +use crate::protocol::bob::{BobState, BtcCancelledByAlice, BtcPunishedWhileRefundError}; use crate::protocol::Database; use anyhow::{bail, Result}; use bitcoin::Txid; @@ -16,12 +16,11 @@ pub async fn cancel_and_refund( }; let state = match refund(swap_id, bitcoin_wallet, db).await { - Ok(s) => { - tracing::info!("Refund transaction submitted"); - s - } + Ok(s) => s, Err(e) => bail!(e), }; + + tracing::info!("Refund transaction submitted"); Ok(state) } @@ -65,24 +64,21 @@ pub async fn cancel( Err(err) => { if let Ok(error_code) = parse_rpc_error_code(&err) { if error_code == i64::from(RpcErrorCode::RpcVerifyError) { - if err - .to_string() - .contains("Failed to broadcast Bitcoin refund transaction") - { - let txid = state6 - .construct_tx_cancel() - .expect("Error when constructing tx_cancel") - .txid(); - let state = BobState::BtcCancelled(state6); - db.insert_latest_state(swap_id, state.clone().into()) - .await?; - tracing::info!("Cancel transaction has already been confirmed on chain. The swap has therefore already been cancelled by Alice."); - return Ok((txid, state)); - } else { - tracing::debug!(%error_code, "parse rpc error"); - tracing::info!("General error trying to submit cancel transaction"); - } + tracing::debug!(%error_code, "parse rpc error"); + tracing::info!("General error trying to submit cancel transaction"); + } else if error_code == i64::from(RpcErrorCode::RpcVerifyAlreadyInChain) { + tracing::info!("Cancel transaction has already been confirmed on chain"); } + } else if let Some(error) = err.downcast_ref::() { + let txid = state6 + .construct_tx_cancel() + .expect("Error when constructing tx_cancel") + .txid(); + let state = BobState::BtcCancelled(state6); + db.insert_latest_state(swap_id, state.clone().into()) + .await?; + tracing::info!(%error); + return Ok((txid, state)); } bail!(err); } @@ -116,29 +112,24 @@ pub async fn refund( }; tracing::info!(%swap_id, "Manually refunding swap"); - match state6.publish_refund_btc(bitcoin_wallet.as_ref()).await { - Ok(_) => (), - Err(refund_error) => { - if refund_error - .to_string() - .contains("Failed to broadcast Bitcoin refund transaction") - { + Ok(()) => { + let state = BobState::BtcRefunded(state6); + db.insert_latest_state(swap_id, state.clone().into()) + .await?; + Ok(state) + } + Err(error) => { + if let Some(error) = error.downcast_ref::() { + tracing::info!(%error); let state = BobState::BtcPunished { tx_lock_id: state6.tx_lock_id(), }; db.insert_latest_state(swap_id, state.clone().into()) .await?; - - tracing::info!("Cannot refund because BTC is punished by Alice."); return Ok(state); } - bail!(refund_error); + bail!(error); } } - let state = BobState::BtcRefunded(state6); - db.insert_latest_state(swap_id, state.clone().into()) - .await?; - - Ok(state) } diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index a1fd750a..942d9de6 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -20,6 +20,20 @@ use sha2::Sha256; use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use uuid::Uuid; +#[derive(Debug)] +pub struct BtcCancelledByAlice; +impl std::fmt::Display for BtcCancelledByAlice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Cancel transaction has already been confirmed on chain. The swap has therefore already been cancelled by Alice.") + } +} +#[derive(Debug)] +pub struct BtcPunishedWhileRefundError; +impl std::fmt::Display for BtcPunishedWhileRefundError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Cannot refund because BTC is punished.") + } +} #[derive(Debug, Clone, PartialEq, Serialize)] pub enum BobState { @@ -663,6 +677,7 @@ impl State6 { Ok(tx) } + pub fn construct_tx_cancel(&self) -> Result { bitcoin::TxCancel::new( &self.tx_lock, @@ -674,24 +689,51 @@ impl State6 { .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) .context("Failed to complete Bitcoin cancel transaction") } + pub fn print_type_of(&self, _: &T) { + tracing::debug!("{}", std::any::type_name::()) + } pub async fn submit_tx_cancel( &self, bitcoin_wallet: &bitcoin::Wallet, ) -> Result<(Txid, Subscription)> { let transaction = self.construct_tx_cancel()?; - - let (tx_id, subscription) = bitcoin_wallet.broadcast(transaction, "cancel").await?; - - Ok((tx_id, subscription)) + match bitcoin_wallet.broadcast(transaction, "cancel").await { + Ok((txid, subscription)) => Ok((txid, subscription)), + Err(error) => { + match error.downcast_ref::() { + Some(bdk::Error::Electrum(bdk::electrum_client::Error::Protocol(serde_json::Value::String(ref protocol_error)))) + // UTXO is already spent, swap timeout. + if protocol_error.contains("bad-txns-inputs-missingorspent") => + { + return Err(anyhow!(BtcCancelledByAlice)); + } + _ => { + Err(error) + } + } + } + } } pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { let signed_tx_refund = self.signed_refund_transaction()?; - bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; - Ok(()) + match bitcoin_wallet.broadcast(signed_tx_refund, "refund").await { + Ok((_, _)) => Ok(()), + Err(error) => { + match error.downcast_ref::() { + Some(bdk::Error::Electrum(bdk::electrum_client::Error::Protocol(serde_json::Value::String(ref protocol_error)))) + if protocol_error.contains("bad-txns-inputs-missingorspent") => + { + return Err(anyhow!(BtcPunishedWhileRefundError)); + } + _ => { + Err(error) + } + } + } + } } - pub fn signed_refund_transaction(&self) -> Result { let tx_cancel = bitcoin::TxCancel::new( &self.tx_lock, diff --git a/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs b/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs index 5eacefd1..d880b60e 100644 --- a/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs +++ b/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs @@ -76,7 +76,7 @@ async fn alice_manually_punishes_after_bob_dead_and_bob_cancels() { let state = cli::cancel_and_refund(bob_swap_id, bob_swap.bitcoin_wallet, bob_swap.db).await?; - assert!(matches!(state, BobState::BtcPunished { .. })); + assert!(matches!(state, BobState::BtcPunished { .. })); Ok(()) }) .await;