From 64b71d0b167052a875aacf12b6e4bebf41e682e0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 17 Mar 2021 16:39:41 +1100 Subject: [PATCH 01/18] Remove unnecessary pinning --- swap/src/protocol/alice/steps.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index eec8a86b..192ca522 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -6,7 +6,6 @@ use crate::protocol::alice::event_loop::EventLoopHandle; use crate::protocol::alice::TransferProof; use crate::{bitcoin, monero}; use anyhow::{bail, Context, Result}; -use futures::pin_mut; pub async fn lock_xmr( state3: alice::State3, @@ -103,9 +102,6 @@ pub async fn wait_for_bitcoin_refund( status.is_confirmed_with(punish_timelock) }); - pin_mut!(punish_timelock_expired); - pin_mut!(seen_refund_tx); - tokio::select! { seen_refund = seen_refund_tx => { match seen_refund { From 575893fb51f08c7fd0db86454866ef91c91ae230 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 17 Mar 2021 16:47:15 +1100 Subject: [PATCH 02/18] Use domain types in fn signature instead of messages This simplifies usage witin the swap module. --- swap/src/protocol/alice/event_loop.rs | 10 ++++++---- swap/src/protocol/alice/steps.rs | 7 ++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 34b196e5..537a476e 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -311,21 +311,23 @@ impl LatestRate for kraken::RateUpdateStream { } impl EventLoopHandle { - pub async fn recv_encrypted_signature(&mut self) -> Result { + pub async fn recv_encrypted_signature(&mut self) -> Result { let signature = self .recv_encrypted_signature .take() .context("Encrypted signature was already received")? - .await?; + .await? + .tx_redeem_encsig; Ok(signature) } - pub async fn send_transfer_proof(&mut self, msg: TransferProof) -> Result<()> { + + pub async fn send_transfer_proof(&mut self, msg: monero::TransferProof) -> Result<()> { if self .send_transfer_proof .take() .context("Transfer proof was already sent")? - .send(msg) + .send(TransferProof { tx_lock_proof: msg }) .is_err() { bail!("Failed to send transfer proof, receiver no longer listening?") diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index 192ca522..a6917539 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -3,7 +3,6 @@ use crate::bitcoin::{ }; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::protocol::alice::TransferProof; use crate::{bitcoin, monero}; use anyhow::{bail, Context, Result}; @@ -27,9 +26,7 @@ pub async fn lock_xmr( // Otherwise Alice might publish the lock tx twice! event_loop_handle - .send_transfer_proof(TransferProof { - tx_lock_proof: transfer_proof, - }) + .send_transfer_proof(transfer_proof) .await?; Ok(()) @@ -45,7 +42,7 @@ pub async fn wait_for_bitcoin_encrypted_signature( tracing::debug!("Message 3 received, returning it"); - Ok(msg3.tx_redeem_encsig) + Ok(msg3) } pub async fn publish_cancel_transaction( From a1e065b4e7ded37f867958088d8a091b1a0b55bd Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 17 Mar 2021 17:13:47 +1100 Subject: [PATCH 03/18] Simplify racing cancel timelock against learning enc-sig --- swap/src/protocol/alice/steps.rs | 17 +-------------- swap/src/protocol/alice/swap.rs | 37 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index a6917539..a48590ae 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -1,6 +1,4 @@ -use crate::bitcoin::{ - CancelTimelock, EncryptedSignature, PunishTimelock, TxCancel, TxLock, TxRefund, -}; +use crate::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxLock, TxRefund}; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::{bitcoin, monero}; @@ -32,19 +30,6 @@ pub async fn lock_xmr( Ok(()) } -pub async fn wait_for_bitcoin_encrypted_signature( - event_loop_handle: &mut EventLoopHandle, -) -> Result { - let msg3 = event_loop_handle - .recv_encrypted_signature() - .await - .context("Failed to receive Bitcoin encrypted signature from Bob")?; - - tracing::debug!("Message 3 received, returning it"); - - Ok(msg3) -} - pub async fn publish_cancel_transaction( tx_lock: TxLock, a: bitcoin::SecretKey, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index be8c5306..b7ddd77b 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -7,8 +7,7 @@ use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::protocol::alice::steps::{ - extract_monero_private_key, lock_xmr, publish_cancel_transaction, - wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, + extract_monero_private_key, lock_xmr, publish_cancel_transaction, wait_for_bitcoin_refund, }; use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; @@ -18,6 +17,7 @@ use futures::future::{select, Either}; use futures::pin_mut; use rand::{CryptoRng, RngCore}; use std::sync::Arc; +use tokio::select; use tokio::time::timeout; use tracing::{error, info}; use uuid::Uuid; @@ -141,25 +141,22 @@ async fn run_until_internal( } => { let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { - let wait_for_enc_sig = - wait_for_bitcoin_encrypted_signature(&mut event_loop_handle); - let state3_clone = state3.clone(); - let cancel_timelock_expires = state3_clone - .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + select! { + _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } + } + enc_sig = event_loop_handle.recv_encrypted_signature() => { + tracing::info!("Received encrypted signature"); - pin_mut!(wait_for_enc_sig); - pin_mut!(cancel_timelock_expires); - - match select(cancel_timelock_expires, wait_for_enc_sig).await { - Either::Left(_) => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - Either::Right((enc_sig, _)) => AliceState::EncSigLearned { - state3, - encrypted_signature: Box::new(enc_sig?), - monero_wallet_restore_blockheight, - }, + AliceState::EncSigLearned { + state3, + encrypted_signature: Box::new(enc_sig?), + monero_wallet_restore_blockheight, + } + } } } _ => AliceState::CancelTimelockExpired { From e77f1729b47a08e42aaa3bf99507103a8bb331f6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 17 Mar 2021 17:20:02 +1100 Subject: [PATCH 04/18] Move `extract_monero_private_key` onto TxRefund This functionality is domain-specific to the refund transaction. Move it onto there. --- swap/src/bitcoin/refund.rs | 30 +++++++++++++++++++++++++++--- swap/src/protocol/alice/steps.rs | 25 +------------------------ swap/src/protocol/alice/swap.rs | 8 +++----- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/swap/src/bitcoin/refund.rs b/swap/src/bitcoin/refund.rs index 3d282c99..34057c79 100644 --- a/swap/src/bitcoin/refund.rs +++ b/swap/src/bitcoin/refund.rs @@ -3,10 +3,10 @@ use crate::bitcoin::{ verify_sig, Address, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs, Transaction, TxCancel, }; +use crate::{bitcoin, monero}; use ::bitcoin::util::bip143::SigHashCache; -use ::bitcoin::{SigHash, SigHashType, Txid}; +use ::bitcoin::{Script, SigHash, SigHashType, Txid}; use anyhow::{bail, Context, Result}; -use bitcoin::Script; use ecdsa_fun::Signature; use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; @@ -77,7 +77,31 @@ impl TxRefund { Ok(tx_refund) } - pub fn extract_signature_by_key( + pub fn extract_monero_private_key( + &self, + published_refund_tx: bitcoin::Transaction, + s_a: monero::Scalar, + a: bitcoin::SecretKey, + S_b_bitcoin: bitcoin::PublicKey, + ) -> Result { + let s_a = monero::PrivateKey { scalar: s_a }; + + let tx_refund_sig = self + .extract_signature_by_key(published_refund_tx, a.public()) + .context("Failed to extract signature from Bitcoin refund tx")?; + let tx_refund_encsig = a.encsign(S_b_bitcoin, self.digest()); + + let s_b = bitcoin::recover(S_b_bitcoin, tx_refund_sig, tx_refund_encsig) + .context("Failed to recover Monero secret key from Bitcoin signature")?; + + let s_b = monero::private_key_from_secp256k1_scalar(s_b.into()); + + let spend_key = s_a + s_b; + + Ok(spend_key) + } + + fn extract_signature_by_key( &self, candidate_transaction: Transaction, B: PublicKey, diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index a48590ae..e6d900f4 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -2,7 +2,7 @@ use crate::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxLock, TxRefund} use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::{bitcoin, monero}; -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; pub async fn lock_xmr( state3: alice::State3, @@ -102,26 +102,3 @@ pub async fn wait_for_bitcoin_refund( } } } - -pub fn extract_monero_private_key( - published_refund_tx: bitcoin::Transaction, - tx_refund: &TxRefund, - s_a: monero::Scalar, - a: bitcoin::SecretKey, - S_b_bitcoin: bitcoin::PublicKey, -) -> Result { - let s_a = monero::PrivateKey { scalar: s_a }; - - let tx_refund_sig = tx_refund - .extract_signature_by_key(published_refund_tx, a.public()) - .context("Failed to extract signature from Bitcoin refund tx")?; - let tx_refund_encsig = a.encsign(S_b_bitcoin, tx_refund.digest()); - - let s_b = bitcoin::recover(S_b_bitcoin, tx_refund_sig, tx_refund_encsig) - .context("Failed to recover Monero secret key from Bitcoin signature")?; - let s_b = monero::private_key_from_secp256k1_scalar(s_b.into()); - - let spend_key = s_a + s_b; - - Ok(spend_key) -} diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index b7ddd77b..fcd5885f 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -7,7 +7,7 @@ use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::protocol::alice::steps::{ - extract_monero_private_key, lock_xmr, publish_cancel_transaction, wait_for_bitcoin_refund, + lock_xmr, publish_cancel_transaction, wait_for_bitcoin_refund, }; use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; @@ -315,9 +315,8 @@ async fn run_until_internal( .await } Some(published_refund_tx) => { - let spend_key = extract_monero_private_key( + let spend_key = state3.tx_refund().extract_monero_private_key( published_refund_tx, - &state3.tx_refund(), state3.s_a, state3.a.clone(), state3.S_b_bitcoin, @@ -393,9 +392,8 @@ async fn run_until_internal( let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; - let spend_key = extract_monero_private_key( + let spend_key = tx_refund.extract_monero_private_key( published_refund_tx, - &tx_refund, state3.s_a, state3.a.clone(), state3.S_b_bitcoin, From d682433ec9aa98ac75580bcca18cdadf01617743 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 12:14:39 +1100 Subject: [PATCH 05/18] Move `EventLoopHandle` next to its impl block This struct is not that important so it can move further down. --- swap/src/protocol/alice/event_loop.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 537a476e..c04dbdb8 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -42,12 +42,6 @@ pub struct EventLoop { swap_sender: mpsc::Sender, } -#[derive(Debug)] -pub struct EventLoopHandle { - recv_encrypted_signature: Option>, - send_transfer_proof: Option>, -} - impl EventLoop where LR: LatestRate, @@ -310,6 +304,12 @@ impl LatestRate for kraken::RateUpdateStream { } } +#[derive(Debug)] +pub struct EventLoopHandle { + recv_encrypted_signature: Option>, + send_transfer_proof: Option>, +} + impl EventLoopHandle { pub async fn recv_encrypted_signature(&mut self) -> Result { let signature = self From 75aec95b0ce29ceeb0b61fd2f115f69375f0e228 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 12:27:08 +1100 Subject: [PATCH 06/18] Introduce monero::TransferRequest This allows us to move critical crypto logic onto `State3` which holds all the necessary data which consequently allows us to get rid of `lock_xmr` altogether by inlining it into the swap function. The reduced indirection improves readability. --- swap/src/monero/wallet.rs | 20 ++++++++++++++------ swap/src/protocol/alice/state.rs | 14 ++++++++++++++ swap/src/protocol/alice/steps.rs | 30 +----------------------------- swap/src/protocol/alice/swap.rs | 17 +++++++++++++---- 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index 764235c9..61894b99 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -120,12 +120,13 @@ impl Wallet { Ok(()) } - pub async fn transfer( - &self, - public_spend_key: PublicKey, - public_view_key: PublicViewKey, - amount: Amount, - ) -> Result { + pub async fn transfer(&self, request: TransferRequest) -> Result { + let TransferRequest { + public_spend_key, + public_view_key, + amount, + } = request; + let destination_address = Address::standard(self.network, public_spend_key, public_view_key.into()); @@ -222,6 +223,13 @@ impl Wallet { } } +#[derive(Debug)] +pub struct TransferRequest { + pub public_spend_key: PublicKey, + pub public_view_key: PublicViewKey, + pub amount: Amount, +} + async fn wait_for_confirmations( txid: String, fetch_tx: impl Fn(String) -> Fut, diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index e961a13b..bc443f03 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -2,6 +2,7 @@ use crate::bitcoin::{ current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, TxCancel, TxPunish, TxRefund, }; use crate::env::Config; +use crate::monero::wallet::TransferRequest; use crate::protocol::alice::{Message1, Message3}; use crate::protocol::bob::{Message0, Message2, Message4}; use crate::protocol::CROSS_CURVE_PROOF_SYSTEM; @@ -343,6 +344,19 @@ impl State3 { )) } + pub fn lock_xmr_transfer_request(&self) -> TransferRequest { + let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: self.s_a }); + + let public_spend_key = S_a + self.S_b_monero; + let public_view_key = self.v.public(); + + TransferRequest { + public_spend_key, + public_view_key, + amount: self.xmr, + } + } + pub fn tx_cancel(&self) -> TxCancel { TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B) } diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index e6d900f4..85075f8d 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -1,35 +1,7 @@ +use crate::bitcoin; use crate::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxLock, TxRefund}; -use crate::protocol::alice; -use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::{bitcoin, monero}; use anyhow::{bail, Result}; -pub async fn lock_xmr( - state3: alice::State3, - event_loop_handle: &mut EventLoopHandle, - monero_wallet: &monero::Wallet, -) -> Result<()> { - let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: state3.s_a }); - - let public_spend_key = S_a + state3.S_b_monero; - let public_view_key = state3.v.public(); - - let transfer_proof = monero_wallet - .transfer(public_spend_key, public_view_key, state3.xmr) - .await?; - - // TODO(Franck): Wait for Monero to be confirmed once - // Waiting for XMR confirmations should not be done in here, but in a separate - // state! We have to record that Alice has already sent the transaction. - // Otherwise Alice might publish the lock tx twice! - - event_loop_handle - .send_transfer_proof(transfer_proof) - .await?; - - Ok(()) -} - pub async fn publish_cancel_transaction( tx_lock: TxLock, a: bitcoin::SecretKey, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index fcd5885f..52c6ae92 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -6,9 +6,7 @@ use crate::env::Config; use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::protocol::alice::steps::{ - lock_xmr, publish_cancel_transaction, wait_for_bitcoin_refund, -}; +use crate::protocol::alice::steps::{publish_cancel_transaction, wait_for_bitcoin_refund}; use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; @@ -113,7 +111,18 @@ async fn run_until_internal( // block 0 for scenarios where we create a refund wallet. let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; - lock_xmr(*state3.clone(), &mut event_loop_handle, &monero_wallet).await?; + let transfer_proof = monero_wallet + .transfer(state3.lock_xmr_transfer_request()) + .await?; + + // TODO(Franck): Wait for Monero to be confirmed once + // Waiting for XMR confirmations should not be done in here, but in a separate + // state! We have to record that Alice has already sent the transaction. + // Otherwise Alice might publish the lock tx twice! + + event_loop_handle + .send_transfer_proof(transfer_proof) + .await?; let state = AliceState::XmrLocked { state3, From c92f2dbc77274644dca43e8bb8569f3e011605b1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 12:36:29 +1100 Subject: [PATCH 07/18] Move more domain knowledge onto the `TxCancel` type --- swap/src/bitcoin/cancel.rs | 38 +++++++++++++++++++++++++++++--- swap/src/protocol/alice/steps.rs | 13 +++++------ swap/src/protocol/bob/state.rs | 20 +++++------------ 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/swap/src/bitcoin/cancel.rs b/swap/src/bitcoin/cancel.rs index e54bc988..d1d0bc43 100644 --- a/swap/src/bitcoin/cancel.rs +++ b/swap/src/bitcoin/cancel.rs @@ -1,12 +1,12 @@ +use crate::bitcoin; use crate::bitcoin::wallet::Watchable; use crate::bitcoin::{ build_shared_output_descriptor, Address, Amount, BlockHeight, PublicKey, Transaction, TxLock, TX_FEE, }; use ::bitcoin::util::bip143::SigHashCache; -use ::bitcoin::{OutPoint, SigHash, SigHashType, TxIn, TxOut, Txid}; +use ::bitcoin::{OutPoint, Script, SigHash, SigHashType, TxIn, TxOut, Txid}; use anyhow::Result; -use bitcoin::Script; use ecdsa_fun::Signature; use miniscript::{Descriptor, DescriptorTrait}; use serde::{Deserialize, Serialize}; @@ -149,7 +149,39 @@ impl TxCancel { OutPoint::new(self.inner.txid(), 0) } - pub fn add_signatures( + pub fn complete_as_alice( + self, + a: bitcoin::SecretKey, + B: bitcoin::PublicKey, + tx_cancel_sig_B: bitcoin::Signature, + ) -> Result { + let sig_a = a.sign(self.digest()); + let sig_b = tx_cancel_sig_B; + + let tx_cancel = self + .add_signatures((a.public(), sig_a), (B, sig_b)) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + Ok(tx_cancel) + } + + pub fn complete_as_bob( + self, + A: bitcoin::PublicKey, + b: bitcoin::SecretKey, + tx_cancel_sig_A: bitcoin::Signature, + ) -> Result { + let sig_a = tx_cancel_sig_A; + let sig_b = b.sign(self.digest()); + + let tx_cancel = self + .add_signatures((A, sig_a), (b.public(), sig_b)) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + Ok(tx_cancel) + } + + fn add_signatures( self, (A, sig_a): (PublicKey, Signature), (B, sig_b): (PublicKey, Signature), diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index 85075f8d..ef222da8 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -1,6 +1,6 @@ use crate::bitcoin; use crate::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxLock, TxRefund}; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; pub async fn publish_cancel_transaction( tx_lock: TxLock, @@ -25,15 +25,12 @@ pub async fn publish_cancel_transaction( // TODO(Franck): Maybe the cancel transaction is already mined, in this case, // the broadcast will error out. - let sig_a = a.sign(tx_cancel.digest()); - let sig_b = tx_cancel_sig_bob.clone(); - - let tx_cancel = tx_cancel - .add_signatures((a.public(), sig_a), (B, sig_b)) - .expect("sig_{a,b} to be valid signatures for tx_cancel"); + let transaction = tx_cancel + .complete_as_alice(a, B, tx_cancel_sig_bob) + .context("Failed to complete Bitcoin cancel transaction")?; // TODO(Franck): Error handling is delicate, why can't we broadcast? - let (..) = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?; + let (..) = bitcoin_wallet.broadcast(transaction, "cancel").await?; // TODO(Franck): Wait until transaction is mined and returned mined // block height diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 7d288a2e..9db3f852 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -8,7 +8,7 @@ use crate::monero_ext::ScalarExt; use crate::protocol::alice::{Message1, Message3}; use crate::protocol::bob::{EncryptedSignature, Message0, Message2, Message4}; use crate::protocol::CROSS_CURVE_PROOF_SYSTEM; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use ecdsa_fun::adaptor::{Adaptor, HashTranscript}; use ecdsa_fun::nonce::Deterministic; use ecdsa_fun::Signature; @@ -441,20 +441,12 @@ impl State4 { } pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { - let tx_cancel = - bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); + let transaction = + bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()) + .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) + .context("Failed to complete Bitcoin cancel transaction")?; - let sig_a = self.tx_cancel_sig_a.clone(); - let sig_b = self.b.sign(tx_cancel.digest()); - - let tx_cancel = tx_cancel - .add_signatures((self.A, sig_a), (self.b.public(), sig_b)) - .expect( - "sig_{a,b} to be valid signatures for - tx_cancel", - ); - - let (tx_id, _) = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?; + let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?; Ok(tx_id) } From afb7e816a186807c80f61df7c35c48db7e212c5b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 12:39:52 +1100 Subject: [PATCH 08/18] Don't wait for confirmations again We only call this function within `CancelTimelockExpired`. There is no need to check the confirmations again. --- swap/src/protocol/alice/steps.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index ef222da8..3d46dde0 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -10,10 +10,6 @@ pub async fn publish_cancel_transaction( tx_cancel_sig_bob: bitcoin::Signature, bitcoin_wallet: &bitcoin::Wallet, ) -> Result<()> { - bitcoin_wallet - .watch_until_status(&tx_lock, |status| status.is_confirmed_with(cancel_timelock)) - .await?; - let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B); // If Bob hasn't yet broadcasted the tx cancel, we do it From 8c9285f1f9ad135830166170ab1970b4a4747efe Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 12:58:37 +1100 Subject: [PATCH 09/18] Inline step function because it has been reduced to a single statement --- swap/src/protocol/alice/steps.rs | 37 ++------------------------------ swap/src/protocol/alice/swap.rs | 37 +++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index 3d46dde0..914dc0c6 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -1,39 +1,6 @@ use crate::bitcoin; -use crate::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxLock, TxRefund}; -use anyhow::{bail, Context, Result}; - -pub async fn publish_cancel_transaction( - tx_lock: TxLock, - a: bitcoin::SecretKey, - B: bitcoin::PublicKey, - cancel_timelock: CancelTimelock, - tx_cancel_sig_bob: bitcoin::Signature, - bitcoin_wallet: &bitcoin::Wallet, -) -> Result<()> { - let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B); - - // If Bob hasn't yet broadcasted the tx cancel, we do it - if bitcoin_wallet - .get_raw_transaction(tx_cancel.txid()) - .await - .is_err() - { - // TODO(Franck): Maybe the cancel transaction is already mined, in this case, - // the broadcast will error out. - - let transaction = tx_cancel - .complete_as_alice(a, B, tx_cancel_sig_bob) - .context("Failed to complete Bitcoin cancel transaction")?; - - // TODO(Franck): Error handling is delicate, why can't we broadcast? - let (..) = bitcoin_wallet.broadcast(transaction, "cancel").await?; - - // TODO(Franck): Wait until transaction is mined and returned mined - // block height - } - - Ok(()) -} +use crate::bitcoin::{PunishTimelock, TxCancel, TxRefund}; +use anyhow::{bail, Result}; pub async fn wait_for_bitcoin_refund( tx_cancel: &TxCancel, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 52c6ae92..218ae8cf 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -6,7 +6,7 @@ use crate::env::Config; use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::protocol::alice::steps::{publish_cancel_transaction, wait_for_bitcoin_refund}; +use crate::protocol::alice::steps::wait_for_bitcoin_refund; use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; @@ -259,15 +259,32 @@ async fn run_until_internal( state3, monero_wallet_restore_blockheight, } => { - publish_cancel_transaction( - state3.tx_lock.clone(), - state3.a.clone(), - state3.B, - state3.cancel_timelock, - state3.tx_cancel_sig_bob.clone(), - &bitcoin_wallet, - ) - .await?; + let tx_cancel = state3.tx_cancel(); + + // If Bob hasn't yet broadcasted the tx cancel, we do it + if bitcoin_wallet + .get_raw_transaction(tx_cancel.txid()) + .await + .is_err() + { + let transaction = tx_cancel + .complete_as_alice( + state3.a.clone(), + state3.B, + state3.tx_cancel_sig_bob.clone(), + ) + .context("Failed to complete Bitcoin cancel transaction")?; + + if let Err(e) = bitcoin_wallet.broadcast(transaction, "cancel").await { + tracing::debug!( + "Assuming transaction is already broadcasted because: {:#}", + e + ) + } + + // TODO(Franck): Wait until transaction is mined and + // returned mined block height + } let state = AliceState::BtcCancelled { state3, From 05849505b19f1f56077c99d7121c018fc8130114 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:13:30 +1100 Subject: [PATCH 10/18] Inline `wait_for_bitcoin_refund` This function is essentially a single select! statement and can easily be inlined into the swap state machine. --- swap/src/protocol/alice.rs | 1 - swap/src/protocol/alice/steps.rs | 36 ------------- swap/src/protocol/alice/swap.rs | 89 ++++++++++++++------------------ 3 files changed, 40 insertions(+), 86 deletions(-) delete mode 100644 swap/src/protocol/alice/steps.rs diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 351cef71..28ef4bce 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -19,7 +19,6 @@ mod encrypted_signature; pub mod event_loop; mod execution_setup; pub mod state; -mod steps; pub mod swap; mod transfer_proof; diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs deleted file mode 100644 index 914dc0c6..00000000 --- a/swap/src/protocol/alice/steps.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::bitcoin; -use crate::bitcoin::{PunishTimelock, TxCancel, TxRefund}; -use anyhow::{bail, Result}; - -pub async fn wait_for_bitcoin_refund( - tx_cancel: &TxCancel, - tx_refund: &TxRefund, - punish_timelock: PunishTimelock, - bitcoin_wallet: &bitcoin::Wallet, -) -> Result> { - let refund_tx_id = tx_refund.txid(); - let seen_refund_tx = - bitcoin_wallet.watch_until_status(tx_refund, |status| status.has_been_seen()); - - let punish_timelock_expired = bitcoin_wallet.watch_until_status(tx_cancel, |status| { - status.is_confirmed_with(punish_timelock) - }); - - tokio::select! { - seen_refund = seen_refund_tx => { - match seen_refund { - Ok(()) => { - let published_refund_tx = bitcoin_wallet.get_raw_transaction(refund_tx_id).await?; - - Ok(Some(published_refund_tx)) - } - Err(e) => { - bail!(e.context("Failed to monitor refund transaction")) - } - } - } - _ = punish_timelock_expired => { - Ok(None) - } - } -} diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 218ae8cf..62ad093d 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -6,7 +6,6 @@ use crate::env::Config; use crate::monero_ext::ScalarExt; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::protocol::alice::steps::wait_for_bitcoin_refund; use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; @@ -309,66 +308,58 @@ async fn run_until_internal( state3, monero_wallet_restore_blockheight, } => { - let published_refund_tx = wait_for_bitcoin_refund( - &state3.tx_cancel(), - &state3.tx_refund(), - state3.punish_timelock, - &bitcoin_wallet, - ) - .await?; + let tx_refund = state3.tx_refund(); + let tx_cancel = state3.tx_cancel(); - // TODO(Franck): Review error handling - match published_refund_tx { - None => { - let state = AliceState::BtcPunishable { - state3, - monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; + let seen_refund_tx = + bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - Some(published_refund_tx) => { - let spend_key = state3.tx_refund().extract_monero_private_key( + let punish_timelock_expired = bitcoin_wallet + .watch_until_status(&tx_cancel, |status| { + status.is_confirmed_with(state3.punish_timelock) + }); + + let state = tokio::select! { + seen_refund = seen_refund_tx => { + seen_refund.context("Failed to monitor refund transaction")?; + let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; + + let spend_key = tx_refund.extract_monero_private_key( published_refund_tx, state3.s_a, state3.a.clone(), state3.S_b_bitcoin, )?; - let state = AliceState::BtcRefunded { + AliceState::BtcRefunded { spend_key, state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } - } + _ = punish_timelock_expired => { + AliceState::BtcPunishable { + state3, + monero_wallet_restore_blockheight, + } + } + }; + + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet.clone(), + monero_wallet, + env_config, + swap_id, + db, + ) + .await } AliceState::BtcRefunded { spend_key, From 0d8962762a99dcfb445d8a0d7a4f53cdbab6d944 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:16:21 +1100 Subject: [PATCH 11/18] Use early return to reduce one level of indentation --- swap/src/protocol/alice/swap.rs | 687 ++++++++++++++++---------------- swap/src/protocol/bob/swap.rs | 670 +++++++++++++++---------------- 2 files changed, 676 insertions(+), 681 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 62ad093d..adedb9b0 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -70,158 +70,146 @@ async fn run_until_internal( ) -> Result { info!("Current state: {}", state); if is_target_state(&state) { - Ok(state) - } else { - match state { - AliceState::Started { state3 } => { - timeout( - env_config.bob_time_to_act, - bitcoin_wallet - .watch_until_status(&state3.tx_lock, |status| status.has_been_seen()), - ) - .await - .context("Failed to find lock Bitcoin tx")??; + return Ok(state); + } - bitcoin_wallet - .watch_until_status(&state3.tx_lock, |status| { - status.is_confirmed_with(env_config.bitcoin_finality_confirmations) - }) - .await?; + match state { + AliceState::Started { state3 } => { + timeout( + env_config.bob_time_to_act, + bitcoin_wallet.watch_until_status(&state3.tx_lock, |status| status.has_been_seen()), + ) + .await + .context("Failed to find lock Bitcoin tx")??; - let state = AliceState::BtcLocked { state3 }; + bitcoin_wallet + .watch_until_status(&state3.tx_lock, |status| { + status.is_confirmed_with(env_config.bitcoin_finality_confirmations) + }) + .await?; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - AliceState::BtcLocked { state3 } => { - // Record the current monero wallet block height so we don't have to scan from - // block 0 for scenarios where we create a refund wallet. - let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; + let state = AliceState::BtcLocked { state3 }; - let transfer_proof = monero_wallet - .transfer(state3.lock_xmr_transfer_request()) - .await?; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + AliceState::BtcLocked { state3 } => { + // Record the current monero wallet block height so we don't have to scan from + // block 0 for scenarios where we create a refund wallet. + let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; - // TODO(Franck): Wait for Monero to be confirmed once - // Waiting for XMR confirmations should not be done in here, but in a separate - // state! We have to record that Alice has already sent the transaction. - // Otherwise Alice might publish the lock tx twice! + let transfer_proof = monero_wallet + .transfer(state3.lock_xmr_transfer_request()) + .await?; - event_loop_handle - .send_transfer_proof(transfer_proof) - .await?; + // TODO(Franck): Wait for Monero to be confirmed once + // Waiting for XMR confirmations should not be done in here, but in a separate + // state! We have to record that Alice has already sent the transaction. + // Otherwise Alice might publish the lock tx twice! - let state = AliceState::XmrLocked { - state3, - monero_wallet_restore_blockheight, - }; + event_loop_handle + .send_transfer_proof(transfer_proof) + .await?; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - AliceState::XmrLocked { + let state = AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - select! { - _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } - } - enc_sig = event_loop_handle.recv_encrypted_signature() => { - tracing::info!("Received encrypted signature"); + }; - AliceState::EncSigLearned { - state3, - encrypted_signature: Box::new(enc_sig?), - monero_wallet_restore_blockheight, - } + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + AliceState::XmrLocked { + state3, + monero_wallet_restore_blockheight, + } => { + let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + select! { + _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } + } + enc_sig = event_loop_handle.recv_encrypted_signature() => { + tracing::info!("Received encrypted signature"); + + AliceState::EncSigLearned { + state3, + encrypted_signature: Box::new(enc_sig?), + monero_wallet_restore_blockheight, } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - AliceState::EncSigLearned { - state3, - encrypted_signature, - monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( - *encrypted_signature, - state3.a.clone(), - state3.s_a.to_secpfun_scalar(), - state3.B, - ) { - Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { - Ok((_, finality)) => match finality.await { - Ok(_) => AliceState::BtcRedeemed, - Err(e) => { - bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) - } - }, + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet.clone(), + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + AliceState::EncSigLearned { + state3, + encrypted_signature, + monero_wallet_restore_blockheight, + } => { + let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( + *encrypted_signature, + state3.a.clone(), + state3.s_a.to_secpfun_scalar(), + state3.B, + ) { + Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { + Ok((_, finality)) => match finality.await { + Ok(_) => AliceState::BtcRedeemed, Err(e) => { - error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); - state3 - .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) - .await?; - - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) } }, Err(e) => { - error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); state3 .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) .await?; @@ -231,236 +219,241 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } + }, + Err(e) => { + error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + state3 + .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) + .await?; + + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } => { - let tx_cancel = state3.tx_cancel(); - - // If Bob hasn't yet broadcasted the tx cancel, we do it - if bitcoin_wallet - .get_raw_transaction(tx_cancel.txid()) - .await - .is_err() - { - let transaction = tx_cancel - .complete_as_alice( - state3.a.clone(), - state3.B, - state3.tx_cancel_sig_bob.clone(), - ) - .context("Failed to complete Bitcoin cancel transaction")?; - - if let Err(e) = bitcoin_wallet.broadcast(transaction, "cancel").await { - tracing::debug!( - "Assuming transaction is already broadcasted because: {:#}", - e - ) - } - - // TODO(Franck): Wait until transaction is mined and - // returned mined block height } - - let state = AliceState::BtcCancelled { + _ => AliceState::CancelTimelockExpired { state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) + }, + }; + + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } => { + let tx_cancel = state3.tx_cancel(); + + // If Bob hasn't yet broadcasted the tx cancel, we do it + if bitcoin_wallet + .get_raw_transaction(tx_cancel.txid()) .await + .is_err() + { + let transaction = tx_cancel + .complete_as_alice(state3.a.clone(), state3.B, state3.tx_cancel_sig_bob.clone()) + .context("Failed to complete Bitcoin cancel transaction")?; + + if let Err(e) = bitcoin_wallet.broadcast(transaction, "cancel").await { + tracing::debug!( + "Assuming transaction is already broadcasted because: {:#}", + e + ) + } + + // TODO(Franck): Wait until transaction is mined and + // returned mined block height } - AliceState::BtcCancelled { + + let state = AliceState::BtcCancelled { state3, monero_wallet_restore_blockheight, - } => { - let tx_refund = state3.tx_refund(); - let tx_cancel = state3.tx_cancel(); + }; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + AliceState::BtcCancelled { + state3, + monero_wallet_restore_blockheight, + } => { + let tx_refund = state3.tx_refund(); + let tx_cancel = state3.tx_cancel(); - let seen_refund_tx = - bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); + let seen_refund_tx = + bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); - let punish_timelock_expired = bitcoin_wallet - .watch_until_status(&tx_cancel, |status| { - status.is_confirmed_with(state3.punish_timelock) - }); + let punish_timelock_expired = bitcoin_wallet.watch_until_status(&tx_cancel, |status| { + status.is_confirmed_with(state3.punish_timelock) + }); - let state = tokio::select! { - seen_refund = seen_refund_tx => { - seen_refund.context("Failed to monitor refund transaction")?; - let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; + let state = tokio::select! { + seen_refund = seen_refund_tx => { + seen_refund.context("Failed to monitor refund transaction")?; + let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; - let spend_key = tx_refund.extract_monero_private_key( - published_refund_tx, - state3.s_a, - state3.a.clone(), - state3.S_b_bitcoin, - )?; + let spend_key = tx_refund.extract_monero_private_key( + published_refund_tx, + state3.s_a, + state3.a.clone(), + state3.S_b_bitcoin, + )?; - AliceState::BtcRefunded { - spend_key, - state3, - monero_wallet_restore_blockheight, - } - } - _ = punish_timelock_expired => { - AliceState::BtcPunishable { - state3, - monero_wallet_restore_blockheight, - } - } - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - AliceState::BtcRefunded { - spend_key, - state3, - monero_wallet_restore_blockheight, - } => { - let view_key = state3.v; - - monero_wallet - .create_from(spend_key, view_key, monero_wallet_restore_blockheight) - .await?; - - let state = AliceState::XmrRefunded; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - Ok(state) - } - AliceState::BtcPunishable { - state3, - monero_wallet_restore_blockheight, - } => { - let signed_tx_punish = state3.tx_punish().complete( - state3.tx_punish_sig_bob.clone(), - state3.a.clone(), - state3.B, - )?; - - let punish_tx_finalised = async { - let (txid, finality) = - bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; - - finality.await?; - - Result::<_, anyhow::Error>::Ok(txid) - }; - - let tx_refund = state3.tx_refund(); - let refund_tx_seen = - bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); - - pin_mut!(punish_tx_finalised); - pin_mut!(refund_tx_seen); - - match select(refund_tx_seen, punish_tx_finalised).await { - Either::Left((Ok(()), _)) => { - let published_refund_tx = - bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; - - let spend_key = tx_refund.extract_monero_private_key( - published_refund_tx, - state3.s_a, - state3.a.clone(), - state3.S_b_bitcoin, - )?; - let state = AliceState::BtcRefunded { - spend_key, - state3, - monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } - Either::Left((Err(e), _)) => { - bail!(e.context("Failed to monitor refund transaction")) - } - Either::Right(_) => { - let state = AliceState::BtcPunished; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, } } - } - AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), - AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), - AliceState::BtcPunished => Ok(AliceState::BtcPunished), - AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), + _ = punish_timelock_expired => { + AliceState::BtcPunishable { + state3, + monero_wallet_restore_blockheight, + } + } + }; + + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet.clone(), + monero_wallet, + env_config, + swap_id, + db, + ) + .await } + AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, + } => { + let view_key = state3.v; + + monero_wallet + .create_from(spend_key, view_key, monero_wallet_restore_blockheight) + .await?; + + let state = AliceState::XmrRefunded; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + Ok(state) + } + AliceState::BtcPunishable { + state3, + monero_wallet_restore_blockheight, + } => { + let signed_tx_punish = state3.tx_punish().complete( + state3.tx_punish_sig_bob.clone(), + state3.a.clone(), + state3.B, + )?; + + let punish_tx_finalised = async { + let (txid, finality) = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; + + finality.await?; + + Result::<_, anyhow::Error>::Ok(txid) + }; + + let tx_refund = state3.tx_refund(); + let refund_tx_seen = + bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); + + pin_mut!(punish_tx_finalised); + pin_mut!(refund_tx_seen); + + match select(refund_tx_seen, punish_tx_finalised).await { + Either::Left((Ok(()), _)) => { + let published_refund_tx = + bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; + + let spend_key = tx_refund.extract_monero_private_key( + published_refund_tx, + state3.s_a, + state3.a.clone(), + state3.S_b_bitcoin, + )?; + let state = AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, + }; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet.clone(), + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + Either::Left((Err(e), _)) => { + bail!(e.context("Failed to monitor refund transaction")) + } + Either::Right(_) => { + let state = AliceState::BtcPunished; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + bitcoin_wallet.clone(), + monero_wallet, + env_config, + swap_id, + db, + ) + .await + } + } + } + AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), + AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), + AliceState::BtcPunished => Ok(AliceState::BtcPunished), + AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), } } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index c2ecc890..f6b7ea78 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -63,347 +63,349 @@ async fn run_until_internal( ) -> Result { trace!("Current state: {}", state); if is_target_state(&state) { - Ok(state) - } else { - match state { - BobState::Started { btc_amount } => { - let bitcoin_refund_address = bitcoin_wallet.new_address().await?; + return Ok(state); + } + match state { + BobState::Started { btc_amount } => { + let bitcoin_refund_address = bitcoin_wallet.new_address().await?; + + event_loop_handle.dial().await?; + + let state2 = request_price_and_setup( + btc_amount, + &mut event_loop_handle, + env_config, + bitcoin_refund_address, + ) + .await?; + + let state = BobState::ExecutionSetupDone(state2); + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::ExecutionSetupDone(state2) => { + // Do not lock Bitcoin if not connected to Alice. + event_loop_handle.dial().await?; + // Alice and Bob have exchanged info + let (state3, tx_lock) = state2.lock_btc().await?; + let signed_tx = bitcoin_wallet + .sign_and_finalize(tx_lock.clone().into()) + .await + .context("Failed to sign Bitcoin lock transaction")?; + let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?; + + let state = BobState::BtcLocked(state3); + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + // Bob has locked Btc + // Watch for Alice to Lock Xmr or for cancel timelock to elapse + BobState::BtcLocked(state3) => { + let state = if let ExpiredTimelocks::None = + state3.current_epoch(bitcoin_wallet.as_ref()).await? + { event_loop_handle.dial().await?; - let state2 = request_price_and_setup( - btc_amount, - &mut event_loop_handle, - env_config, - bitcoin_refund_address, - ) + let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); + let cancel_timelock_expires = + state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + + // Record the current monero wallet block height so we don't have to scan from + // block 0 once we create the redeem wallet. + let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; + + tracing::info!("Waiting for Alice to lock Monero"); + + select! { + transfer_proof = transfer_proof_watcher => { + let transfer_proof = transfer_proof?.tx_lock_proof; + + tracing::info!(txid = %transfer_proof.tx_hash(), "Alice locked Monero"); + + BobState::XmrLockProofReceived { + state: state3, + lock_transfer_proof: transfer_proof, + monero_wallet_restore_blockheight + } + }, + _ = cancel_timelock_expires => { + tracing::info!("Alice took too long to lock Monero, cancelling the swap"); + + let state4 = state3.cancel(); + BobState::CancelTimelockExpired(state4) + } + } + } else { + let state4 = state3.cancel(); + BobState::CancelTimelockExpired(state4) + }; + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::XmrLockProofReceived { + state, + lock_transfer_proof, + monero_wallet_restore_blockheight, + } => { + let state = if let ExpiredTimelocks::None = + state.current_epoch(bitcoin_wallet.as_ref()).await? + { + event_loop_handle.dial().await?; + + let xmr_lock_watcher = state.clone().watch_for_lock_xmr( + monero_wallet.as_ref(), + lock_transfer_proof, + monero_wallet_restore_blockheight, + ); + let cancel_timelock_expires = + state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + + select! { + state4 = xmr_lock_watcher => { + match state4? { + Ok(state4) => BobState::XmrLocked(state4), + Err(InsufficientFunds {..}) => { + warn!("The other party has locked insufficient Monero funds! Waiting for refund..."); + state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?; + let state4 = state.cancel(); + BobState::CancelTimelockExpired(state4) + }, + } + }, + _ = cancel_timelock_expires => { + let state4 = state.cancel(); + BobState::CancelTimelockExpired(state4) + } + } + } else { + let state4 = state.cancel(); + BobState::CancelTimelockExpired(state4) + }; + + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::XmrLocked(state) => { + let state = if let ExpiredTimelocks::None = + state.expired_timelock(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(); + + let state4_clone = state.clone(); + + let enc_sig_sent_watcher = + event_loop_handle.send_encrypted_signature(tx_redeem_encsig); + let bitcoin_wallet = bitcoin_wallet.clone(); + let cancel_timelock_expires = + state4_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + + select! { + _ = enc_sig_sent_watcher => { + BobState::EncSigSent(state) + }, + _ = cancel_timelock_expires => { + BobState::CancelTimelockExpired(state) + } + } + } else { + BobState::CancelTimelockExpired(state) + }; + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::EncSigSent(state) => { + let state = if let ExpiredTimelocks::None = + state.expired_timelock(bitcoin_wallet.as_ref()).await? + { + let state_clone = state.clone(); + let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); + let cancel_timelock_expires = + state_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + + select! { + state5 = redeem_watcher => { + BobState::BtcRedeemed(state5?) + }, + _ = cancel_timelock_expires => { + BobState::CancelTimelockExpired(state) + } + } + } else { + BobState::CancelTimelockExpired(state) + }; + + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet.clone(), + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::BtcRedeemed(state) => { + // Bob redeems XMR using revealed s_a + state.claim_xmr(monero_wallet.as_ref()).await?; + + // Ensure that the generated wallet is synced so we have a proper balance + monero_wallet.refresh().await?; + // Sweep (transfer all funds) to the given address + let tx_hashes = monero_wallet.sweep_all(receive_monero_address).await?; + + for tx_hash in tx_hashes { + tracing::info!("Sent XMR to {} in tx {}", receive_monero_address, tx_hash.0); + } + + let state = BobState::XmrRedeemed { + tx_lock_id: state.tx_lock_id(), + }; + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::CancelTimelockExpired(state4) => { + if state4 + .check_for_tx_cancel(bitcoin_wallet.as_ref()) + .await + .is_err() + { + state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; + } + + let state = BobState::BtcCancelled(state4); + db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) .await?; - let state = BobState::ExecutionSetupDone(state2); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::ExecutionSetupDone(state2) => { - // Do not lock Bitcoin if not connected to Alice. - event_loop_handle.dial().await?; - // Alice and Bob have exchanged info - let (state3, tx_lock) = state2.lock_btc().await?; - let signed_tx = bitcoin_wallet - .sign_and_finalize(tx_lock.clone().into()) - .await - .context("Failed to sign Bitcoin lock transaction")?; - let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?; - - let state = BobState::BtcLocked(state3); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - // Bob has locked Btc - // Watch for Alice to Lock Xmr or for cancel timelock to elapse - BobState::BtcLocked(state3) => { - let state = if let ExpiredTimelocks::None = - state3.current_epoch(bitcoin_wallet.as_ref()).await? - { - event_loop_handle.dial().await?; - - let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); - let cancel_timelock_expires = - state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - - // Record the current monero wallet block height so we don't have to scan from - // block 0 once we create the redeem wallet. - let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; - - tracing::info!("Waiting for Alice to lock Monero"); - - select! { - transfer_proof = transfer_proof_watcher => { - let transfer_proof = transfer_proof?.tx_lock_proof; - - tracing::info!(txid = %transfer_proof.tx_hash(), "Alice locked Monero"); - - BobState::XmrLockProofReceived { - state: state3, - lock_transfer_proof: transfer_proof, - monero_wallet_restore_blockheight - } - }, - _ = cancel_timelock_expires => { - tracing::info!("Alice took too long to lock Monero, cancelling the swap"); - - let state4 = state3.cancel(); - BobState::CancelTimelockExpired(state4) - } - } - } else { - let state4 = state3.cancel(); - BobState::CancelTimelockExpired(state4) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::XmrLockProofReceived { + run_until_internal( state, - lock_transfer_proof, - monero_wallet_restore_blockheight, - } => { - let state = if let ExpiredTimelocks::None = - state.current_epoch(bitcoin_wallet.as_ref()).await? - { - event_loop_handle.dial().await?; - - let xmr_lock_watcher = state.clone().watch_for_lock_xmr( - monero_wallet.as_ref(), - lock_transfer_proof, - monero_wallet_restore_blockheight, - ); - let cancel_timelock_expires = - state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - - select! { - state4 = xmr_lock_watcher => { - match state4? { - Ok(state4) => BobState::XmrLocked(state4), - Err(InsufficientFunds {..}) => { - warn!("The other party has locked insufficient Monero funds! Waiting for refund..."); - state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?; - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) - }, - } - }, - _ = cancel_timelock_expires => { - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) - } - } - } else { - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::XmrLocked(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(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(); - - let state4_clone = state.clone(); - - let enc_sig_sent_watcher = - event_loop_handle.send_encrypted_signature(tx_redeem_encsig); - let bitcoin_wallet = bitcoin_wallet.clone(); - let cancel_timelock_expires = - state4_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - - select! { - _ = enc_sig_sent_watcher => { - BobState::EncSigSent(state) - }, - _ = cancel_timelock_expires => { - BobState::CancelTimelockExpired(state) - } - } - } else { - BobState::CancelTimelockExpired(state) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::EncSigSent(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(bitcoin_wallet.as_ref()).await? - { - let state_clone = state.clone(); - let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); - let cancel_timelock_expires = - state_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - - select! { - state5 = redeem_watcher => { - BobState::BtcRedeemed(state5?) - }, - _ = cancel_timelock_expires => { - BobState::CancelTimelockExpired(state) - } - } - } else { - BobState::CancelTimelockExpired(state) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet.clone(), - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::BtcRedeemed(state) => { - // Bob redeems XMR using revealed s_a - state.claim_xmr(monero_wallet.as_ref()).await?; - - // Ensure that the generated wallet is synced so we have a proper balance - monero_wallet.refresh().await?; - // Sweep (transfer all funds) to the given address - let tx_hashes = monero_wallet.sweep_all(receive_monero_address).await?; - - for tx_hash in tx_hashes { - tracing::info!("Sent XMR to {} in tx {}", receive_monero_address, tx_hash.0); - } - - let state = BobState::XmrRedeemed { - tx_lock_id: state.tx_lock_id(), - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::CancelTimelockExpired(state4) => { - if state4 - .check_for_tx_cancel(bitcoin_wallet.as_ref()) - .await - .is_err() - { - state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; - } - - let state = BobState::BtcCancelled(state4); - db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::BtcCancelled(state) => { - // Bob has cancelled the swap - let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - bail!("Internal error: canceled state reached before cancel timelock was expired"); - } - ExpiredTimelocks::Cancel => { - state.refund_btc(bitcoin_wallet.as_ref()).await?; - BobState::BtcRefunded(state) - } - ExpiredTimelocks::Punish => BobState::BtcPunished { - tx_lock_id: state.tx_lock_id(), - }, - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await - } - BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), - BobState::BtcPunished { tx_lock_id } => Ok(BobState::BtcPunished { tx_lock_id }), - BobState::SafelyAborted => Ok(BobState::SafelyAborted), - BobState::XmrRedeemed { tx_lock_id } => Ok(BobState::XmrRedeemed { tx_lock_id }), + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await } + BobState::BtcCancelled(state) => { + // Bob has cancelled the swap + let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + bail!( + "Internal error: canceled state reached before cancel timelock was expired" + ); + } + ExpiredTimelocks::Cancel => { + state.refund_btc(bitcoin_wallet.as_ref()).await?; + BobState::BtcRefunded(state) + } + ExpiredTimelocks::Punish => BobState::BtcPunished { + tx_lock_id: state.tx_lock_id(), + }, + }; + + let db_state = state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await + } + BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), + BobState::BtcPunished { tx_lock_id } => Ok(BobState::BtcPunished { tx_lock_id }), + BobState::SafelyAborted => Ok(BobState::SafelyAborted), + BobState::XmrRedeemed { tx_lock_id } => Ok(BobState::XmrRedeemed { tx_lock_id }), } } From b1affe3ecf1677f986341eeb206e152f5cd6ac30 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:18:48 +1100 Subject: [PATCH 12/18] Insert latest state and call run_until only once Instead of calling this function in all the branches, we can simply make the whole match statement evaluate to the new state and perform this functionality at the very end. --- swap/src/protocol/alice/swap.rs | 277 ++++++++++---------------------- swap/src/protocol/bob/swap.rs | 195 +++++----------------- 2 files changed, 118 insertions(+), 354 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index adedb9b0..5f520241 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -73,7 +73,7 @@ async fn run_until_internal( return Ok(state); } - match state { + let new_state = match state { AliceState::Started { state3 } => { timeout( env_config.bob_time_to_act, @@ -88,22 +88,7 @@ async fn run_until_internal( }) .await?; - let state = AliceState::BtcLocked { state3 }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + AliceState::BtcLocked { state3 } } AliceState::BtcLocked { state3 } => { // Record the current monero wallet block height so we don't have to scan from @@ -123,105 +108,60 @@ async fn run_until_internal( .send_transfer_proof(transfer_proof) .await?; - let state = AliceState::XmrLocked { + AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - select! { - _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + } => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + select! { + _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, } - enc_sig = event_loop_handle.recv_encrypted_signature() => { - tracing::info!("Received encrypted signature"); + } + enc_sig = event_loop_handle.recv_encrypted_signature() => { + tracing::info!("Received encrypted signature"); - AliceState::EncSigLearned { - state3, - encrypted_signature: Box::new(enc_sig?), - monero_wallet_restore_blockheight, - } + AliceState::EncSigLearned { + state3, + encrypted_signature: Box::new(enc_sig?), + monero_wallet_restore_blockheight, } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }, AliceState::EncSigLearned { state3, encrypted_signature, monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( - *encrypted_signature, - state3.a.clone(), - state3.s_a.to_secpfun_scalar(), - state3.B, - ) { - Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { - Ok((_, finality)) => match finality.await { - Ok(_) => AliceState::BtcRedeemed, - Err(e) => { - bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) - } - }, + } => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( + *encrypted_signature, + state3.a.clone(), + state3.s_a.to_secpfun_scalar(), + state3.B, + ) { + Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { + Ok((_, finality)) => match finality.await { + Ok(_) => AliceState::BtcRedeemed, Err(e) => { - error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); - state3 - .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) - .await?; - - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) } }, Err(e) => { - error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); state3 .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) .await?; @@ -231,29 +171,25 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } + }, + Err(e) => { + error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + state3 + .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) + .await?; + + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }, AliceState::CancelTimelockExpired { state3, monero_wallet_restore_blockheight, @@ -281,24 +217,10 @@ async fn run_until_internal( // returned mined block height } - let state = AliceState::BtcCancelled { + AliceState::BtcCancelled { state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::BtcCancelled { state3, @@ -314,7 +236,7 @@ async fn run_until_internal( status.is_confirmed_with(state3.punish_timelock) }); - let state = tokio::select! { + select! { seen_refund = seen_refund_tx => { seen_refund.context("Failed to monitor refund transaction")?; let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; @@ -338,23 +260,7 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::BtcRefunded { spend_key, @@ -367,11 +273,7 @@ async fn run_until_internal( .create_from(spend_key, view_key, monero_wallet_restore_blockheight) .await?; - let state = AliceState::XmrRefunded; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - Ok(state) + AliceState::XmrRefunded } AliceState::BtcPunishable { state3, @@ -409,51 +311,36 @@ async fn run_until_internal( state3.a.clone(), state3.S_b_bitcoin, )?; - let state = AliceState::BtcRefunded { + AliceState::BtcRefunded { spend_key, state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } Either::Left((Err(e), _)) => { bail!(e.context("Failed to monitor refund transaction")) } - Either::Right(_) => { - let state = AliceState::BtcPunished; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + Either::Right(_) => AliceState::BtcPunished, } } - AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), - AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), - AliceState::BtcPunished => Ok(AliceState::BtcPunished), - AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), - } + AliceState::XmrRefunded => AliceState::XmrRefunded, + AliceState::BtcRedeemed => AliceState::BtcRedeemed, + AliceState::BtcPunished => AliceState::BtcPunished, + AliceState::SafelyAborted => AliceState::SafelyAborted, + }; + + let db_state = (&new_state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + new_state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index f6b7ea78..0a7dabe6 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -66,7 +66,7 @@ async fn run_until_internal( return Ok(state); } - match state { + let new_state = match state { BobState::Started { btc_amount } => { let bitcoin_refund_address = bitcoin_wallet.new_address().await?; @@ -80,21 +80,7 @@ async fn run_until_internal( ) .await?; - let state = BobState::ExecutionSetupDone(state2); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::ExecutionSetupDone(state2) } BobState::ExecutionSetupDone(state2) => { // Do not lock Bitcoin if not connected to Alice. @@ -107,28 +93,12 @@ async fn run_until_internal( .context("Failed to sign Bitcoin lock transaction")?; let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?; - let state = BobState::BtcLocked(state3); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::BtcLocked(state3) } // Bob has locked Btc // Watch for Alice to Lock Xmr or for cancel timelock to elapse BobState::BtcLocked(state3) => { - let state = if let ExpiredTimelocks::None = - state3.current_epoch(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state3.current_epoch(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); @@ -163,30 +133,14 @@ async fn run_until_internal( } else { let state4 = state3.cancel(); BobState::CancelTimelockExpired(state4) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::XmrLockProofReceived { state, lock_transfer_proof, monero_wallet_restore_blockheight, } => { - let state = if let ExpiredTimelocks::None = - state.current_epoch(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; let xmr_lock_watcher = state.clone().watch_for_lock_xmr( @@ -217,27 +171,10 @@ async fn run_until_internal( } else { let state4 = state.cancel(); BobState::CancelTimelockExpired(state4) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::XmrLocked(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; // Alice has locked Xmr // Bob sends Alice his key @@ -261,26 +198,10 @@ async fn run_until_internal( } } else { BobState::CancelTimelockExpired(state) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::EncSigSent(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? { let state_clone = state.clone(); let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); let cancel_timelock_expires = @@ -296,22 +217,7 @@ async fn run_until_internal( } } else { BobState::CancelTimelockExpired(state) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet.clone(), - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::BtcRedeemed(state) => { // Bob redeems XMR using revealed s_a @@ -326,23 +232,9 @@ async fn run_until_internal( tracing::info!("Sent XMR to {} in tx {}", receive_monero_address, tx_hash.0); } - let state = BobState::XmrRedeemed { + BobState::XmrRedeemed { tx_lock_id: state.tx_lock_id(), - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::CancelTimelockExpired(state4) => { if state4 @@ -353,26 +245,11 @@ async fn run_until_internal( state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; } - let state = BobState::BtcCancelled(state4); - db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::BtcCancelled(state4) } BobState::BtcCancelled(state) => { // Bob has cancelled the swap - let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? { + match state.expired_timelock(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { bail!( "Internal error: canceled state reached before cancel timelock was expired" @@ -385,28 +262,28 @@ async fn run_until_internal( ExpiredTimelocks::Punish => BobState::BtcPunished { tx_lock_id: state.tx_lock_id(), }, - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } - BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), - BobState::BtcPunished { tx_lock_id } => Ok(BobState::BtcPunished { tx_lock_id }), - BobState::SafelyAborted => Ok(BobState::SafelyAborted), - BobState::XmrRedeemed { tx_lock_id } => Ok(BobState::XmrRedeemed { tx_lock_id }), - } + BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4), + BobState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id }, + BobState::SafelyAborted => BobState::SafelyAborted, + BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, + }; + + let db_state = new_state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + new_state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await } pub async fn request_price_and_setup( From 776a50137de28f2ca340eb29deba3ba61d1f53e9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:21:31 +1100 Subject: [PATCH 13/18] Use tokio::select macro instead of function This is slightly less verbose and therefore hopefully easier to read. --- swap/src/protocol/alice/swap.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 5f520241..baa734d7 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -10,8 +10,6 @@ use crate::protocol::alice::AliceState; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; use async_recursion::async_recursion; -use futures::future::{select, Either}; -use futures::pin_mut; use rand::{CryptoRng, RngCore}; use std::sync::Arc; use tokio::select; @@ -297,11 +295,10 @@ async fn run_until_internal( let refund_tx_seen = bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen()); - pin_mut!(punish_tx_finalised); - pin_mut!(refund_tx_seen); + select! { + result = refund_tx_seen => { + result.context("Failed to monitor refund transaction")?; - match select(refund_tx_seen, punish_tx_finalised).await { - Either::Left((Ok(()), _)) => { let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; @@ -317,10 +314,9 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } - Either::Left((Err(e), _)) => { - bail!(e.context("Failed to monitor refund transaction")) + _ = punish_tx_finalised => { + AliceState::BtcPunished } - Either::Right(_) => AliceState::BtcPunished, } } AliceState::XmrRefunded => AliceState::XmrRefunded, From 09e2d5b5d798f820fd0ead62b3cd3636009abffa Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:34:46 +1100 Subject: [PATCH 14/18] Simplify code within BobState::XmrLocked By reducing the number of local variables, we can greatly simplify this piece of code. --- swap/src/protocol/bob/swap.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 0a7dabe6..20b059ef 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -178,21 +178,12 @@ async fn run_until_internal( event_loop_handle.dial().await?; // Alice has locked Xmr // Bob sends Alice his key - let tx_redeem_encsig = state.tx_redeem_encsig(); - - let state4_clone = state.clone(); - - let enc_sig_sent_watcher = - event_loop_handle.send_encrypted_signature(tx_redeem_encsig); - let bitcoin_wallet = bitcoin_wallet.clone(); - let cancel_timelock_expires = - state4_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); select! { - _ = enc_sig_sent_watcher => { + _ = event_loop_handle.send_encrypted_signature(state.tx_redeem_encsig()) => { BobState::EncSigSent(state) }, - _ = cancel_timelock_expires => { + _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { BobState::CancelTimelockExpired(state) } } From c32ef92cf5d736d559a1aaf35a8cce240945ebcc Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:42:04 +1100 Subject: [PATCH 15/18] Simplify code within BobState::EncSigSent --- swap/src/protocol/bob/swap.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 20b059ef..4a6cc598 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -193,16 +193,11 @@ async fn run_until_internal( } BobState::EncSigSent(state) => { if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? { - let state_clone = state.clone(); - let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); - let cancel_timelock_expires = - state_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - select! { - state5 = redeem_watcher => { + state5 = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()) => { BobState::BtcRedeemed(state5?) }, - _ = cancel_timelock_expires => { + _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { BobState::CancelTimelockExpired(state) } } From 338f4b82e576cb535f7e7b0e4380beefb829026c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 14:23:55 +1100 Subject: [PATCH 16/18] Introduce dedicated bob::State6 for cancelling --- swap/src/database/bob.rs | 18 ++-- swap/src/protocol/bob/cancel.rs | 18 ++-- swap/src/protocol/bob/refund.rs | 16 ++-- swap/src/protocol/bob/state.rs | 154 ++++++++++++++++++++------------ swap/src/protocol/bob/swap.rs | 8 +- 5 files changed, 127 insertions(+), 87 deletions(-) diff --git a/swap/src/database/bob.rs b/swap/src/database/bob.rs index 943957dd..322e21ba 100644 --- a/swap/src/database/bob.rs +++ b/swap/src/database/bob.rs @@ -29,8 +29,8 @@ pub enum Bob { state4: bob::State4, }, BtcRedeemed(bob::State5), - CancelTimelockExpired(bob::State4), - BtcCancelled(bob::State4), + CancelTimelockExpired(bob::State6), + BtcCancelled(bob::State6), Done(BobEndState), } @@ -38,7 +38,7 @@ pub enum Bob { pub enum BobEndState { SafelyAborted, XmrRedeemed { tx_lock_id: bitcoin::Txid }, - BtcRefunded(Box), + BtcRefunded(Box), BtcPunished { tx_lock_id: bitcoin::Txid }, } @@ -60,9 +60,9 @@ impl From for Bob { BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), - BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4), - BobState::BtcCancelled(state4) => Bob::BtcCancelled(state4), - BobState::BtcRefunded(state4) => Bob::Done(BobEndState::BtcRefunded(Box::new(state4))), + BobState::CancelTimelockExpired(state6) => Bob::CancelTimelockExpired(state6), + BobState::BtcCancelled(state6) => Bob::BtcCancelled(state6), + BobState::BtcRefunded(state6) => Bob::Done(BobEndState::BtcRefunded(Box::new(state6))), BobState::XmrRedeemed { tx_lock_id } => { Bob::Done(BobEndState::XmrRedeemed { tx_lock_id }) } @@ -92,12 +92,12 @@ impl From for BobState { Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), - Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4), - Bob::BtcCancelled(state4) => BobState::BtcCancelled(state4), + Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6), + Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6), Bob::Done(end_state) => match end_state { BobEndState::SafelyAborted => BobState::SafelyAborted, BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, - BobEndState::BtcRefunded(state4) => BobState::BtcRefunded(*state4), + BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6), BobEndState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id }, }, } diff --git a/swap/src/protocol/bob/cancel.rs b/swap/src/protocol/bob/cancel.rs index 90209383..ea9f2d9d 100644 --- a/swap/src/protocol/bob/cancel.rs +++ b/swap/src/protocol/bob/cancel.rs @@ -20,12 +20,12 @@ pub async fn cancel( db: Database, force: bool, ) -> Result> { - let state4 = match state { + let state6 = match state { BobState::BtcLocked(state3) => state3.cancel(), BobState::XmrLockProofReceived { state, .. } => state.cancel(), - BobState::XmrLocked(state4) => state4, - BobState::EncSigSent(state4) => state4, - BobState::CancelTimelockExpired(state4) => state4, + BobState::XmrLocked(state4) => state4.cancel(), + BobState::EncSigSent(state4) => state4.cancel(), + BobState::CancelTimelockExpired(state6) => state6, _ => bail!( "Cannot cancel swap {} because it is in state {} which is not refundable.", swap_id, @@ -34,16 +34,16 @@ pub async fn cancel( }; if !force { - if let ExpiredTimelocks::None = state4.expired_timelock(bitcoin_wallet.as_ref()).await? { + if let ExpiredTimelocks::None = state6.expired_timelock(bitcoin_wallet.as_ref()).await? { return Ok(Err(Error::CancelTimelockNotExpiredYet)); } - if state4 + if state6 .check_for_tx_cancel(bitcoin_wallet.as_ref()) .await .is_ok() { - let state = BobState::BtcCancelled(state4); + let state = BobState::BtcCancelled(state6); let db_state = state.into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; @@ -51,9 +51,9 @@ pub async fn cancel( } } - let txid = state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; + let txid = state6.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; - let state = BobState::BtcCancelled(state4); + let state = BobState::BtcCancelled(state6); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; diff --git a/swap/src/protocol/bob/refund.rs b/swap/src/protocol/bob/refund.rs index b1390045..4fa96778 100644 --- a/swap/src/protocol/bob/refund.rs +++ b/swap/src/protocol/bob/refund.rs @@ -16,14 +16,14 @@ pub async fn refund( db: Database, force: bool, ) -> Result> { - let state4 = if force { + let state6 = if force { match state { BobState::BtcLocked(state3) => state3.cancel(), BobState::XmrLockProofReceived { state, .. } => state.cancel(), - BobState::XmrLocked(state4) => state4, - BobState::EncSigSent(state4) => state4, - BobState::CancelTimelockExpired(state4) => state4, - BobState::BtcCancelled(state4) => state4, + BobState::XmrLocked(state4) => state4.cancel(), + BobState::EncSigSent(state4) => state4.cancel(), + BobState::CancelTimelockExpired(state6) => state6, + BobState::BtcCancelled(state6) => state6, _ => bail!( "Cannot refund swap {} because it is in state {} which is not refundable.", swap_id, @@ -32,16 +32,16 @@ pub async fn refund( } } else { match state { - BobState::BtcCancelled(state4) => state4, + BobState::BtcCancelled(state6) => state6, _ => { return Ok(Err(SwapNotCancelledYet(swap_id))); } } }; - state4.refund_btc(bitcoin_wallet.as_ref()).await?; + state6.refund_btc(bitcoin_wallet.as_ref()).await?; - let state = BobState::BtcRefunded(state4); + let state = BobState::BtcRefunded(state6); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 9db3f852..e1f3c2d6 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -34,9 +34,9 @@ pub enum BobState { XmrLocked(State4), EncSigSent(State4), BtcRedeemed(State5), - CancelTimelockExpired(State4), - BtcCancelled(State4), - BtcRefunded(State4), + CancelTimelockExpired(State6), + BtcCancelled(State6), + BtcRefunded(State6), XmrRedeemed { tx_lock_id: bitcoin::Txid, }, @@ -357,23 +357,17 @@ impl State3 { Ok(()) } - pub fn cancel(&self) -> State4 { - State4 { + pub fn cancel(&self) -> State6 { + State6 { A: self.A, b: self.b.clone(), s_b: self.s_b, - S_a_bitcoin: self.S_a_bitcoin, - v: self.v, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address.clone(), - redeem_address: self.redeem_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(), - // For cancel scenarios the monero wallet rescan blockchain height is irrelevant for - // Bob, because Bob's cancel can only lead to refunding on Bitcoin - monero_wallet_restore_blockheight: BlockHeight { height: 0 }, } } @@ -428,29 +422,6 @@ impl State4 { self.b.encsign(self.S_a_bitcoin, tx_redeem.digest()) } - pub async fn check_for_tx_cancel( - &self, - bitcoin_wallet: &bitcoin::Wallet, - ) -> Result { - let tx_cancel = - bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); - - let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?; - - Ok(tx) - } - - pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { - let transaction = - bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()) - .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) - .context("Failed to complete Bitcoin cancel transaction")?; - - let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?; - - Ok(tx_id) - } - pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address); let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest()); @@ -505,29 +476,18 @@ impl State4 { )) } - pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { - let tx_cancel = - bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); - let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); - - let adaptor = Adaptor::, Deterministic>::default(); - - let sig_b = self.b.sign(tx_refund.digest()); - let sig_a = - adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone()); - - let signed_tx_refund = - tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; - - let (_, finality) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; - - finality.await?; - - Ok(()) - } - - pub fn tx_lock_id(&self) -> bitcoin::Txid { - self.tx_lock.txid() + pub fn cancel(self) -> State6 { + State6 { + A: self.A, + b: self.b, + s_b: self.s_b, + cancel_timelock: self.cancel_timelock, + punish_timelock: self.punish_timelock, + refund_address: self.refund_address, + tx_lock: self.tx_lock, + tx_cancel_sig_a: self.tx_cancel_sig_a, + tx_refund_encsig: self.tx_refund_encsig, + } } } @@ -559,3 +519,83 @@ impl State5 { self.tx_lock.txid() } } + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct State6 { + A: bitcoin::PublicKey, + b: bitcoin::SecretKey, + s_b: monero::Scalar, + cancel_timelock: CancelTimelock, + punish_timelock: PunishTimelock, + refund_address: bitcoin::Address, + tx_lock: bitcoin::TxLock, + tx_cancel_sig_a: Signature, + tx_refund_encsig: bitcoin::EncryptedSignature, +} + +impl State6 { + pub async fn expired_timelock( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { + let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); + + let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?; + let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?; + + Ok(current_epoch( + self.cancel_timelock, + self.punish_timelock, + tx_lock_status, + tx_cancel_status, + )) + } + + pub async fn check_for_tx_cancel( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { + let tx_cancel = + bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); + + let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?; + + Ok(tx) + } + + pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { + let transaction = + bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()) + .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) + .context("Failed to complete Bitcoin cancel transaction")?; + + let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?; + + Ok(tx_id) + } + + pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { + let tx_cancel = + bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); + + let adaptor = Adaptor::, Deterministic>::default(); + + let sig_b = self.b.sign(tx_refund.digest()); + let sig_a = + adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone()); + + let signed_tx_refund = + tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; + + let (_, finality) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; + + finality.await?; + + Ok(()) + } + + pub fn tx_lock_id(&self) -> bitcoin::Txid { + self.tx_lock.txid() + } +} diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 4a6cc598..1e26e04c 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -184,11 +184,11 @@ async fn run_until_internal( BobState::EncSigSent(state) }, _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { - BobState::CancelTimelockExpired(state) + BobState::CancelTimelockExpired(state.cancel()) } } } else { - BobState::CancelTimelockExpired(state) + BobState::CancelTimelockExpired(state.cancel()) } } BobState::EncSigSent(state) => { @@ -198,11 +198,11 @@ async fn run_until_internal( BobState::BtcRedeemed(state5?) }, _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { - BobState::CancelTimelockExpired(state) + BobState::CancelTimelockExpired(state.cancel()) } } } else { - BobState::CancelTimelockExpired(state) + BobState::CancelTimelockExpired(state.cancel()) } } BobState::BtcRedeemed(state) => { From 16dfea035b5dd424a4a42640555ffbeed015708d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 13:42:19 +1100 Subject: [PATCH 17/18] Simplify code within BobState::XmrLockProofReceived To achieve this, we decompose `watch_for_locked_xmr` into two parts: 1. A non-self-consuming function to construct a `WatchRequest` 2. A state transition that can now consume `self` again because it is only called once within the whole select! expression. Ideally, we would move more logic onto this state transition (like comparing the actual amounts and fail the transition if it is not valid). Doing so would have an unfortunate side-effect: We would always wait for the full confirmations before checking whether or not we actually receive enough XMR. This allows us to have state transitions that consume self. --- swap/src/monero.rs | 2 +- swap/src/monero/wallet.rs | 26 ++++++++++++++------ swap/src/protocol/bob/state.rs | 45 +++++++++++++++++++--------------- swap/src/protocol/bob/swap.rs | 35 ++++++++++---------------- 4 files changed, 57 insertions(+), 51 deletions(-) diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 77b7dae9..407f7aa2 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -182,7 +182,7 @@ impl fmt::Display for TxHash { } #[derive(Debug, Clone, Copy, thiserror::Error)] -#[error("transaction does not pay enough: expected {expected}, got {actual}")] +#[error("expected {expected}, got {actual}")] pub struct InsufficientFunds { pub expected: Amount, pub actual: Amount, diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index 61894b99..db9c7c57 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -150,14 +150,15 @@ impl Wallet { )) } - pub async fn watch_for_transfer( - &self, - public_spend_key: PublicKey, - public_view_key: PublicViewKey, - transfer_proof: TransferProof, - expected: Amount, - conf_target: u32, - ) -> Result<(), InsufficientFunds> { + pub async fn watch_for_transfer(&self, request: WatchRequest) -> Result<()> { + let WatchRequest { + conf_target, + public_view_key, + public_spend_key, + transfer_proof, + expected, + } = request; + let txid = transfer_proof.tx_hash(); tracing::info!(%txid, "Waiting for {} confirmation{} of Monero transaction", conf_target, if conf_target > 1 { "s" } else { "" }); @@ -230,6 +231,15 @@ pub struct TransferRequest { pub amount: Amount, } +#[derive(Debug)] +pub struct WatchRequest { + pub public_spend_key: PublicKey, + pub public_view_key: PublicViewKey, + pub transfer_proof: TransferProof, + pub conf_target: u32, + pub expected: Amount, +} + async fn wait_for_confirmations( txid: String, fetch_tx: impl Fn(String) -> Fut, diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index e1f3c2d6..2682b85a 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -3,7 +3,8 @@ use crate::bitcoin::{ TxLock, Txid, }; use crate::monero; -use crate::monero::{monero_private_key, InsufficientFunds, TransferProof}; +use crate::monero::wallet::WatchRequest; +use crate::monero::{monero_private_key, TransferProof}; use crate::monero_ext::ScalarExt; use crate::protocol::alice::{Message1, Message3}; use crate::protocol::bob::{EncryptedSignature, Message0, Message2, Message4}; @@ -305,30 +306,22 @@ pub struct State3 { } impl State3 { - pub async fn watch_for_lock_xmr( - self, - xmr_wallet: &monero::Wallet, - transfer_proof: TransferProof, - monero_wallet_restore_blockheight: BlockHeight, - ) -> Result> { + pub fn lock_xmr_watch_request(&self, transfer_proof: TransferProof) -> WatchRequest { let S_b_monero = monero::PublicKey::from_private_key(&monero::PrivateKey::from_scalar(self.s_b)); let S = self.S_a_monero + S_b_monero; - if let Err(e) = xmr_wallet - .watch_for_transfer( - S, - self.v.public(), - transfer_proof, - self.xmr, - self.min_monero_confirmations, - ) - .await - { - return Ok(Err(e)); + WatchRequest { + public_spend_key: S, + public_view_key: self.v.public(), + transfer_proof, + conf_target: self.min_monero_confirmations, + expected: self.xmr, } + } - Ok(Ok(State4 { + pub fn xmr_locked(self, monero_wallet_restore_blockheight: BlockHeight) -> State4 { + State4 { A: self.A, b: self.b, s_b: self.s_b, @@ -342,7 +335,7 @@ impl State3 { tx_cancel_sig_a: self.tx_cancel_sig_a, tx_refund_encsig: self.tx_refund_encsig, monero_wallet_restore_blockheight, - })) + } } pub async fn wait_for_cancel_timelock_to_expire( @@ -595,6 +588,18 @@ impl State6 { Ok(()) } + pub async fn wait_for_cancel_timelock_to_expire( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result<()> { + bitcoin_wallet + .watch_until_status(&self.tx_lock, |status| { + status.is_confirmed_with(self.cancel_timelock) + }) + .await?; + Ok(()) + } + pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 1e26e04c..f13e5ffc 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1,7 +1,6 @@ use crate::bitcoin::ExpiredTimelocks; use crate::database::{Database, Swap}; use crate::env::Config; -use crate::monero::InsufficientFunds; use crate::protocol::bob; use crate::protocol::bob::event_loop::EventLoopHandle; use crate::protocol::bob::state::*; @@ -11,7 +10,7 @@ use async_recursion::async_recursion; use rand::rngs::OsRng; use std::sync::Arc; use tokio::select; -use tracing::{trace, warn}; +use tracing::trace; use uuid::Uuid; pub fn is_complete(state: &BobState) -> bool { @@ -143,34 +142,26 @@ async fn run_until_internal( if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; - let xmr_lock_watcher = state.clone().watch_for_lock_xmr( - monero_wallet.as_ref(), - lock_transfer_proof, - monero_wallet_restore_blockheight, - ); - let cancel_timelock_expires = - state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + let watch_request = state.lock_xmr_watch_request(lock_transfer_proof); select! { - state4 = xmr_lock_watcher => { - match state4? { - Ok(state4) => BobState::XmrLocked(state4), - Err(InsufficientFunds {..}) => { - warn!("The other party has locked insufficient Monero funds! Waiting for refund..."); + received_xmr = monero_wallet.watch_for_transfer(watch_request) => { + match received_xmr { + Ok(()) => BobState::XmrLocked(state.xmr_locked(monero_wallet_restore_blockheight)), + Err(e) => { + tracing::warn!("Waiting for refund because insufficient Monero have been locked! {}", e); state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?; - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) + + BobState::CancelTimelockExpired(state.cancel()) }, } - }, - _ = cancel_timelock_expires => { - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) + } + _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { + BobState::CancelTimelockExpired(state.cancel()) } } } else { - let state4 = state.cancel(); - BobState::CancelTimelockExpired(state4) + BobState::CancelTimelockExpired(state.cancel()) } } BobState::XmrLocked(state) => { From f49f8977d329ff7e826ffc1e667d75686ba10f9b Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Mar 2021 18:09:30 +1100 Subject: [PATCH 18/18] Remove dead code --- swap/src/protocol/bob/state.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 2682b85a..28aa05a0 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -588,18 +588,6 @@ impl State6 { Ok(()) } - pub async fn wait_for_cancel_timelock_to_expire( - &self, - bitcoin_wallet: &bitcoin::Wallet, - ) -> Result<()> { - bitcoin_wallet - .watch_until_status(&self.tx_lock, |status| { - status.is_confirmed_with(self.cancel_timelock) - }) - .await?; - Ok(()) - } - pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() }