From 529de8d5fd1c81dfa8d0d00e6b3f9994db26cd59 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 22 Jun 2021 20:26:26 +1000 Subject: [PATCH] ASB aborts if CLI does not lock BTC Includes a new state that is used to await BTC lock tx finality. Upon starting the swap we initially only wait for the BTC lock tx to be seen in the mempool. This is guarded by a short timeout (3 mins), because it is assumed that in the current setup (sport_price + execution_setup only triggered upon funds being available already) the lock transaction should be picked up almost instanly after the execution setup succeeded. --- swap/src/database/alice.rs | 12 +++++++++++ swap/src/env.rs | 8 +++++-- swap/src/protocol/alice/recovery/cancel.rs | 1 + swap/src/protocol/alice/recovery/punish.rs | 3 ++- swap/src/protocol/alice/recovery/redeem.rs | 1 + swap/src/protocol/alice/recovery/refund.rs | 1 + .../protocol/alice/recovery/safely_abort.rs | 4 +++- swap/src/protocol/alice/state.rs | 6 ++++++ swap/src/protocol/alice/swap.rs | 21 +++++++++++++++++++ 9 files changed, 53 insertions(+), 4 deletions(-) diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs index 3c62c006..3358238f 100644 --- a/swap/src/database/alice.rs +++ b/swap/src/database/alice.rs @@ -15,6 +15,9 @@ pub enum Alice { Started { state3: alice::State3, }, + BtcLockTransactionSeen { + state3: alice::State3, + }, BtcLocked { state3: alice::State3, }, @@ -81,6 +84,9 @@ impl From<&AliceState> for Alice { AliceState::Started { state3 } => Alice::Started { state3: state3.as_ref().clone(), }, + AliceState::BtcLockTransactionSeen { state3 } => Alice::BtcLockTransactionSeen { + state3: state3.as_ref().clone(), + }, AliceState::BtcLocked { state3 } => Alice::BtcLocked { state3: state3.as_ref().clone(), }, @@ -179,6 +185,9 @@ impl From for AliceState { Alice::Started { state3 } => AliceState::Started { state3: Box::new(state3), }, + Alice::BtcLockTransactionSeen { state3 } => AliceState::BtcLockTransactionSeen { + state3: Box::new(state3), + }, Alice::BtcLocked { state3 } => AliceState::BtcLocked { state3: Box::new(state3), }, @@ -278,6 +287,9 @@ impl fmt::Display for Alice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Alice::Started { .. } => write!(f, "Started"), + Alice::BtcLockTransactionSeen { .. } => { + write!(f, "Bitcoin lock transaction in mempool") + } Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"), Alice::XmrLockTransactionSent { .. } => f.write_str("Monero lock transaction sent"), Alice::XmrLocked { .. } => f.write_str("Monero locked"), diff --git a/swap/src/env.rs b/swap/src/env.rs index 95d4488a..e12390a8 100644 --- a/swap/src/env.rs +++ b/swap/src/env.rs @@ -6,6 +6,7 @@ use time::NumericalStdDurationShort; #[derive(Debug, Copy, Clone, PartialEq)] pub struct Config { + pub bitcoin_lock_mempool_timeout: Duration, pub bitcoin_lock_confirmed_timeout: Duration, pub bitcoin_finality_confirmations: u32, pub bitcoin_avg_block_time: Duration, @@ -43,7 +44,8 @@ pub struct Regtest; impl GetConfig for Mainnet { fn get_config() -> Config { Config { - bitcoin_lock_confirmed_timeout: 24.hours(), + bitcoin_lock_mempool_timeout: 3.minutes(), + bitcoin_lock_confirmed_timeout: 2.hours(), bitcoin_finality_confirmations: 2, bitcoin_avg_block_time: 10.minutes(), bitcoin_cancel_timelock: CancelTimelock::new(72), @@ -59,7 +61,8 @@ impl GetConfig for Mainnet { impl GetConfig for Testnet { fn get_config() -> Config { Config { - bitcoin_lock_confirmed_timeout: 12.hours(), + bitcoin_lock_mempool_timeout: 3.minutes(), + bitcoin_lock_confirmed_timeout: 1.hours(), bitcoin_finality_confirmations: 2, bitcoin_avg_block_time: 10.minutes(), bitcoin_cancel_timelock: CancelTimelock::new(12), @@ -75,6 +78,7 @@ impl GetConfig for Testnet { impl GetConfig for Regtest { fn get_config() -> Config { Config { + bitcoin_lock_mempool_timeout: 30.seconds(), bitcoin_lock_confirmed_timeout: 1.minutes(), bitcoin_finality_confirmations: 1, bitcoin_avg_block_time: 5.seconds(), diff --git a/swap/src/protocol/alice/recovery/cancel.rs b/swap/src/protocol/alice/recovery/cancel.rs index fedab340..c3168026 100644 --- a/swap/src/protocol/alice/recovery/cancel.rs +++ b/swap/src/protocol/alice/recovery/cancel.rs @@ -23,6 +23,7 @@ pub async fn cancel( // In case no XMR has been locked, move to Safely Aborted AliceState::Started { .. } + | AliceState::BtcLockTransactionSeen { .. } | AliceState::BtcLocked { .. } => bail!("Cannot cancel swap {} because it is in state {} where no XMR was locked.", swap_id, state), AliceState::XmrLockTransactionSent { monero_wallet_restore_blockheight, transfer_proof, state3, } diff --git a/swap/src/protocol/alice/recovery/punish.rs b/swap/src/protocol/alice/recovery/punish.rs index 6ffacdf5..965fdba3 100644 --- a/swap/src/protocol/alice/recovery/punish.rs +++ b/swap/src/protocol/alice/recovery/punish.rs @@ -36,7 +36,8 @@ pub async fn punish( AliceState::Started { .. } => bail!(Error::NoBtcLocked(state)), // Punish potentially possible (no knowledge of cancel transaction) - AliceState::BtcLocked { state3, .. } + AliceState::BtcLockTransactionSeen { state3 } + | AliceState::BtcLocked { state3, .. } | AliceState::XmrLockTransactionSent {state3, ..} | AliceState::XmrLocked {state3, ..} | AliceState::XmrLockTransferProofSent {state3, ..} diff --git a/swap/src/protocol/alice/recovery/redeem.rs b/swap/src/protocol/alice/recovery/redeem.rs index 55450c87..8344db7b 100644 --- a/swap/src/protocol/alice/recovery/redeem.rs +++ b/swap/src/protocol/alice/recovery/redeem.rs @@ -84,6 +84,7 @@ pub async fn redeem( Ok((txid, state)) } AliceState::Started { .. } + | AliceState::BtcLockTransactionSeen { .. } | AliceState::BtcLocked { .. } | AliceState::XmrLockTransactionSent { .. } | AliceState::XmrLocked { .. } diff --git a/swap/src/protocol/alice/recovery/refund.rs b/swap/src/protocol/alice/recovery/refund.rs index bb26ee67..3297a454 100644 --- a/swap/src/protocol/alice/recovery/refund.rs +++ b/swap/src/protocol/alice/recovery/refund.rs @@ -39,6 +39,7 @@ pub async fn refund( // In case no XMR has been locked, move to Safely Aborted AliceState::Started { .. } + | AliceState::BtcLockTransactionSeen { .. } | AliceState::BtcLocked { .. } => bail!(Error::NoXmrLocked(state)), // Refund potentially possible (no knowledge of cancel transaction) diff --git a/swap/src/protocol/alice/recovery/safely_abort.rs b/swap/src/protocol/alice/recovery/safely_abort.rs index 29df2814..8105f068 100644 --- a/swap/src/protocol/alice/recovery/safely_abort.rs +++ b/swap/src/protocol/alice/recovery/safely_abort.rs @@ -8,7 +8,9 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc) -> Result { + AliceState::Started { .. } + | AliceState::BtcLockTransactionSeen { .. } + | AliceState::BtcLocked { .. } => { let state = AliceState::SafelyAborted; let db_state = (&state).into(); diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index 17522a76..a2b1a6a1 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -21,6 +21,9 @@ pub enum AliceState { Started { state3: Box, }, + BtcLockTransactionSeen { + state3: Box, + }, BtcLocked { state3: Box, }, @@ -79,6 +82,9 @@ impl fmt::Display for AliceState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AliceState::Started { .. } => write!(f, "started"), + AliceState::BtcLockTransactionSeen { .. } => { + write!(f, "bitcoin lock transaction in mempool") + } AliceState::BtcLocked { .. } => write!(f, "btc is locked"), AliceState::XmrLockTransactionSent { .. } => write!(f, "xmr lock transaction sent"), AliceState::XmrLocked { .. } => write!(f, "xmr is locked"), diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index bf71895d..4070a04b 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -70,6 +70,27 @@ where Ok(match state { AliceState::Started { state3 } => { + let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; + match timeout( + env_config.bitcoin_lock_mempool_timeout, + tx_lock_status.wait_until_seen(), + ) + .await + { + Err(_) => { + info!( + minutes = %env_config.bitcoin_lock_mempool_timeout.as_secs_f64() / 60.0, + "TxLock lock was not seen in mempool in time", + ); + AliceState::SafelyAborted + } + Ok(res) => { + res?; + AliceState::BtcLockTransactionSeen { state3 } + } + } + } + AliceState::BtcLockTransactionSeen { state3 } => { let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; match timeout( env_config.bitcoin_lock_confirmed_timeout,