diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 6690e5c7..4f0b9cf6 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -429,6 +429,10 @@ pub async fn run_until( state3, encrypted_signature, } => { + // TODO: Evaluate if it is correct for Alice to Redeem no matter what. + // If T1 expired she should potentially not try redeem. (The implementation + // gives her an advantage.) + let signed_tx_redeem = match build_bitcoin_redeem_transaction( encrypted_signature, &state3.tx_lock, diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 19a0bbd0..b2b55c82 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -223,16 +223,40 @@ where // Bob has locked Btc // Watch for Alice to Lock Xmr or for t1 to elapse BobState::BtcLocked(state3) => { - // TODO(Franck): Refund if cannot connect to Alice. - event_loop_handle.dial().await?; + let state = if let Epoch::T0 = state3.current_epoch(bitcoin_wallet.as_ref()).await? + { + event_loop_handle.dial().await?; - // todo: watch until t1, not indefinitely - let msg2 = event_loop_handle.recv_message2().await?; - let state4 = state3 - .watch_for_lock_xmr(monero_wallet.as_ref(), msg2) - .await?; + let msg2_watcher = event_loop_handle.recv_message2(); + let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); - let state = BobState::XmrLocked(state4); + select! { + msg2 = msg2_watcher => { + + let xmr_lock_watcher = state3.clone() + .watch_for_lock_xmr(monero_wallet.as_ref(), msg2?); + let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); + + select! { + state4 = xmr_lock_watcher => { + BobState::XmrLocked(state4?) + }, + _ = t1_timeout => { + let state4 = state3.t1_expired(); + BobState::T1Expired(state4) + } + } + + }, + _ = t1_timeout => { + let state4 = state3.t1_expired(); + BobState::T1Expired(state4) + } + } + } else { + let state4 = state3.t1_expired(); + BobState::T1Expired(state4) + }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, state::Swap::Bob(db_state)) .await?; @@ -249,10 +273,8 @@ where .await } BobState::XmrLocked(state) => { - // TODO(Franck): Refund if cannot connect to Alice. - event_loop_handle.dial().await?; - let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? { + event_loop_handle.dial().await?; // Alice has locked Xmr // Bob sends Alice his key let tx_redeem_encsig = state.tx_redeem_encsig(); diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 74557f5d..94c30acf 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -28,7 +28,7 @@ use std::{ use tokio::{sync::Mutex, time::timeout}; use tracing::{error, info}; pub mod message; -use crate::bitcoin::{BlockHeight, TransactionBlockHeight}; +use crate::bitcoin::{current_epoch, wait_for_t1, BlockHeight, TransactionBlockHeight}; pub use message::{Message, Message0, Message1, Message2}; #[derive(Debug)] @@ -684,31 +684,20 @@ impl State3 { where W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, { - let tx_id = self.tx_lock.txid(); - let tx_lock_height = bitcoin_wallet.transaction_block_height(tx_id).await; - - let t1_timeout = - poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + self.refund_timelock); - t1_timeout.await; - Ok(()) + wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await } pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result where W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, { - let current_block_height = bitcoin_wallet.block_height().await; - let t0 = bitcoin_wallet - .transaction_block_height(self.tx_lock.txid()) - .await; - let t1 = t0 + self.refund_timelock; - let t2 = t1 + self.punish_timelock; - - match (current_block_height < t1, current_block_height < t2) { - (true, _) => Ok(Epoch::T0), - (false, true) => Ok(Epoch::T1), - (false, false) => Ok(Epoch::T2), - } + current_epoch( + bitcoin_wallet, + self.refund_timelock, + self.punish_timelock, + self.tx_lock.txid(), + ) + .await } } diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index 562b262f..733b4a0d 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::str::FromStr; +use crate::Epoch; pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; @@ -243,3 +244,40 @@ where tokio::time::delay_for(std::time::Duration::from_secs(1)).await; } } + +pub async fn current_epoch( + bitcoin_wallet: &W, + refund_timelock: u32, + punish_timelock: u32, + lock_tx_id: ::bitcoin::Txid, +) -> anyhow::Result +where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, +{ + let current_block_height = bitcoin_wallet.block_height().await; + let t0 = bitcoin_wallet.transaction_block_height(lock_tx_id).await; + let t1 = t0 + refund_timelock; + let t2 = t1 + punish_timelock; + + match (current_block_height < t1, current_block_height < t2) { + (true, _) => Ok(Epoch::T0), + (false, true) => Ok(Epoch::T1), + (false, false) => Ok(Epoch::T2), + } +} + +pub async fn wait_for_t1( + bitcoin_wallet: &W, + refund_timelock: u32, + lock_tx_id: ::bitcoin::Txid, +) -> Result<()> +where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, +{ + let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await; + + let t1_timeout = + poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + refund_timelock); + t1_timeout.await; + Ok(()) +} diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 61c072ab..d0aa4049 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -34,7 +34,9 @@ use tracing::error; pub mod message; use crate::{ - bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight}, + bitcoin::{ + current_epoch, wait_for_t1, BlockHeight, GetRawTransaction, Network, TransactionBlockHeight, + }, monero::{CreateWalletForOutput, WatchForTransfer}, }; use ::bitcoin::{Transaction, Txid}; @@ -621,9 +623,50 @@ impl State3 { }) } + pub async fn wait_for_t1(&self, bitcoin_wallet: &W) -> Result<()> + where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + { + wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await + } + + pub fn t1_expired(&self) -> State4 { + State4 { + A: self.A, + b: self.b.clone(), + s_b: self.s_b, + S_a_monero: self.S_a_monero, + S_a_bitcoin: self.S_a_bitcoin, + v: self.v, + btc: self.btc, + xmr: self.xmr, + refund_timelock: self.refund_timelock, + punish_timelock: self.punish_timelock, + refund_address: self.refund_address.clone(), + redeem_address: self.redeem_address.clone(), + punish_address: self.punish_address.clone(), + tx_lock: self.tx_lock.clone(), + tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), + tx_refund_encsig: self.tx_refund_encsig.clone(), + } + } + pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } + + pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result + where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + { + current_epoch( + bitcoin_wallet, + self.refund_timelock, + self.punish_timelock, + self.tx_lock.txid(), + ) + .await + } } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] @@ -748,31 +791,20 @@ impl State4 { where W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, { - let tx_id = self.tx_lock.txid(); - let tx_lock_height = bitcoin_wallet.transaction_block_height(tx_id).await; - - let t1_timeout = - poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + self.refund_timelock); - t1_timeout.await; - Ok(()) + wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await } pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result where W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, { - let current_block_height = bitcoin_wallet.block_height().await; - let t0 = bitcoin_wallet - .transaction_block_height(self.tx_lock.txid()) - .await; - let t1 = t0 + self.refund_timelock; - let t2 = t1 + self.punish_timelock; - - match (current_block_height < t1, current_block_height < t2) { - (true, _) => Ok(Epoch::T0), - (false, true) => Ok(Epoch::T1), - (false, false) => Ok(Epoch::T2), - } + current_epoch( + bitcoin_wallet, + self.refund_timelock, + self.punish_timelock, + self.tx_lock.txid(), + ) + .await } pub async fn refund_btc(