From 183e8f02de45c8bd12836b48ca32c5742acfeace Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Thu, 25 Mar 2021 19:55:54 +1100 Subject: [PATCH] Wait for lock tx and send transfer proof in separate state Sending the transfer transaction in a distinct state helps ensuring that we do not send the Monero lock transaction twice in a restart scenario. Waiting for the first transaction confirmation in a separate state helps ensuring that we send the transfer proof in a restart scenario. --- swap/src/database/alice.rs | 52 +++++++++++++++++++- swap/src/protocol/alice/state.rs | 14 ++++++ swap/src/protocol/alice/swap.rs | 84 ++++++++++++++++++++++---------- swap/tests/harness/mod.rs | 4 +- 4 files changed, 126 insertions(+), 28 deletions(-) diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs index b2424fba..9af07122 100644 --- a/swap/src/database/alice.rs +++ b/swap/src/database/alice.rs @@ -1,6 +1,6 @@ use crate::bitcoin::EncryptedSignature; use crate::monero; -use crate::monero::monero_private_key; +use crate::monero::{monero_private_key, TransferProof}; use crate::protocol::alice; use crate::protocol::alice::AliceState; use ::bitcoin::hashes::core::fmt::Display; @@ -18,7 +18,17 @@ pub enum Alice { BtcLocked { state3: alice::State3, }, + XmrLockTransactionSent { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: alice::State3, + }, XmrLocked { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: alice::State3, + }, + XmrLockTransferProofSent { monero_wallet_restore_blockheight: BlockHeight, state3: alice::State3, }, @@ -65,10 +75,28 @@ impl From<&AliceState> for Alice { AliceState::BtcLocked { state3 } => Alice::BtcLocked { state3: state3.as_ref().clone(), }, + AliceState::XmrLockTransactionSent { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + } => Alice::XmrLockTransactionSent { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + transfer_proof: transfer_proof.clone(), + state3: state3.as_ref().clone(), + }, AliceState::XmrLocked { monero_wallet_restore_blockheight, + transfer_proof, state3, } => Alice::XmrLocked { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + transfer_proof: transfer_proof.clone(), + state3: state3.as_ref().clone(), + }, + AliceState::XmrLockTransferProofSent { + monero_wallet_restore_blockheight, + state3, + } => Alice::XmrLockTransferProofSent { monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, state3: state3.as_ref().clone(), }, @@ -130,10 +158,28 @@ impl From for AliceState { Alice::BtcLocked { state3 } => AliceState::BtcLocked { state3: Box::new(state3), }, + Alice::XmrLockTransactionSent { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + } => AliceState::XmrLockTransactionSent { + monero_wallet_restore_blockheight, + transfer_proof, + state3: Box::new(state3), + }, Alice::XmrLocked { monero_wallet_restore_blockheight, + transfer_proof, state3, } => AliceState::XmrLocked { + monero_wallet_restore_blockheight, + transfer_proof, + state3: Box::new(state3), + }, + Alice::XmrLockTransferProofSent { + monero_wallet_restore_blockheight, + state3, + } => AliceState::XmrLockTransferProofSent { monero_wallet_restore_blockheight, state3: Box::new(state3), }, @@ -192,7 +238,11 @@ impl Display for Alice { match self { Alice::Started { .. } => write!(f, "Started"), Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"), + Alice::XmrLockTransactionSent { .. } => f.write_str("Monero lock transaction sent"), Alice::XmrLocked { .. } => f.write_str("Monero locked"), + Alice::XmrLockTransferProofSent { .. } => { + f.write_str("Monero lock transfer proof sent") + } Alice::CancelTimelockExpired { .. } => f.write_str("Cancel timelock is expired"), Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"), Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"), diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index fe0f5679..d771b156 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -22,7 +22,17 @@ pub enum AliceState { BtcLocked { state3: Box, }, + XmrLockTransactionSent { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: Box, + }, XmrLocked { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: Box, + }, + XmrLockTransferProofSent { monero_wallet_restore_blockheight: BlockHeight, state3: Box, }, @@ -59,7 +69,11 @@ impl fmt::Display for AliceState { match self { AliceState::Started { .. } => write!(f, "started"), AliceState::BtcLocked { .. } => write!(f, "btc is locked"), + AliceState::XmrLockTransactionSent { .. } => write!(f, "xmr lock transaction sent"), AliceState::XmrLocked { .. } => write!(f, "xmr is locked"), + AliceState::XmrLockTransferProofSent { .. } => { + write!(f, "xmr lock transfer proof sent") + } AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"), AliceState::BtcRedeemed => write!(f, "btc is redeemed"), AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"), diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index e090e8d5..98333390 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -5,6 +5,7 @@ use crate::env::Config; use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; use crate::protocol::alice::AliceState; +use crate::protocol::alice::AliceState::XmrLockTransferProofSent; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; use rand::{CryptoRng, RngCore}; @@ -88,43 +89,76 @@ async fn next_state( } } } - AliceState::BtcLocked { state3 } => match state3 - .expired_timelocks(bitcoin_wallet) - .await? - { + AliceState::BtcLocked { state3 } => { + match state3.expired_timelocks(bitcoin_wallet).await? { + ExpiredTimelocks::None => { + // 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 transfer_proof = monero_wallet + .transfer(state3.lock_xmr_transfer_request()) + .await?; + + AliceState::XmrLockTransactionSent { + state3, + transfer_proof, + monero_wallet_restore_blockheight, + } + } + _ => AliceState::SafelyAborted, + } + } + AliceState::XmrLockTransactionSent { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + } => match state3.expired_timelocks(bitcoin_wallet).await? { ExpiredTimelocks::None => { - // 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 transfer_proof = monero_wallet - .transfer(state3.lock_xmr_transfer_request()) - .await?; - monero_wallet .watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1)) .await?; - // TODO: Waiting for XMR confirmations should be done 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.clone()) - .await?; - - monero_wallet - .watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof, 10)) - .await?; - AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, + transfer_proof, } } - _ => AliceState::SafelyAborted, + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, }, + AliceState::XmrLocked { + state3, + transfer_proof, + monero_wallet_restore_blockheight, + } => match state3.expired_timelocks(bitcoin_wallet).await? { + ExpiredTimelocks::None => { + event_loop_handle + .send_transfer_proof(transfer_proof.clone()) + .await?; + + // TODO: Handle this upon refund instead. + // Make sure that the balance of the created wallet is unlocked instead of + // watching for transfer. + monero_wallet + .watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof, 10)) + .await?; + + XmrLockTransferProofSent { + state3, + monero_wallet_restore_blockheight, + } + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }, + AliceState::XmrLockTransferProofSent { state3, monero_wallet_restore_blockheight, } => { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index e41f4ab1..bb4f7444 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -790,8 +790,8 @@ struct Containers<'a> { pub mod alice_run_until { use swap::protocol::alice::AliceState; - pub fn is_xmr_locked(state: &AliceState) -> bool { - matches!(state, AliceState::XmrLocked { .. }) + pub fn is_xmr_lock_transaction_sent(state: &AliceState) -> bool { + matches!(state, AliceState::XmrLockTransactionSent { .. }) } pub fn is_encsig_learned(state: &AliceState) -> bool {