From c4c798ea2063e254049d784bbd857a07653d8343 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 22 Jun 2021 17:16:40 +1000 Subject: [PATCH 1/3] Timeout on `execution_setup` for ASB Similar to the CLI the ASB has to ensure that the execution_setup is executed within a certain time. Without a timeout the price (returned by `spot_price` would be guaranteed with the CLI indefinitely. --- swap/src/protocol/alice/execution_setup.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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? } + }); } } From 529de8d5fd1c81dfa8d0d00e6b3f9994db26cd59 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 22 Jun 2021 20:26:26 +1000 Subject: [PATCH 2/3] 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, From 8e80628a71f5b965b6a34b16f2fe98d1a8179990 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 23 Jun 2021 15:42:15 +1000 Subject: [PATCH 3/3] Changelog entry for price guarantee changes --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) 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.