diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f1d5fc..ea24f3ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Printing the deposit address to the terminal as a QR code. To not break automated scripts or integrations with other software, this behaviour is disabled if `--json` is passed to the application. +### Fixed + +- An issue where the ASB gives long price guarantees when setting up a swap. + Now, after sending a spot price the ASB will wait for one minute for the CLI's to trigger the execution setup, and three minutes to see the BTC lock transaction of the CLI in mempool after the swap started. + If the first timeout is triggered the execution setup will be aborted, if the second timeout is triggered the swap will be safely aborted. + ### Removed - The websocket transport from the CLI. 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/execution_setup.rs b/swap/src/protocol/alice/execution_setup.rs index eb068256..21a371a9 100644 --- a/swap/src/protocol/alice/execution_setup.rs +++ b/swap/src/protocol/alice/execution_setup.rs @@ -4,6 +4,7 @@ use crate::protocol::{alice, Message0, Message2, Message4}; use anyhow::{Context, Error}; use libp2p::PeerId; use libp2p_async_await::BehaviourOutEvent; +use std::time::Duration; use uuid::Uuid; #[derive(Debug)] @@ -49,8 +50,8 @@ impl Default for Behaviour { impl Behaviour { pub fn run(&mut self, bob: PeerId, state0: State0) { - self.inner - .do_protocol_listener(bob, move |mut substream| async move { + self.inner.do_protocol_listener(bob, move |mut substream| { + let protocol = async move { let message0 = serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) .context("Failed to deserialize message0")?; @@ -83,7 +84,10 @@ impl Behaviour { let state3 = state2.receive(message4)?; Ok((bob, (swap_id, state3))) - }) + }; + + async move { tokio::time::timeout(Duration::from_secs(60), protocol).await? } + }); } } 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,